Continuous Integration with Python and Circle CI

In this tutorial, we’ll review why Continuous Integration is indispensable to the development lifecycle of both solitary developers and teams and how we can immediately begin to reap its benefits.

Continuous Integration (CI) is a software development practice wherein developers regularly merge their code with a central repository after which automated builds and tests are run.

We’ll be using git for version control and Circle CI as our CI server. It’ll provide us with a reliable configurable environment for us to run our automated builds and tests.

Principles of Continuous Integration

Before we get our hands dirty with a demonstration, let’s briefly discuss the principles of Continuous Integration.

Maintain a single source code repository

Continuous Integration (CI) advocates the use of a version control system to track changes in code.

Everything needed for a fully functional application should be pushed to a single repository.

Build automation

It should be trivial to trigger a complete rendering of the project with a single command.
This should include tasks like the creation and migration of databases and configuring project environment variables.

Make the build self testing

The CI tool used should trigger a run of the project tests after the build is complete.
Of course, this necessitates that comprehensive tests be written for any work to be integrated.

Before the code that triggered the build is merged with the main branch, all tests must be seen to pass.

Make frequent commits to the main branch

Making atomic and frequent commits encourages integration and makes conflicts between work from different developers easier to manage and resolve.

After a section of work has been built and passes tests on the CI server, it should be merged to the main branch after review.

Every commit should trigger a build on the CI server

This assures that each commit made has not broken the build. It also becomes trivial to pinpoint a commit that caused errors in the build.

Broken builds should be fixed immediately

When it is discovered that a commit has caused a build on the CI server to fail, the commit should be analysed and the cause of the break resolved as soon as possible.

The CI build should be done in a replica of the production environment

Running the build in an environment with little or no deviation from the production environment assures that running the same application in the actual production environment will not be fraught with unwelcome surprises.

The output of CI builds should be visible

Build status should be made available to all relevant stakeholders.
Circle CI can be configured to send email and web notifications after a build has completed.

Advantages of continuous integration

The benefits of implementing Continuous Integration in your development cycle are several. Let’s note down a few here.

  1. Since build output is extremely visible, when a build fails, we can find and resolve bugs quickly.
  2. Continuous Integration helps to enforce testing in our applications, since we rely on these tests to assess the success of our builds.
  3. Because the build is run in a production like environment, CI reduces the time it takes to validate the quality of software to be deployed.
  4. Since atomic commits are encouraged, CI allows us to reduce integration problems with existing code, enabling us to deliver better software faster.

CI for solo developers and teams

Now that I’ve bended your ear with praises for Continuous Integration, let’s sober up a little.

It’s evident how the benefits we discussed above would be extremely valuable for teams. Now, let’s focus on how CI can enrich development for solo developers and whether the benefits outweigh the costs.

All the advantages we mentioned above still apply to solo developers but if you’re solo, you should think about the following before implementing CI.

  1. CI takes time: Depending on the complexity of your application’s environment, it may require a lot of manpower to set up your CI environment in a way that mirrors your actual production environment. If your production environment changes, you’ll need to reconfigure your CI environment too.
  2. Tests take time: Again, depending on the complexity of your application and the thoroughness of the tests running in the CI environment, your tests may take a while to run and confirm the health of the build. Since the build and tests should ideally run on every commit made, this could be an expensive operation. You may also need to spend valuable time optimising how your tests run. If you want to move fast and break things, this may be a little frustrating.
  3. CI is a way of life: Continous Integration begins and ends with the developer. It’s a commitment not to be taken lightly. When you don’t have a team that reminds you of the value of the process, it can be a lonely and tiring path to walk.
  4. CI can be a red herring: Developers implementing CI have to ensure that they are not lulled into a false sense of security by passing builds. This is even more important when you’re working alone and without the benefit of other processes that could alert you to unseen problems. The build is only as good as the tests that run in it.

Account Setup

Now that we’ve got a handle on what Continuous Integration is, let’s see what it entails. We’ll need accounts with the following services, so go ahead and register.

  1. Github: We’ll use Github to host our git repository.
  2. Circle CI: Registering on Circle CI with your Github account will make things easier in future.
    This has the advantage of adding CircleCI integration to your Github account.
  3. Coveralls: Samsies here. Sign up with Coveralls using your Github account.

Global Dependencies

