2 ms·
Indeed, I tend to avoid writing a single test that captures 'all' of a function/unit's behaviour (like the article is doing). Instead, I would write multiple t
by chriswarbo 4y ago
Indeed, I tend to avoid writing a single test that captures 'all' of a function/unit's behaviour (like the article is doing).
Instead, I would write multiple tests, mostly just asserting one thing each. For example: `test_gcd_always_positive(m, n)`, `test_gcd_divides_arguments(m, n)`, `test_gcd_is_largest_divisor(m, n)`, etc.
I also think in terms of a 'threat model' for our colleagues or past/future selves:
- They're probably not trying to write broken code, so we don't need to catch all incorrect implementations. In other words, they're not adversarial.
- They're probably lazy or mistaken, so we should try to catch incorrect implementations which are simple to write, make obvious mistakes, etc.
For example, having `my_sort = lambda _: []` is a really lazy way to implement the right type signature. Checking that the lengths and set of elements are the same are good tests to catch that, so it seems worth doing. Likewise, checking that the elements are sorted is a good test to avoid the lazy approach of `my_sort = lambda l: l`.
However, the implementation which sorts the set of elements and pads with the largest is pretty convoluted, so a lazy developer will likely just write a basic sort algorithm at that point (e.g. `my_sort = lambda l: [x for y in set(l) for x in l if x == y]`). Also, the test is a bit tricky; if that counting function didn't already exist, I probably wouldn't bother writing that test at all (there are equivalents which might be simpler to write, e.g. for each input element: assert it appears in the output, then remove its first occurrence; then also assert the output ends up empty)