Classic OOP in Python

Terry Reedy tjreedy at udel.edu
Wed Jun 17 19:46:23 EDT 2015


On 6/17/2015 4:39 PM, Marko Rauhamaa wrote:
> Ned Batchelder <ned at nedbatchelder.com>:
>
>> TDD is about writing tests as a way to design the best system, and
>> putting testing at the center of your development workflow. It works
>> great with Python even without interfaces.

I use what I might call 'smart TDD'.  I think it a mistake to dismiss 
TDD because 'stupid TDD' is possible, and maybe even extant.

The reason for having a test initially fail is to make sure that the 
test is called.  A unittest example:

class NoTest(unittest.TestCase):
     def text_ohmygod(self):
         assertEqual(True, False)

Why does this 'pass'?  Within the last year, it was discovered that a 
TestCase subclass in one of the Python test files was not being run.  I 
made a similar mistake in my own code.  The more I do TDD 
(intelligently), the more I like it.

> I wonder how great it really is. Testing is important, that's for sure,
> but to make it a dogmatic starting point of development is not that
> convincing.

The bugs in Python, whether already fixed, on the tracker, or yet to be 
recorded, are the result of CPython not originally using TDD of some 
version.  In my opinion, CPython maintenance and enhancement has 
improved since some semblance of TDD was instituted.  A runable test is 
now the first stage for tracker issues.

> The way it was explained to me was that in TDD you actually don't write
> code to any requirements or design:

Writing sane code to pass tests based on requirements or a design is 
intended to ensure that one is actually meeting the design.

> you simply do the least to pass the tests.

The incantation I have read is 'simplest', not 'least'.  I personally 
interpret 'simplest' to include some presumption of sanity in relation 
to the ultimate goal.

 > Thus, say you need to write a program that inputs a string and
> outputs the same string surrounded by parentheses (the requirement),

One should write a reusable and easily testable *function* that takes a 
string as input (and outputs the same string surrounded by parens). 
Call the function paren_around.

 > the starting point might be this test case:
>
>     - run the program
>     - give it the word "hello" as input
>     - check that the program prints out "(hello)"

Doubly bad.

1. I consider mixing program i/o with function not related to i/o to be 
a bad idea.  TDD should cure one of this madness, not reinforce it.

2. The first test for smart TDD should best be with a falsey input (or 
other corner or edge case).  With this guideline, there is a good chance 
the 'simplest code' will be augmented rather than replaced with 
additional tests. A smart TDD test might be

     assertEqual(paren_arount(''), '()')

> The right TDD thing would be to satisfy the test with this program:
>
>     input()
>     print("(hello)")

This does not work.  The above adds '\n' to the end of the string, which 
is not part of the specification.  A human may not notice, but an 
automated test that captures stdout and compares with "(hello)" will 
notice and fail.

> That *ought* to be the first version of the program until further test
> cases are added that invalidate it.

As a statistician, I know about the mistake of overfitting functions to 
data.  'Teaching to the test' is a related mistake  Smart TDD should not 
reinforce bad practice.  This simple code passes the initial testcase above.

def paren_around(s):
     "Return string s surrounded by '(' and ')'."
     return '()'

Add a more generic (non-null) testcase.

     assertEqual(paren_around('abc', '(abc)')

The irrelevant details should usually be ignored when augmenting the 
code*.  The simplicity of this example makes it easy to write a correct 
assert, but is not relevant to the intent of the function.

def paren_around(s):
     "Return string s surrounded by '(' and ')'."
     return '({})'.format(s)

Code added, with nothing deleted.

> Another interesting ism I have read about is the idea that the starting
> point of any software project should be the user manual. The developers
> should then go and build the product that fits the manual.

The corresponding TDD approach is to write tests from the manual, and 
then write code.  One can also write tests from docs for existing code 
- and then see if the code passes the doc-based tests.  This is one way 
people discover bugs.

-- 
Terry Jan Reedy




More information about the Python-list mailing list