Before we start, you’ll need to install a few applications globally.

  1. git
  2. Python: I’ll be using v3.5.2
  3. virtualenv: A recommended installation to isolate our application environment.

Project structure

The following will be the scaffolding for our project, so go ahead and create these directories.

+-- python-ci      +-- src      |   +-- math.py      +-- test      |   +-- math-test.py      +-- requirements.txt

Next, copy and paste the following into the requirements.txt file at the root of the project.

appdirs==1.4.0  astroid==1.4.9  click==6.7  coverage==4.3.4  coveralls==1.1  docopt==0.6.2  isort==4.2.5  lazy-object-proxy==1.2.2  mccabe==0.6.1  nose==1.3.7  packaging==16.8  pyparsing==2.1.10  requests==2.13.0  six==1.10.0  wrapt==1.10.8

Finally, create and activate the virtual environment for this project then run

pip install -r requirements.txt

This will install all the required dependencies for our project in your current virtual environment.

Let’s create a simple class with a function that returns the sum of two numbers.
Add this into your src/math.py file.

class Math():      def addition(value1, value2):          if not isinstance(value1, int) and not isinstance(value2, int):              return 'Invalid input'          else:              return value1 + value2

Next, let’s write a test to make sure our function is working as expected.

import unittest  from src.math import Math  class MathTest(unittest.TestCase):      def test_addition(self):          # Make test fail          self.assertEqual(Math.addition(3, 4), 8)

You’ll notice that we’ve made an incorrect assertion in the test. Let’s let it slide for now.

Now, run the following in your shell to make sure the test fails.

nosetests tests/math-test.py

Expect similar results

F  ======================================================================  FAIL: test_addition (math-test.MathTest)  ----------------------------------------------------------------------  Traceback (most recent call last):    File "/Users/emabishi/Desktop/Projects/Personal/python-ci/tests/math-test.py", line 8, in test_addition      self.assertEqual(Math.addition(3, 4), 8)  AssertionError: 7 != 8  ----------------------------------------------------------------------  Ran 1 test in 0.001s  FAILED (failures=1)

Github

Let’s create a repository on Github that will hold our application.

Once you’ve signed up, create one. I’ll be calling mine python-ci.

Create New Github Repository

Next, it’s time to initialise our local project directory as a git repository and add a reference to our github remote.
At the root of your project, run the following commands:

git init && git remote add origin <enter-your-github-repository-url-here>

A second option would be to clone the Github respository to our local machine. We can do this with a single command.

git clone <enter-your-github-repository-url-here>

Let’s create a new branch called develop and check out to it with the command

git checkout -b develop

After this, we can add and commit our previous changes using

git add . && git commit -m "<enter-short-commit-message-here>"

Whew, we’re done. I’d pat you on the back if I could. I promise you that the hardest part’s over.

Circle CI

Circle CI is a service that helps us employ Continuous Integration by letting us build our application and run tests in a configurable environment.

It’s also pretty handy because it can send us email and web notifications of the status of our current build. You can also integrate it with messaging services like Slack for real time notifications.

We just created an account with Circle CI, so let’s take full advantage of it. Log in to your Circle CI account using Github authentication.

Once logged in, you’ll be directed to your dashboard. Click on the fourth icon on the task bar on the left side of the screen. This is the projects tab and lists all your current Github repositories.

You’ll see a page like the one below.

Circle CI projects page

Click on the build project button next to the name of the repository you created before. Sit back, relax and watch what happens.

Circle CI runs our build but eventually errors out. Helpfully, it provides us with an error message.

No circle.yml

No tests

To configure settings for the build on Circle CI, we’ll need to create a configuration file in the form of a file with a .yml extension.
Let’s create a circle.yml file at the root of our project.

touch circle.yml

Fill it with the following:

machine:    python:      version: 3.5.2  dependencies:    override:      - pip install -r requirements.txt  test:    override:      - nosetests tests/math-test.py

The machine section configures the virtual machine we’re using. Here we’re explicitly defining that the machine should run python version 3.5.2.

We use the dependencies section to install our application prerequisites and the test section to specify the command which will trigger our tests.

As you can see, it’s exactly the command we used to run our tests locally.

As expected, with much pomp and circumstance, our tests have failed and failed loudly.
We’ll remedy this soon enough. Leave them in their imperfect state for now.

Failed Tests Circle CI

Coverage reporting

