Code Coverage
Testing code coverage
In this session we will be using GitHub actions along with a service called Codecov.
Code coverage is a measure used in software testing.
It describes the degree to which the source code of a program is tested by a particular test suite.
It's a way to check that all paths through the code, meaning every function, method and line, are exercised by the tests.
There are various types of code coverage, including:
- Function coverage: Has each function (or subroutine) in the program been called?
- Statement coverage: Has each line of the source code been executed?
- Branch coverage: Has each branch (also called DD-path) of each control structure (such as in if and case statements) been executed?
- Condition coverage: Has each boolean sub-expression evaluated both to true and false?
Statement coverage is probably the most common of these, and that will be our focus right now.
There are many benefits of testing code coverage:
- Identify untested parts of code: Code coverage allows developers to understand which parts of the code are not covered by tests. It brings attention to areas where bugs may be lurking unnoticed.
- Improve code quality: A high percentage of code coverage can indicate that the software has a lower chance of containing undetected software bugs.
- Prevent regressions: Coverage can help you prevent regressions, which are changes that break existing functionality, by making sure that all parts of the code are tested.
- Confidence during Refactoring: It allows developers to refactor code or add new features with the confidence that they haven't accidentally broken existing functionality.
While code coverage is a valuable tool in software testing, it's not a silver bullet.
100% code coverage doesn't guarantee that your software is 100% bug-free or that it handles all possible use cases.
It's also possible for code to have high coverage but low test quality: it's still essential to write good quality tests.
We will be using a service called Codecov to automate the process of collecting and analyzing code coverage metrics.
Codecov is free for public repositories.
Set up codecov
Go to https://codecov.io/, and log in with your GitHub account.
You may need to follow the steps required to grant Codecov access to see your repositories.
Once you have done that, you should see your repository on the Codecov webpage.
Next to your repository, you should see a 'Configure' button. Click this to set up code coverage:
- Make sure 'Using GitHub Actions' is selected.
- Select 'Pytest' from the menu of language options.
- Select 'Repository token' to generate a secret just for this repository.
You will see a token of the form
CODECOV_TOKEN=XXXXXXXXXX.
This token will allow your GitHub Actions workflow to communicate with Codecov.
It is not strictly needed if your repository is public, but let's run through the process of setting it up anyway.- Go to your repository on GitHub
- Go to Settings
- On the left, go to Secrets and variables then to Actions
- Click New repository secret
- Set the name to
CODECOV_TOKENand the value to the token string, copied from Codecov - Click Add secret
This will allow you to use the token in a GitHub Actions workflow, without anyone who looks at your workflow seeing what the token is.
We will see shortly how this is used.
Your repository is now set up and ready to use with Codecov.
Creating coverage data
We will use the pytest-cov tool to generate coverage information when running our unit tests with pytest.
To install this locally, using pip, run:
pip install pytest-cov
We can now run pytest in the following way to generate an
xml file containing coverage information for the whole project:pytest --cov=ci_course --cov-report=xml
Let's break that down:
--cov=ci_course: measure coverage of theci_coursepackage.--cov-report=xml: write the report as XML, which is the format Codecov expects. Leave this off and you get a summary in the terminal instead, which is more useful while you are working.
The rest of the configuration lives under
[tool.coverage.run] in pyproject.toml, alongside the settings for ruff and pytest.
Keeping tool configuration in one file is worth doing: a project that scatters it across individual files such as .coveragerc, .flake8, setup.cfg and tox.ini can get really messy!Try it without
--cov-report=xml as well, and read the table it prints.
The Missing column is the interesting one.Run coverage locally
With your project installed in a virtual environment, run the command above and check that it produces a
coverage.xml file.Then run it again without
--cov-report=xml and read the table.Use GitHub Actions to automate this process
We want to generate coverage, and upload the results to Codecov, every time someone commits or opens a pull request.
Run coverage on GitHub
Write a GitHub Actions workflow,
coverage.yml, that generates a coverage report and uploads it to Codecov.
You will find the following step helpful:- name: Upload coverage reports to Codecov uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true files: coverage.xml
You can read more about the
codecov-action step here:
https://github.com/codecov/codecov-actionOnce you have committed this workflow file, go to GitHub and check that it has run successfully.
If it has, go to Codecov and explore the coverage report.
It may take several minutes to finish processing.
Next steps
Improve code coverage
Look at the lines Codecov reports as uncovered in
src/ci_course/stats.py.Write a test that covers the first of them, then look hard at what the test tells you.
Cover the rest
There are still uncovered lines in
src/ci_course/io.py.
Work out what they are and whether they are worth a test.Add branch coverage
Statement coverage asks whether each line ran.
Branch coverage asks whether each decision went both ways: an
if with no else can have every line executed while the false case has never once been taken.Turn branch coverage on and see how your numbers change.
You can read about the configuration here:
https://pytest-cov.readthedocs.io/en/latest/config.html
- [optional] Read more about encrypted secrets in GitHub Actions.
- [optional] Read more about environment variables in GitHub Actions.