Introduction
- This course will teach you how to write effective tests and ensure the quality and reliability of your research software
- No prior testing experience is required
- You can catch up on practicals by copying the corresponding folder
from the
files
directory of this course’s materials
Why Test My Code?
- Automated testing helps to catch hard to spot errors in code & find the root cause of complex issues.
- Tests reduce the time spent manually verifying (and re-verifying!) that code works.
- Tests help to ensure that code works as expected when changes are made.
- Tests are especially useful when working in a team, as they help to ensure that everyone can trust the code.
Simple Tests
- The
assert
keyword is used to check if a statement is true and is a shorthand for writingif
statements in tests. - Pytest is invoked by running the command
pytest ./
in the terminal. -
pytest
will run all the tests in the current directory, found by looking for files that start withtest_
. - The output of a test is displayed in the terminal, with green text indicating a successful test and red text indicating a failed test.
- It’s best practice to write tests in a separate file from the code
they are testing. Eg:
scripts.py
andtest_scripts.py
.
Interacting with Tests
- You can run multiple tests at once by running
pytest
in the terminal. - Pytest searches for tests in files that start or end with ‘test’ in the current directory and subdirectories.
- The output of pytest tells you which tests have passed and which have failed and precisely why they failed.
- Flags such as
-v
,-q
,-k
, and-x
can be used to get more detailed output, less detailed output, run specific tests, and stop running tests after the first failure, respectively.
Unit tests & Testing Practices
- Complex functions can be broken down into smaller, testable units.
- Testing each unit separately is called unit testing.
- The AAA pattern is a good way to structure your tests.
- Test driven development can help you to write clean, maintainable code.
- Randomness in tests can be made deterministic using random seeds.
- Adding tests to an existing project can be done incrementally, starting with regression tests.
Testing for Exceptions
- Use
pytest.raises
to check that a function raises an exception.
Testing Data Structures
- You can test equality of lists and dictionaries using the
==
operator. - Numpy arrays cannot be compared using the
==
operator. Instead, usenumpy.testing.assert_array_equal
andnumpy.testing.assert_allclose
. - Data structures that contain numpy arrays should be compared using
numpy.testing.assert_equal
. - Pandas DataFrames and Series should be compared using
pandas.testing.assert_frame_equal
andpandas.testing.assert_series_equal
.
Fixtures
- Fixtures are useful way to store data, objects and automations to re-use them in many different tests.
- Fixtures are defined using the
@pytest.fixture
decorator. - Tests can use fixtures by passing them as arguments.
- Fixtures can be placed in a separate file or in the same file as the tests.
Parametrization
- Parametrization is a way to run the same test with different parameters in a concise and more readable way, especially when there is a lot of repetition in the setup for each of the different test cases.
- Use the
@pytest.mark.parametrize
decorator to define a parametrized test.
Regression Testing and Plots
- Regression testing ensures that the output of a function remains consistent between changes and are a great first step in adding tests to an existing project.
-
pytest-regtest
provides a simple way to do regression testing. -
pytest-mpl
provides a simple way to test plots by comparing the output of a test function to a reference image.
Continuous Integration with GitHub Actions
- Continuous Integration (CI) is the practice of automating the merging of code changes into a project.
- GitHub Actions is a feature of GitHub that allows you to automate the testing of your code.
- GitHub Actions are defined in
yaml
files and are stored in the.github/workflows
directory in your repository. - You can use GitHub Actions to only allow code to be merged into the main branch if the tests pass.