Friday finking: TDD and EAFP

Peter J. Holzer hjp-python at hjp.at
Mon Nov 4 17:18:37 EST 2019


On 2019-11-03 18:51:25 -0500, Terry Reedy wrote:
> 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".

Yup.


> > 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.

Yes. But Percival at least (and he isn't the only one) makes a point
that you really should do this.

I think there are two reasons for this:

* For the beginner, it's kind of a drill. Do something simple and
  mindless over and over, so that it becomes a habit. For the expert, it
  becomes a ritual: Puts you into the right mindset.

* It prevents you from getting ahead of yourself: Without the reminder
  to keep the implementation minimal it is too easy to skip ahead: Write
  one simple test, then implement the whole function/class/program. Your
  function/class/program will pass the test, but it will include a lot
  of functionality which isn't tested (I plead guilty of having done
  this)


> 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.

Yeah, randomizing inputs, cross-checks, etc. are important. I didn't
want to give the impression that tests must be as simple as my example.


> 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()

Interesting. Your test suite has only testcases where the result is an
integer. So sqrt(5) or sqrt(3.14) might return completely wrong results
or not even be implemented at all.

I think this is a good example of what I was thinking of when I wrote
this paragraph:

> > 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'.

I'd say that there is some specification which defines what "correct" is
(even if that specification exists only vaguely in the mind of the
customer). The act of writing test cases necessarily reduces this
holistic definition to a number of isolated points. The art of writing
test cases is to write them in such a way that you can reason about
having covered the whole problem space. The art of writing a program in
TDD then consists in finding a solution which satisfies all test cases.
This is not the same as trying to find a solution to the problem posed
by the specification. Ideally, the solution will be the same, but the
way to it is different and there is no guarantuee that the result will
be tha same.

Don't get me wrong - I think tests are very important and I'm trying to
get into the same habit of writing tests in Python I had when I was
writing Perl. And I think that it makes a lot of sense to write tests
first and only make changes for which you have a test. But I can't help
feeling that TDD encourages a "make random changes until all tests pass"
style of programming instead of a "reason about the code" style of
programming. (But the programmer I know who seems to hail from
the school of random changes doesn't write tests, either, so it's
probably unfair of me to hang that albatross around TDD's neck.)

        hp

-- 
   _  | Peter J. Holzer    | Story must make more sense than reality.
|_|_) |                    |
| |   | hjp at hjp.at         |    -- Charles Stross, "Creative writing
__/   | http://www.hjp.at/ |       challenge!"
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/python-list/attachments/20191104/9f49e786/attachment.sig>


More information about the Python-list mailing list