Basic CI/CD on Google Cloud Platform using Cloud Build
By: Abhi Chakraborty
Tags: Unit Testing, Google Cloud Platform, Continuous Integration, Continuous Delivery, Google Compute Engine

Note: To simultaneously demonstrate a non-containerized method of testing within the pipeline, the Jest/Enzyme tests for our different React components will be run through only the npm gcloud image.

Setting up automated tests on PRs to main

Most dev teams employ some form of a GitHub workflow within their Agile process. This means that for individual features, developers branch off the main/master production branch and add their feature/bug-fix. Developers would also be expected update any existing tests affected by their changes and also write additional tests for their new code (in a test-driven development (TDD) setting, they would write tests prior-to/simultaneously writing their new code!). Sometimes in more large complicated projects, branches are created off of branches and so on!

Eventually, every feature/bug-fix branch must be merged into its origin branch through opening up a pull-request (PR). This pull-request is then code-reviewed (hopefully throughly) by fellow developers. However, humans are prone to errors, especially as projects become large and features become complicated. Merging faulty code can lead to painstaking bugs and in the worst cases can even break your production code. Therefore a key verification tool would be through automatically running all the tests which all the team’s developers have been compiling since the initiation of the project and displaying the result of this on the PR itself.

With all that said, we get to our first build trigger. For our small project, our trigger will run a series of steps defined in a testbuild.yaml file whenever a PR is created to merge a feature branch into main. The only purpose of this trigger will be to run our flask API as well as React component tests. Before creating our trigger, let us define our testbuild.yaml, so we know the exact steps we intend for Cloud Build to follow.

Let’s note what’s happening in each of these steps:

First, we build our Dockerfile.dev for our backend flask API tests . We are using the gcloud docker cloud builder. Note how we must use ‘-f ./flask_app/Dockerfile.dev’ to specify to docker to build our Dockerfile.dev file and not the default Dockerfile. Of course, the build context is also ./flask_app.

. We are using the gcloud docker cloud builder. Note how we must use ‘-f ./flask_app/Dockerfile.dev’ to specify to docker to build our Dockerfile.dev file and not the default Dockerfile. Of course, the build context is also ./flask_app. We then push this newly built docker image to our project’s container image registry. Note here that you may need to provide CloudBuilder additional permissions to do this step. So go ahead into your IAM and update the access of your …@cloudbuild.gserviceaccount.com as you feel is appropriate level of access (please do not just assign the owner level to everything).

to our project’s container image registry. Note here that you may need to provide CloudBuilder additional permissions to do this step. So go ahead into your IAM and update the access of your …@cloudbuild.gserviceaccount.com as you feel is appropriate level of access (please do not just assign the owner level to everything). We now will run this newly pushed image . We do this through bash, and while running we specify all the necessary environmental variables needed by our container to run the test processes properly as we discussed above. This environmental variables will be defined within our Cloud Build trigger itself. Note that all the referred environmental variables begin with a ‘_’ since this is the format in which environmental variables will need to be defined in your build trigger.

. We do this through bash, and while running we specify all the necessary environmental variables needed by our container to run the test processes properly as we discussed above. This environmental variables will be defined within our Cloud Build trigger itself. Note that all the referred environmental variables begin with a ‘_’ since this is the format in which environmental variables will need to be defined in your build trigger. Next, we are onto setting up our React component tests! First, we will use the gcloud npm cloud builder to install our required npm modules. Note that the ‘ — prefix’ addition allows us to specify ‘./react_app’ as our context for finding the package.json file needed to install the modules. An additional note on this: generally, when you create your base React Project with create-react-app, your package.json will have “test”: “react-scripts test” as the default script for running npm test. You will want to change this to “test”: “react-scripts test — watchAll=false”. This is because the default command expects a user input after running a test, so in an automated system this will cause your tests to never stop running!

"test" : "react-scripts test" -> "test" : "react-scripts test --watchAll=false"

Finally, we will run our React tests. Once again, the ‘ — prefix’ addition allows us to specify ‘./react_app’ as our source code context. The ‘a’ addition enables us to run ALL the test-suites we have and finally the ‘ — — coverage’ prints our a useful mini stats chart for our tests

So let’s go ahead and create this trigger. If you have not done so already, click to the left hamburger icon to open up the menu on your GCP cloud console, scroll down and navigate to Cloud Build under Tools. There, enable the API. Remember, there are costs for running processes through Cloud Build. Go onto Triggers and create a trigger. You will have to link your GitHub repository to GitHub App, so go ahead and follow the steps to do so.

Under Build Configuration, choose CloudBuild configuration file and specify your file name and if that yaml file is not located in the default folder, specify yaml file path as well. Most importantly, specify your environmental variables! Create your trigger and when you are ready, open a PR and check if the build is triggered in the History tab! You should see something like this on your GitHub PR and in builds.

Note that these steps work in a manner where if one of the steps in the yaml fails, the build will indeed stop and none of the remaining steps will continue being executed. Below is an example where I purposely pushed some code to cause a single react test to fail:

If the tests all pass, you will see success like the following:

And of course you should also see a green check on the tests on your GitHub PR. With this green check, if you tests are hopefully comprehensive enough, you and your team will have a certain level of reassurance and code confidence in merging your code into the main branch. Of course, tests can never replace through code reviews!