| layout | post |
|---|---|
| title | Tests and testing |
| subtitle | Testing scientific codes - the basics |
| author | Peter Hill |
| tags | testing basics |
Along with version control, tests are essential to good practice for developing software. Having tests makes it easier to reason about changes, and ensure trust in the outputs of your code. And if you can't trust your code, you can't trust the papers written using the results...
- What are tests?
- Why are tests important?
- How to test your code
- Resources
- You might already test
- Run a "standard" case, eyeball the result, check a number
- This is a test!
- Integrated tests
- Test whole program at once (or large chunks)
- Most likely do something like this already
- “Known good” answer (“regression test”)
- Unit tests
- Test individual pieces (functions, subroutines, objects)
- Useful as part of test driven development (TDD)
- Code is wrong → wrong results → bad things happen
- Need to be confident of results
- Bugs are a fact of life
- How do you change code?
- Either "Edit and Pray" or "Cover and Modify"
- Proper tests will:
- Catch common bugs
- Catch edge cases
- Catch errors early
- Reduce time to solution
- Allow you to make changes confidently
If it doesn’t have tests, it’s wrong!
- Cover as much of the code as possible
- Each test should cover as little as possible (localisation)
- Should run fast! (“1/10 second is too slow”)
- Should be automated as much as possible
- Should run as often as possible
- Verification and validation
- "Have we done the thing right?" vs "Have we done the right thing?"
- Future topic!
- Testing “legacy” code different to starting fresh
- No “one size fits all”
- Lots of guides on internet written for e.g. web development, enterprise systems, not scientific computing
- Exact method can be language dependent
- Working Effectively with Legacy Code - Michael C. Feathers
- Test driven development for new codes
- Integrated tests good start
- Lots of frameworks available for every language (cppunit, funit, pytest)
- Continuous integration tools (Travis CI, Jenkins)
from numpy import isclose
from sys import exit
data = run_test_case()
answer = analyse_data(data)
real_answer = 0.24
if isclose(answer, real_answer):
print("Test passed!")
exit(0)
else:
print("ERROR! Bad result")
exit(1)[/columns]
- Add tests as you need to change things
- Find seams -- "places to alter code behaviour without editing in place"
- Find change point
- Find seam to insert test
- Break dependencies
- Write tests
- Make changes
- Redefine function with preprocessor macro
- Switch out function at link time
- Switch out object for testing ("fake") object
- Write tests first
- Then write code!
- Looks like more effort to begin with, but then always confident of results
- Write tests → Write simplest implementation → Debug/fix tests → Optimise
- Find bug → Write test → Fix bug → Run test
- Deal with setup and teardown between individual tests
- Run everything that e.g. looks like test_function in a file
- Run on every commit/pull request
- Integrations into popular VCS websites (Travis CI)
- Or run locally (Jenkins)
- No changes get into master/trunk without passing tests
- Run automatically, so no effort aside from initial setup
- Working Effectively with Legacy Code - MC Feathers (ISBN: 9780131177055)
- Software Engineering for Science - JC Carver, NP Chue Hong, GK Thiruvathukal (ISBN: 9781498743853)
- Dealing with Risk in Scientific Software Development - R Sanders, D Kelly (IEEE Software, 25(4), July 2008)
- Python test frameworks: Pytest, nose, unittest
- C test frameworks: Check, CUnit, Autounit
- C++ test frameworks: Boost, Catch, CppUnit
- Fortran test frameworks: FRUIT, pFUnit, Ftnunit
- Continuous integration: Travis CI, Jenkins, GitLab CI
- Cover as much of the code as possible
- Each test should cover as little as possible (localisation)
- Should run fast! (“1/10 second is too slow”)
- Should be automated as much as possible
- Should run as often as possible
- Write tests
- Automate them
