Conclusions and recommendations
Objectives
What is a realistic approach to automated test?
How can I start?
Discussion: What’s easy and hard to test?
Discussion: Testing in practice
Use the collaborative notes to answer these questions:
Give examples of things (from your work) that are easy to test.
Give examples of things (from your work) that are hard to test.
Considering automated tests when writing code is a major mental shift, that you will hopefully embrace after attending this lesson.
The basics: what knowledge do you need?
Learn one testing framework well enough for basics:
Explore and use the good tools that exist out there.
An incomplete list of testing frameworks can be found here.
Why use a testing framework?
Automated testing typically involves a number of repetitive tasks and tricky problem solving.
Fortunately for us, someone has already found a solution for most of these and created testing frameworks that we can use.
Note: not all frameworks solve all problems (also because sometimes the underlying language does not have the necessary features).
Typical problem |
Solution |
Examples |
Report failures/successes |
Automated collection and output, |
Fundamental feature that |
Remember to run |
Automatic discovery, |
|
Run only some tests |
Test filtering |
|
Provide useful information |
“Smart” assertions/macros |
|
Run same test for many |
Parametric tests |
|
Check that a property holds |
Automatically generate test cases |
hypothesis |
Debugging on failure |
Start debugger on test failure |
|
Floating point equalities |
Macros/classes |
|
Set up and tear down |
||
Estimate how much of your code |
Automatic coverage |
Pytest-cov, |
Do code examples |
Documentation tests |
|
Will my code work |
Test in different environments |
Don’t over-test
Not every code needs perfect test coverage.
A simple script or notebook probably does not need an automated test.
Pick the low-hanging fruits first
You probably won’t do everything perfectly when you start off… But what are some of the easy starting points?
If you have got nothing yet:
Start with an end-to-end test. Typically easy to add, from a “manual” use case. This should match (or serve as) an example in the code documentation anyway.
Describe in words how you check whether the code still works.
Translate the words into a script.
Run the script as often as reasonable.
Do you have some single functions that are easy to test, but hard to verify just by looking at them? Add unit tests.
A local testing framework + GitHub actions/Gitlab CI-CD is very easy! And works well in the background - you do whatever you want and get an email if you break things. It’s actually pretty freeing.
If you need to start modifying some existing code:
Add a characterization test for the part of the code you need to change.
Add tests for any functionality you intend to add.
Consider adding some end-to-end tests for the use case you have in mind.
Going more in-depth
With time:
The code gets larger, the chance of undetected bugs increases, tests should increase.
Bugs will be found. When you find them, add tests against those.
How to improve your code:
Use code coverage analysis to identify untested or unused code. Remember Goodhart’s Law.
Strike a healthy balance between different kinds of tests:
Fast tests give you information quicker but can be shallow;
Thorough tests can catch more bugs but take longer to run and can be brittle.
The test pyramid is a recommended strategy to balance between test types.
If you make your code easier to test, it becomes more modular (and vice versa - see the modular code development lesson).
Learning how to test well make the rest of your code better, too.