Style Guides for Testing in Python
In the last decade language communities (especially JavaScript) have adopted style guides to shape individual contributions and establish clear expectations for new work. I’ve often wondered why there isn’t the same sort of interest in establishing guides for testing. The Unit testing guidelines from the Pylons project are a good counterexample and there are individuals that have pursued this idea (like this guide from Thea Flowers), but they are exceptions.
Here’s my small contribution to the topic. This post was originally published on the Safari Flow Blog on . I’ve updated the links.
Python programmers are fortunate to have a clear, reasonable style guide in PEP8. While PEP8 is widely followed by professional and amateur Python programmers, there’s no widely adopted equivalent style guide for testing in Python.
To help our own team improve code reviews and train newcomers (both to the company and to Python), I’ve started a draft style guide outlining how we write tests for Python code at Safari. It is meant to be expanded and refined over time.
Here’s the outline of the draft style guide (the complete version is available at https://github.com/safarijv/python-testing-style-guide). What would you add or remove?
Prerequisites
- PEP8 first
- Lint in your editor
- Prefer imperfect tests to no tests
The practice of testing
- Use Tox
- Master should always pass 100% of the time
- Manually run tests often; automatically run tests always
- Use TDD if it makes sense
- Pay attention to coverage, but don’t obsess over metrics
- Remember the QA team: some things are still best tested by humans
- Delete dead or misleading tests aggressively
- Focus on tests during code reviews
- Reproduce before testing
- Always “Click the thing”: it’s not fixed until you’ve tried it like a real user, regardless of how many unit tests you wrote
- Write regression tests whenever possible
- Fail tests first
- Prefer fewer asserts per test
Structure
- Prefer small tests
- Separate tests into different files
- Follow import conventions
Write to be read
- Always write a docstring
- Use “should” in every docstring
- Prefer descriptive variables
Expectations
- Always bind an
expected
value - Assert the
expected
value, then the returned value - Prefer explicit expected values
Things that people will yell about
- Never leave behind
print
statements: use logging
Unusual testing conventions
- Repeat yourself
Django
- Avoid fixtures: they’re fast, but you’ll forget to update them when the data model changes
- Use CSS selectors to find specific nodes rather than just looking for ‘substring in undifferentiated HTML blob’
- Prefer extremely targeted functional tests
- Target testable HTML with CSS classes prefaced with
t-
, rather than using presentational classes that your front-end team might change. - Consider templatetags
Testing tools and techniques
- Use mocks with care
- Enjoy
assertRaises
How to avoid traps
- Really think about boundary values
- Date-related functionality should test oddball cases like leap years
- Test the obvious positive and negative cases separately
- Introduce some randomness: pick random numbers or strings within an expected range
- Introduce some Unicode: we like upside-down input text or users named ☃.
Resources
- A gentle introduction to the feel of testing (Django focus): Test-Driven Web Development with Python
- The classic description of TDD (Java examples, oh well): Test Driven Development: By Example
- Another classic focused on explaining how testing works in the real world (more Java examples, oh well): Test-Driven Development: A Practical Guide
- Recent take on testing lessons from a mature project: Testing Django Projects at Scale, a PyCon CA 2013 video
- Thoughts on mocking versus faking: Stop mocking, start testing, a writeup with links to a PyCon 2012 video
- A presentation to convince you to start testing: Getting Started Testing your Python