CircleCI is one of the most popular continuous integration software. It is basically a script runner that can be triggered by your github/gitlab push.
If you haven’t know about it, and you are still doing manual deploy to server, check it out now! There is a free plan! (for 6000 build minutes per month).
Once you setup the circleCI with your Github repo, whenever you push a change to the repo, circleCI will run a script that you specified to test your repo, and even deploy to cloud server.
Standard CicleCI setup:
A standard setup of a web app (dev/staging/prod) would generally include:
- A
test
job that runs tests, adeploy-staging
job that deploy repo to a staging environment, adeploy-master
job that deploy to production environment. - run
test
job whenever there is a push to any branch, runtest
anddeploy
jobs whenever there are commit (most likely a merge-request) tostaging
ormaster
branch.
3. A more complex web app, such as a mono repo, should have multiple test jobs, like test-frontend
, test-backend
.
Optimize the setup
Obviously, we want want the circleCI to be as efficient as possible, this is not to say I am cheap (I am, but that’s not the point), this is also about development productivity.
- Shorter turnoever. Developers will know sooner that their changes failed test.
- Faster deployment. Any
merge-request
(push feature to master branch), is deployed faster. - Most importantly, budget. As CircleCI, (any many other services) charge by minutes, obviously less time is cheaper.
There are multiple directions to approach this “do not run unnecessary tests”.
1. Cancel previous run if a the branch is pushed a new commit.
Luckily, CircleCI’s platform already does this. If previous job
is still running when you commit a new change to the branch, the previous job is automatically canceled and a new job with the most recent commit will start.
2. Cache test result.
Every code commit should trigger a test run, no problem, but over the years, we have discovered, there are many non-code changes, such as .gitignore
, package.json
change package name, config changes that does not affect how code runs. If you put frontend
and backend
code together, you would want frontend
changes to not run backend
tests and vice versa. Therefore, it is a pretty important thing to add that significantly reduced our circleCI usage. (over 60%).
The idea is simple, in the circle config, we want to run tests only if a specific folder’s files are changed. CircleCI support a cache mechanism that allows you to store a folder and allow it to be loaded across all the jobs.
In the example code above, first, I created acode_md5.txt
file by taking md5 of all files in src
directory. Then, I use the restore_cache
+ save_cache
syntax in circleCI that restores the test result in jestCov/
directory. It cache key is a long text with the checksum of the code_md5.txt
file. In other words, if all the code files in src
directory are unchanged, then code_md5.txt
should be unchanged, the cache should be correctly found and lcov
file in jestCov/
should be restored. In any case, the test run command is combined with a bash if so that only if the lcov
file does not exist, will it run the test.
This combination of “build md5 of all code”, “ restore test result”, “run test only if test result is not found” allows the circleCI to skip a test if all the dependent files are not changed.
This same idea could also be applied to different kind of tests. For example, you can split up unit tests by modules, so if code in one module is modified, all other modules tests are not run.
3. Cache package installation.
To run tests in circleCI environment, we must include many steps to prepare for the tests. Steps like yarn install
, pip install
usually takes quite a long time. So obviously we would like to cache it if possible. One approach is to cache the cache. Little twisted right ? It means we want to use the CircleCI’s cache to store the yarn cache directory. Then load it before we run yarn install
, this way, we don’t have to download all the packages, instead directly copy from yarn cache.
Summary
This post showed a way to config CircleCI to skip tests if the code is unchanged. In most cases, a feature update only modifies a couple of files, so you may save a lot of time by skipping unit tests in other modules. (Still should run integration test though).
Now if you keep tabs on new technology, you might know about this monorepo framework NX.js, which build dependency graphs of your code so that it is smart enough to know the exact tests that are affected by your changes. However, the nx.js, in my own opinion, has a lot of compatibility issues with all the existing frameworks out there, it actually create a lot of problems. So I decided to manually split up tests.
That’s it for now, I will come back soon with more tricks with CircleCI .