Friday finking: TDD and EAFP
Terry Reedy
tjreedy at udel.edu
Sun Nov 3 18:51:25 EST 2019
Some general thoughts on core development and testing. For
bugs.python.org issues, the first Stage choice is 'test needed'. All
code patches *should* include new tests. This was not always so, and we
are still paying off technical debt. One problem I have encountered with
idlelib is that some refactoring is needed to make tests easier or even
possible to write, while refactoring should be preceded by good tests.
*Should* is not always enforced, but skipping them without doing
adequate manual tests can lead to new bugs, and adequate manual tests
are tedious and too easy to forget. I have learned this the hard way.
On 11/3/2019 3:44 PM, Peter J. Holzer wrote:
> On 2019-11-04 07:41:32 +1300, DL Neil via Python-list wrote:
>> Agreed: (in theory) TDD is independent of language or style. However, I'm
>> wondering if (in practice) it creates a mode of thinking that pushes one
>> into an EAFP way of thinking?
>
> This is exactly the opposite of what you proposed in your first mail,
> and I think it is closer to the truth:
>
> TDD does in my opinion encourage EAFP thinking.
As in "use the code and if it fails, add a test and fix it" versus "if
the code can be proven correct, use it".
> The TDD is usually:
>
> 1 Write a test
> 2 Write the minimal amount of code that makes the test pass
> 3 If you think you have covered the whole spec, stop, else repeat
> from 1
>
> This is often (e.g. in [1]) exaggerated for pedagogic and humoristic
> reasons. For example, your first test for a sqrt function might be
> assert(sqrt(4) == 2)
> and then of course the minimal implementation is
> def sqrt(x):
> return 2
The *is* exaggerated. For math functions, I usually start with a few
cases, not just 1, to require something more of an implementation. See
below.
> Which just means that we don't have enough test cases yet. But the point
> is that a test suite can only check a finite (and usually rather small)
> number of cases, while most interesting programs accept a very large (if
> not really infinite) number of inputs, so the test suite will always be
> incomplete.
I usually try to also test with larger 'normal' values. When possible,
we could and I think should make more use of randomized testing. I got
this from reading about the Hypothesis module. See below for one
example. A similar example for multiplication might test
assertEqual(a*b + b, (a+1)*b)
where a and b are random ints.
> At some point you will have to decide thet the test suite is
> good enough and ship the code - and hope that the customer will forgive
> you if you have (inevitably) forgotten an important case.
Possible test for math.sqrt.
from math import sqrt
from random import randint
import unittest
class SqrtTest(unittest.TestCase):
def test_small_counts(self):
for i in range(3):
with self.subTest(i=i):
self.assertEqual(sqrt(i*i), i)
def test_random_counts(self):
for i in range(100): # Number of subtests.
with self.subTest():
n = randint(0, 9999999)
self.assertEqual(sqrt(n*n), float(n))
def test_negative_int(self):
self.assertRaises(ValueError, sqrt, -1)
unittest.main()
> There is very little emphasis in TDD on verifying that the code is
> correct - only that it passes the tests.
For the kind of business (non-math) code that seems to be the stimulus
for TDD ideas, there often is no global definition of 'correct'.
--
Terry Jan Reedy
More information about the Python-list
mailing list