An important and subtle point
Imagine you're coding a module for working with prime numbers. It has functions like:
- # primes.py
- def isprime(value):
- 'True if value is Prime, else return False.'
- # ...
-
- def max_prime(values):
- 'Find the largest prime number in the sequence.'
- # ...
And you want to write a test for max_prime().
This is a perfect situation for something called "parametrized tests". The idea is that in your unit test, you have a table of input values, paired with expected correct outputs...
And rather than writing repetitive assertions over and over, you set it up so one assertion is run repeatedly, on each of those inputs.
There's more to it. But that's the basic idea.
So how do you write a parametrized test? It depends on the testing library you use.
In unittest - a battery included with Python - you use "subtests":
- import unittest
- from primes import max_prime
-
- class TestPrimes(unittest.TestCase):
- def test_max_prime(self):
- testdata = [
- # max_prime_value, input_values
- (3, [3]),
- (7, [7, 3]),
- (7, [3, 7]),
- (11, [10, 11, 12]),
- # ...
- ]
- for expected, values in testdata:
- with self.subTest(values=values):
- self.assertEqual(expected, max_prime(values))
The other big testing framework is Pytest. Which does this in a completely different way, using the @pytest.mark.parametrize decorator:
- import pytest
- from primes import max_prime
-
- testdata = [
- # max_prime_value, input_values
- (3, [3]),
- (7, [7, 3]),
- (7, [3, 7]),
- (11, [10, 11, 12]),
- # ...
- ]
-
- @pytest.mark.parametrize('expected,values', testdata)
- def test_max_prime(expected, values):
- assert expected == max_prime(values)
Those of you who've written unit tests know what these code samples mean...
But even if you didn't, focus on the design:
These two libraries - unittest and pytest - both support this feature, called parametrized tests. And the library developers have a decision to make about how they're going to do that.
Just looking at the code above...
(Again, it's okay if you don't even know how to write unit tests. Because part of good code design is making things as readable as possible to people who aren't (yet) experts... Just look at it, and decipher what you can...)
Looking at these two approaches - what's different?
- The unittest version feels a little lower-level to me. But it also seems less "magic". Neither of these are good or bad, necessarily.
- They both have some boilerplate. But that boilerplate is totally different.
- The pytest version is more concise.
- The unittest version requires special arguments to self.subTest(), which it turns out affect the reporting output. That's usefully flexible, but also gives room for error.
- The pytest version uses decorators in a way not everyone is familiar with.
Other differences too, I'm sure you will notice.
The point of this exercise is not to decide one of these libraries is fundamentally better than the other. Though of course, people have preferences, and that's fine.
But one level of skill with Python is to be proficient using its libraries...
And another level is being proficient at DESIGNING them.
That's my sekrit subversive long-term goal for each of you... that you write code, classes, even whole libraries that naturally catapult the productivity of any developer who uses them.
There's an art and a science to this... and you can learn it.
In closing, ask yourself:
- What are the trade-offs in design between these two libraries, for this particular feature?
- What might you do differently for each?
- And: might there be unintended consequences of that change?