To ascertain the extent to which the tests we’ve written cover our code, we’ll use Coverage.py.
We’ve already installed it so there’s little we have to do now.

At the root of our project, run

coverage run src/*.py && coverage report

Expect similar results

Name                 Stmts   Miss  Cover  ----------------------------------------  tests/math-test.py       5      3    40%

You can tweak Coverage.py reporting in all sorts of interesting ways. If you’re interested, have a look here. For our purposes, the default reporting will do for now.

Coveralls

The next service we’re going to take advantage of is Coveralls. Coveralls displays our test coverage for all to see, thus making efforts at Continuous Integration loud and visible.

Once you sign in, you’ll be directed to your dashboard.

Coveralls Dashboard

Click on the second icon on the task bar on the left side of the screen. This is the add repo tab and lists all your Github repositories that are currently synced to Coveralls.

To refresh the list of repositories under your account, click on the Options menu on the right of the page linked to your Github account

Options Circle CI Refresh repositories

Next, search of the name of the repository we recently created. In this case, I’m looking for python-ci. Your repository name may be different.
Click on the switch to turn it to its ON position.

Coveralls Switch ON repository

Click the details button next to the name of the repository. You’ll see something like this:

Coveralls set up

Take note of the repo token.

To register our CI build with the Coveralls service, we have to perform some configuration. Let’s do this by setting our Coveralls repository token as an environment variable on Circle CI.

Screenshot of gear icon on Circle CI build page

Click on the gear button on the top right of the Circle CI build page. Follow along by clicking the Environment Variables tab on the left of the page. Finally, click the Add variable button and add the token like this.

coveralls repo token page on Circle CI

Now, let’s edit our circle.yml file to send coverage data to the Coveralls service after our tests run. Edit the circle.yml to look like this.

machine:    python:      version: 3.5.2  dependencies:    override:      - pip install -r requirements.txt  test:    override:      - nosetests tests/math-test.py    post:      - coverage run src/*.py      - coverage report      - coveralls

Add and push the changes to your remote Github repository. The push will trigger another build on Circle CI.

If we go back to our Coveralls dashboard, we’ll notice our dashboard now displays our test coverage percentage. We can even tell at a glance how our coverage has changed over time.

Sometimes it takes a while for the data to reflect, so give it a few minutes.

Coveralls graph of python ci repo

Github Status Checks

Going even further, in the spirit of Continuous Integration, we can prevent pushes or merges to the main branch of a repository until all required checks pass. In our case, these checks will be our tests.

We’ll implement this using a helpful tool by Github called Status Checks.

Let’s navigate to the settings tab of the repository we created on Github. Under the settings tab, select the Branches menu.

On the protected branches menu, choose to protect the master branch.

Protect master

From there, make the following selections and save your changes. You’ll be prompted to enter your password to authorise the changes.

Github save changes on branch status checks

Github branch protection options saved

To see the power of Github’s Status Checks in action, let’s make a Pull Request comparing the master branch of our repository with the develop branch.

As can be seen below, our tests failed on Circle CI. Therefore, because of the checks we put in place, merging to master is blocked.

Checks failing

Let’s fix our tests, make a push to Github and watch our Pull Request for any changes.

Wohoo! Green all the way. We can now merge our work to the master branch with assurances that all our Continuous Integration checks have passed.

All checks passing

Conclusion

We’ve demonstrated that employing a Continuous Integration strategy in software development leads to major benefits in the quality of our work and the rapidity at which we resolve issues and conflicts within our application code.

Even more of interest is that when paired with Continuous Deployment strategies, CI becomes an even more powerful and capable tool. We’ve not gone into leveraging Continuous Deployment in our workflow, but I trust that you’ll look into the possibilities that its use opens up.

However, there’s a lot more we can do with CI which we’ve not gone into here. If you’re interested, I’m leaving some links to a few resources which I believe will prove extremely helpful.

Resources

If you’d like to read more about Continous Integration, here are a few places you can start with.

  1. AWS
  2. Martin Fowler

A few other 3rd party CI tools are:

  1. Jenkins
  2. TeamCity
  3. Bamboo by Atlassian
  4. Hudson
  5. goCD

This list is by no means exhaustive.

I’d love some feedback on this guide. If you have a moment, drop me a comment in the box below. Also, if you have any questions, don’t be shy, let me know.


You may also like...