5 CI/CD Best Practices for Better Code Quality
December 7, 2021

5 CICD Best Practices for Better Code Quality


This blog post will cover the top CI/CD best practices, regardless of which CI/CD tool you choose.

You can apply this knowledge to any tool you want to ensure that you can improve your code quality as soon as possible. Let’s get started.

What Are Top CICD Best Practices that Are Tool-Agnostic?

  1. Decide What to Test and When
  2. Adapt Your CI/CD Pipeline to Your Development Process
  3. Make the Build as Fast as Possible
  4. Build in a Containerized Environment
  5. Install the Infrastructure Automatically and as Code

1. Decide What to Test and When

My first CI/CD best practice for you is to decide which part of your code you are testing, and when you should be testing it.

Testing is important to be sure that an already existing functionality was not broken by code and will not break from future code changes. But manual testing isn’t enough. Test automation is required to make the testing process faster/cheaper and to be able integrate it with the CI/CD pipeline. This makes the development process faster/cheaper and helps support code in high quality.

For automation, you can use tools like open source Taurus, which automates the performance testing process.

What to Test

All tests can be split into two types — lightweight tests or heavyweight tests.

Lightweight tests

Like unit tests or integration tests with mocks. Can be executed on CI/CD on each commit to a feature-branch. They will identify code that broke the build earlier, for faster localization and fixes.

Feature-branches are also a good place for automatic static code analyzing (you can use tools like SonarQube). The build will be green if the tests passed successfully and if SonarQuibe passed the quality gate. This means the code can be reviewed and if it is ok, it can be merged to develop.

Heavyweight tests

Like integration tests, these tests may require deployment or testing with real systems without mocks and against the develop branch. Please note that these tests will take more time and will be triggered less often, only on each merge of the feature-branch to the development branch. If development is red on the CI/CD pipeline it should be fixed ASAP because the development branch should be as stable as possible.

Your performance, functional UI, and API tests can be performed automatically in BlazeMeter. BlazeMeter lets you easily run open source load testing tools like JMeter or Gatling, while scaling them to thousands or millions of users and then running them from all over the world. You can also integrate BlazeMeter with CI tools like Jenkins and TeamCity, and get insightful reports that will show you what passed and failed, response time, and more.

When to Test

If a Sprint takes 2 weeks, development has to be merged to the staging branch 3 days before the end of sprint, as 3 days should be enough to test and fix all critical bugs and prepare the environment for demo.

The code from staging can be tested manually and if it has no critical bugs it can be merged to the release branch. As soon as the code is merged to release it can be automatically deployed to the production environment.

Feature-branches must be updated with development before merging them to develop. Otherwise, you might encounter a situation where the feature-branch is green and develop is green, but after the merge the feature-branch in develop breaks.

So, the only way to make sure develop will maintain the same quality after the merge is to merge develop to the feature-branch and test it. This test can be automated in the CI/CD. In the tests, the merge should be built and pushed to the repository only if the test is green.

2. Adapt Your CI/CD Pipeline to Your Development Process

The CI/CD pipeline is the automatization of your development process, so it needs to fit your development process strategy. The first step is the formalization of the development process, to ensure you know which branches you have and what they do. Then, you can create the correct pipeline.

Here are a few examples:

  • If you have multi-branches with one feature per branch and a pull request review process, then your CI/CD pipeline shouldn’t enable merging pull requests if the build for the branch with the feature failed. But, if you have only one branch (in case of Feature Toggle for example) or if instead of the review process you use pair-programming, then the pipeline should not care about pull requests.
  • If you need to be able to deliver each feature separately as soon as it has been implemented, then you need to merge the feature to release the branch. In this case, the CI/CD pipeline should enable merging to release, only if all integration automated tests was passed for the branch with the feature. But, if you don’t want to waste time on a full integration for each feature then CI/CD may allow to merge features to some intermediate branch (a staging branch for example) and then CI/CD should allow to merge the staging to release if full integration automated tests was passed on staging branch.
  • If you need to take care of code quality, the CI/CD pipeline can use Checkstyle or any tool for static code analysing, or CI/CD may require an integration with SonarQube to collect information about the code quality status that also shows code coverage report by tests.

3. Make the Build as Fast as Possible

A shorter code-change-result cycle makes the code easier to fix and update, as changes are still fresh in developers’ minds. You can achieve a quicker build by triggering it as soon as code is pushed to the repository (webhooks are the best way to do this). Another way to shorten the process is to split the build into parts, and run them in Parallel. For example, tests can be split into chunks and run in parallel.

If your CI/CD tool supports horizontal scaling (for example Jenkins CI does) you can add more machines. If you see you have a build queue that is waiting for a free CI/CD machine it is good time to add more machines.

Finally, vertical scaling is also helpful. If an application build requires intensive work with HDD, you can use SSD on CI/CD machines or build on memory-based partitions.

4. Build in a Containerized Environment

If you are developing an application and want to create a CI/CD infrastructure, you probably need to install additional software on CI/CD machines (e.g. Git for checkout code, Maven/Ant/Gradle to build the code, Ansible to deploy it and so on).

Note that it is required to use a particular version for each software component. If you install any version it most probably will not work.

If you have been developing your application for a long time it probably has different versions. If you are developing many different applications on the CI/CD infrastructure you probably have different sets of additional software and its versions. These can cause conflicts between software components.

So, you need some way of isolation between builds of different applications even though they go on the same machine.

The solution can be any container solution (LXC/LXD), but I prefer Docker. Don’t install any additional software that is required for building your application. Instead you can install additional applications inside the Docker container and run the build process inside the container. Then it will be much more easy to support the CI/CD infrastructure.

5. Install the Infrastructure Automatically and as Code

To prepare your CI/CD infrastructure you need to perform some actions:

  • Create virtual machines - because you may need more than one machine to install CI/CD to increase its performance, or you might want to install additional tools like SonarQube for code quality monitoring, a Selenium server for UI tests, machines for applications’ deployment (DEV, STAGING, PROD environments for example) or a monitoring tool (e.g. Graphana)
  • Install all the required software on it
  • Configure the software properly

If you are going to install your software manually this may take you a huge amount of time and it is error-prone (you may forget to install something or install the wrong version or misconfigure something).

To save your time (and customers’ money) I would recommend you use special software for automatic infrastructure creation (Puppet, Hashicorp's Terraform, and Cloud Formation for example). You can create scripts that will prepare the CI/CD infrastructure with the exact required configuration. Even if the CI/CD infrastructure crashed for some reason, you can easily recreate it in a few minutes.

It is a good idea to store these scripts in a code repository (Git or Helix Core). In this case, you have a full history of the infrastructure changes and you can prepare any historical version of the infrastructure if it is required.

Please pay attention to installing the particular and required versions of all software/components/plugins. Because if you don’t specify the exact version, it may install something different and the infrastructure may stop working. But if you use a script it is required to specify all versions there.

I hope these best practices help you. Let me know in the comments section what you do to improve your code quality. 


Related Resources:

This blog was originally published on May 19, 2018, and has since been updated for accuracy and relevance.


Related Resources