Getting started with CircleCI isn’t so hard, but understanding how it all works and what we can do with this tool is a little more complicated.
1. Version 2.1
Using version 2.1 will make your life easier. It introduces a lot of features like reusable commands or orbs which are ready to use solutions. You don’t have to write everything on your own. Additionally, syntax has changed a little and it is clearer/easier to understand!
2. Plan your workflow
Proper plan for the work is receipt for success in nearly every case. CircleCI supports CI/CD which means that it can do all the stuff automatically. Good pattern is to list everything you need to do in the build process and then rewrite it to a config file.
- Checkout code
- Check code with linter
- Create database
- Run tests
- Upload test coverage
- Deploy to staging
- Deploy to production
- Health check
3. Separate your jobs
Basic CircleCI setup can look like this:
version: 2.1 jobs: build: docker: - image: circleci/ruby:2.4.1 steps: - checkout - run: echo "A first hello" - run: name: Run Tests command: bundle exec rspec
You can separate this one job to three jobs based on their responsibilities. The first job will checkout the code, the second one will run the echo, and the third one will run the tests. The main profit of this solution is that without digging into jobs you will be able to determine what failed during the build process.
version: 2.1 jobs: checkout_code: docker: - image: circleci/ruby:2.6 steps: - checkout - persist_to_workspace root: ~/project paths: . test_echo: docker: - image: circleci/ruby:2.6 steps: - run: echo "A first hello" run_tests: docker: - image: circleci/ruby:2.6 steps: - run: name: Run Tests command: bundle exec rspec
4. Parallelism and Resource_class
run_unit_tests: executor: rails_machine resource_class: small parallelism: 25 run_integration_tests: executor: rails_machine resource_class: medium parallelism: 5
CircleCi gives a possibility to change number and types of containers. For each task, you can determine amount of resources which will be used to finish the job. Parallelism and resource_class become really useful while running tests. Increased number of containers mixed with split tests strategies can reduce the time needed for tests to finish. Remember: different kinds of jobs requires different amounts of resources. Before adding this feature, you need to consider which job will need better machine and which will need only additional container(s).
5. Split by timings
bundle exec rspec $(circleci tests glob "spec/features/**/*_spec.rb" | circleci tests split --split-by=timings)
CircleCi has a great feature which can split your tests and run them in parallel. First of all, you have to start storing your test results, then the special algorithm will queue and run your tests based on their execution times. Using parallelism and having more than one container will save us some time.
We have 2 containers and 3 tests which will run given amount of time
- a 1 minute
- b 4 minutes
- c 6 minutes
Algorithm should queue a, b to run on the first container, and c on the second one, so the job will finish in 6 minutes instead of 11.
6. Split your tests by type
run_tests: executor: rails_machine resource_class: medium parallelism: 20
run_unit_tests: executor: rails_machine resource_class: small parallelism: 10 run_feature_tests: executor: rails_machine resource_class: medium parallelism: 10
Running all tests in one job is not a good idea. The reason is simple: feature tests need more resources than unit tests. Splitting tests by type gives you a chance to decrease the time of tests execution and allows you to better manage your resources. You can divide your tests into 2 jobs for example. The first one will run unit tests and the second one will run feature tests. Additionally instead of running all the specs with 20 parallelism and medium resource class, you can use medium resource class on features and small on units. It will save us some resources (money in this example) and in some cases will be even faster.
7. Run jobs simultaneously
It is a good practice to run as many jobs simultaneously as possible. If we have 3 kinds of tests, we can run them async, so we don’t have to execute them one by one and wait 12 minutes (we have to wait till the longest job will finish). You can do the same when installing some dependencies if they do not rely on each other.
- restore_cache: keys: - gem-cache-v7-- - gem-cache-v7-master- - run: name: Bundle check command: bundle check || bundle install - save_cache: key: gem-cache-v7-- paths: - vendor/bundle - persist_to_workspace: root: ~/project paths: - vendor/bundle
Caching is a well known practise that helps to save some data and then restores it without the execution of all instructions needed to get this data. In CircleCI, it is a good practice to cache installed dependencies like Gemfile.lock or yarn.lock. Depending on the kind of cached dependencies, this practice can speed up job execution by 10 to 30 times!
9. Use tools
To benefit more from CircleCI, you should start to extend it by adding useful tools. Most of tools which can be added are written as orbs libraries. There are a lot of ready implementations like AWS Client, Rollbar or Slack. All you need to do is to add some env variables to your CircleCI app and write some lines of implementation of an orb which is easy and well documented.
All of these practises were formed by experimenting with the config file. Keep in mind that every project is different, so you don’t have to use all of the practices listed above. There are only a few examples that should show you how to benefit more from CircleCI. But still, don’t be afraid of trying various configurations, you can find new best practises while experimenting with your own projects!