Prepare and test your skills
Prepare and test your skills
Writing unit tests with the help of AI coding assistants is the practice of letting an AI draft test code and then having a developer validate and complete it. The assistant can quickly propose tests for cloud-native applications, but every assertion must be checked against real code behavior. The final test is trustworthy only after the developer adds missing branches, edge cases, and mocks for cloud dependencies.
Generating draft tests with AI assistants starts with a prompt that contains the function under test and a clear instruction. In one example, the prompt prefix Write a unit test for this function: is followed by the full Python is_leap_year(year) function and sent with temperature 0.5 and maxOutputTokens 256 to the code-bison@001 model through the Vertex AI prediction service. Cloud Code provides this workflow inside the integrated development environment (IDE) by integrating with Gemini Code Assist Standard and Enterprise, an AI-powered collaborator that generates code and offers inline suggestions while coding. Cloud Code with Gemini Code Assist is not auto-enabled by default and requires connecting to Google Cloud, selecting a Google Cloud project, and enabling Identity and Access Management (IAM) permissions before use.
The generated output is only a starting proposal because generative models have randomness and variability, even for identical inputs. That variability makes it hard to generate a comprehensive set of cases for open-ended outputs and creates reproducibility issues when the same input does not produce the same result. Human validation therefore treats every AI suggestion as untrusted until it is executed, reviewed, and refined against the real behavior of the code.
Checking accuracy and coverage means verifying that every assertion matches the actual branching logic and that both happy paths and failure paths are tested. For the is_leap_year example, a complete test must verify the full decision chain where a year divisible by 4 is a leap year, except when divisible by 100, except when also divisible by 400. Code coverage is tracked at the project level to ensure expected behavior is validated, and any critical user journey must also have integration tests that validate all necessary components and dependencies.
This evaluation follows the shift-left principle, which moves testing earlier into development because fixing a bug during development has no customer impact and no context-switching overhead. The development flow moves from writing code and corresponding tests, to running written tests plus presubmit checks, to requesting iterative human review, and only after author and reviewer reach consensus does code flow from the development environment into the shared repository. Because coding standards block submission when tests fail, early validation prevents incomplete AI tests from reaching later qualification and rollout phases.
Adding edge cases and runtime checks is the manual step where the developer adds complex inputs that predetermined AI examples usually miss. Fuzzing complements unit tests by bombarding an application with random inputs to expose hidden flaws, security vulnerabilities, or crashes that users might trigger in unexpected ways. For generative AI behavior, teams also use adversarial testing to identify vulnerabilities and failure modes, plus subgroup analysis, counterfactual generation, human-in-the-loop evaluation, and continuous monitoring with automatic metrics for quality, safety, instruction adherence, grounding, style, and verbosity.
Boundary refinement also requires explicit error-path tests around invalid types, null values, out-of-range numbers, and failed dependencies. Static analysis finds erroneous, inefficient, or deprecated patterns without running code, while dynamic analysis compiles and runs code to uncover issues that only surface during execution. The dynamic tools for this runtime validation are:
Exam tip: Unit tests validate expected behavior with predetermined inputs and outputs, while fuzzing validates unexpected behavior with random inputs — use both when an AI draft only covers the happy path.
Isolating tests with mocks and emulators means replacing live Google Cloud dependencies with locally controlled doubles so unit tests run fast and safely. The test then runs against that double instead of against production infrastructure, so failures point to code defects rather than network issues, permissions, or shared test data. Isolation also avoids impacting production services, saves costs during repeated runs, and lets developers iterate faster during development.
An AI assistant can draft mock or stub code inside the IDE that matches idiomatic patterns for Go, Java, Node.js, Python, and .NET Core using Cloud Client Libraries. Generated mocks must still be reviewed because the assistant can invent methods, omit error paths, or simulate success when the real service would return permission, quota, or not-found errors. For higher fidelity, Google Cloud SDK emulators are local processes that simulate service behavior, where requests flow from the test to the local emulator and responses flow back from the emulator to the test without crossing a production trust boundary. Choose lightweight mocks or stubs when the goal is pure unit isolation and speed, and choose an emulator when the goal is behavioral fidelity, such as verifying Firestore queries or Pub/Sub message handling.
Writing prompts with enough context means giving the AI the surrounding detail it needs to produce a relevant and runnable test suite. A vague request such as “write a test” often produces generic code that does not compile or misses edge cases. A contextual prompt instead supplies the function source, expected inputs and outputs, and any cloud dependencies so the model can generate assertions that match real behavior.
Include the following peer pieces of context in the prompt when they apply to the code under test:
When the function depends on a cloud service, state the dependency boundary explicitly. Application code sits on one side of the boundary and the managed service sits on the other side. The generated test should run against the application code and use fakes or mocks for calls that would otherwise flow from the test to the live service. A programmatic call packages this prompt as an instance value and parameters value, builds an endpoint name from project, location, publisher google, and model code-bison@001, and then calls predictionServiceClient.predict(endpointName, instances, parameterValue) against the regional endpoint us-central1-aiplatform.googleapis.com:443.
Validating tests before merging means checking that AI-produced code actually compiles, passes, and asserts correct behavior before it enters the codebase. The developer runs the suite locally, fixes imports and mocks, and confirms coverage of branches before submitting. Automated testing in continuous integration and continuous delivery (CI/CD) pipelines then enforces this validation on every change. Cloud Build is a fully managed build service that defines and executes build steps on code pushes and pull requests, while Cloud Deploy promotes releases through testing, staging, and production with controls such as traffic splitting and rollback.
The three common test levels divide validation work by scope:
| Test type | Scope | Choose when |
|---|---|---|
| Unit testing | Single function or method in isolation | The goal is to verify logic, branches, and edge cases for AI-generated assertions |
| Integration testing | Interaction between components or modules | The goal is to verify that mocked cloud clients and real application code work together |
| End-to-end testing | Full application in a realistic scenario | The goal is to simulate user journeys after unit and integration tests pass |
Presubmit checks extend this discipline by running unit tests, hermetic integration tests, and static and dynamic analysis continuously while code is proposed and before human review. A global presubmit can test a widely used change against a large codebase to catch cross-project breakage.
Continue with this study guide: Writing unit tests with the help of AI coding assistants
Executing automated integration tests in Cloud Build (opens in a new tab) is the practice of defining an ordered set of containerized tasks that install dependencies, run tests, and publish artifacts only when tests pass. Each task runs in its own isolated container in a fixed order, so integration tests are reproducible and later steps run only when earlier tests succeed. Build results and logs flow from each worker to the Build History page, the gcloud CLI, and the Cloud Build API for inspection.
Defining ordered pipelines with build config files means declaring the source to test, the suites to run, and the artifacts to produce in YAML or JSON. A Cloud Build configuration file is typically named cloudbuild.yaml or cloudbuild.json and contains a steps list. Each entry in the steps list is a build step with a name field that selects a container image, an args field that passes arguments to that image, and an optional entrypoint field that overrides the default command.
The standard lifecycle submitted through the Cloud Build API flows in a fixed order:
requirements.txt and a Dockerfile.docker run.This order creates a dependency chain where the test pipeline depends on the configuration file and the published artifact depends on successful completion of all prior steps. Executing build steps is like executing commands in a script, so Cloud Build runs the steps in listed order and stops the build when a step fails.
Running steps in isolated containers means each build step runs in its own Docker container on its own ephemeral worker. Cloud Build provisions a new virtual machine environment for every build and destroys it afterward, so no residual files affect the next run. Ephemerality also improves security by removing the chance for an attacker to leave malicious files in a reused environment and reduces maintenance from managing long-lived builders.
The container image selected by the name field controls the tools available in that isolated environment:
gcr.io/cloud-builders/docker for Docker commands.python, Ubuntu, and Gradle, including the public python image preinstalled with python and pip.Choose a supported builder such as gcr.io/cloud-builders/docker when running standard docker build and docker push operations, choose a language image such as python when installing dependencies and running test frameworks, and choose google/cloud-sdk when invoking gcloud commands. A typical Python pipeline first runs pip install -r requirements.txt --user in a python image with entrypoint: pip, where the --user flag ensures modules remain accessible to subsequent steps. The test step then runs in the same python image with entrypoint: python and args such as -m pytest --junitxml=${SHORT_SHA}_test_log.xml, where substitutions like ${SHORT_SHA}, ${PROJECT_ID}, and custom $_BUCKET_NAME tag images and logs with the commit that produced them.
Linking ephemeral services over the cloudbuild network lets test steps reach backing services that were started as temporary containers for one build. Every build-step container is attached to a local Docker network literally named cloudbuild. The cloudbuild network allows containers from different steps in the same build to communicate and share data from the shared workspace. The flow moves from the dependency container to the network to the test container, which simulates production-like interaction between microservices without requiring external infrastructure.
Because the network exists only inside the ephemeral worker, all linked services disappear together when Cloud Build destroys the virtual machine (VM) after the build. A typical integration-test configuration uses these steps to fetch dependencies, run unit tests, run static analyses and integration tests, and then create artifacts such as Docker containers or Java archives with tools including docker, gradle, maven, bazel, and gulp. Guidance for Python projects is described in build and test Python applications (opens in a new tab) and build, test, and containerize Python applications. If the build needs resources in a private network rather than the public internet available to the default pool, the build depends on a private pool, which is a dedicated, customer-specific set of workers with private-network access.
Protecting credentials with Secret Manager (opens in a new tab) means storing passwords, API keys, and other sensitive values outside source code and build files. Integration tests that need a database password, third-party API key, or Docker credential store that value in Secret Manager and configure Cloud Build to read it during the build. Before use, the Cloud Build and Secret Manager APIs must be enabled, which requires the serviceusage.services.enable permission through the Owner role or Service Usage Admin role, and the secret name and version must be recorded.
Access to secrets is controlled by granting the build service account the Secret Manager Secret Accessor role (roles/secretmanager.secretAccessor) on the specific secret. The service account that runs the build fetches the secret value, so developers do not need direct access to run tests. Access is granted in the Google Cloud console:
Granting this role to the legacy Cloud Build service account carries extra risk because any user who runs a build through a build trigger inherits the service account permissions at build time. Mapping secrets into steps connects a secret version to an environment variable inside a specific step. After all build steps, an availableSecrets field with a secretManager entry defines each mapping through versionName and env, and the test step declares which mapped variables it may use through its secretEnv field.
Exam tip: A build step can only read a mapped secret when it sets secretEnv for that variable, uses the bash entrypoint, and starts args with -c.
Using test results as quality gates means the outcome of integration test steps decides whether later pipeline work is allowed to run. Test evidence flows from the test container to durable storage through the artifacts and images fields. The artifacts.objects field copies the JUnit XML log to a Cloud Storage location such as gs://${_BUCKET_NAME}/, while the images field pushes the built container to Artifact Registry and surfaces it in build results. Use the images field plus requestedVerifyOption: VERIFIED in options to generate SLSA build provenance, instead of using only a separate docker push step.
A typical routine for automated builds (opens in a new tab) follows an ordered sequence:
/workspace directory.$COMMIT_SHA.pipeline.json, are uploaded to Artifact Registry.A failed test command fails its build step and fails the whole build, and the associated commit shows a Failure commit status. To investigate, the repository page provides a Details link next to the failed status that opens the Cloud Build execution log with troubleshooting guidance.
Continue with this study guide: Executing automated integration tests in Cloud Build
is_leap_year, and manually adding edge cases and error handling.steps list in cloudbuild.yaml or cloudbuild.json in order, with each step running as an isolated Docker container attached to a local Docker network named cloudbuild on a fresh virtual machine worker that is destroyed afterward.availableSecrets mapping with versionName and env, and the step must declare secretEnv, use the bash entrypoint, and start args with -c.Unit tests validate expected behavior with predetermined inputs and outputs, while fuzzing validates unexpected behavior with random inputs. Fuzzing bombards an application with random inputs to expose hidden flaws, security vulnerabilities, or crashes, so use both when an AI draft only covers the happy path.
Use lightweight mocks or stubs when the goal is pure unit isolation and speed, and use a Google Cloud SDK emulator when the goal is behavioral fidelity. Emulators are local processes that simulate service behavior without crossing a production trust boundary, which is useful for verifying Firestore queries or Pub/Sub message handling.
Secret Manager secrets reach a build step through an availableSecrets mapping after all build steps that connects a secret version to an environment variable with versionName and env. The step must declare secretEnv for that variable, use the bash entrypoint, and start args with -c. Access also requires granting the build service account the Secret Manager Secret Accessor role on the specific secret.
Last updated