SWC2013TDD » History » Version 13

Chris Cannam, 2013-02-06 08:28 PM

1 1 Chris Cannam
h1. Test-driven development outline
2 2 Chris Cannam
3 2 Chris Cannam
We assume that the "intro to Python" section has at least introduced how you would run a Python program and compare the output against an external source of "correct" results; also that the NumPy/audiofile section has shown how to suck in an entire (mono) audio file as a NumPy array.
4 2 Chris Cannam
5 6 Chris Cannam
h2. Motivation
6 6 Chris Cannam
7 2 Chris Cannam
We'll refer first back to the "intro to Python" example, with the text file of dates and observations.
8 3 Chris Cannam
9 3 Chris Cannam
<pre>
10 3 Chris Cannam
Date,Species,Count
11 3 Chris Cannam
2012.04.28,marlin,2
12 3 Chris Cannam
2012.04.28,turtle,1
13 3 Chris Cannam
2012.04.28,shark,3
14 3 Chris Cannam
# I think it was a Marlin... luis
15 3 Chris Cannam
2012.04.27,marlin,4
16 3 Chris Cannam
</pre>
17 3 Chris Cannam
18 3 Chris Cannam
We have our program that prints out the number of marlin.
19 3 Chris Cannam
20 3 Chris Cannam
<pre>
21 3 Chris Cannam
$ python count-marlin.py
22 4 Chris Cannam
2
23 3 Chris Cannam
$
24 3 Chris Cannam
</pre>
25 5 Chris Cannam
26 5 Chris Cannam
We can check this against some human-generated output, or the result of "grep" or something if the program is simple enough, in order to see whether it produces the right result. But what if we change the program to add a new feature -- will we remember to check all the old behaviour as well and make sure we haven't broken it? What if the program as a whole is so complex and subtle that we don't actually know what its output will be?
27 5 Chris Cannam
28 5 Chris Cannam
We need to do two things:
29 5 Chris Cannam
30 1 Chris Cannam
# automate the tests, and
31 6 Chris Cannam
# make sure we test the individual components that the program is made up of (so we can be confident of its behaviour even when we don't know what the program as a whole should produce)
32 6 Chris Cannam
33 6 Chris Cannam
h2. Automating a test
34 6 Chris Cannam
35 6 Chris Cannam
Simple program that uses @assert@. It calls the fish counter for a known file, and checks the output.
36 6 Chris Cannam
37 7 Chris Cannam
We're starting to automate things. We can make it more convenient by using @nosetests@, which runs all the functions it finds called @test_@-something in files called @test_@-something in the current directory and subdirectories (recursively searched).
38 6 Chris Cannam
39 8 Chris Cannam
Split this out thus, and run it using @nosetests@.
40 8 Chris Cannam
41 8 Chris Cannam
h2. Testing units, and test-driven development
42 9 Chris Cannam
43 12 Chris Cannam
h3. Context
44 12 Chris Cannam
45 9 Chris Cannam
So consider we have a program that loads data from an audio file, like
46 9 Chris Cannam
47 9 Chris Cannam
<pre>
48 9 Chris Cannam
import scikits.audiolab as al
49 9 Chris Cannam
sfile = al.Sndfile("testfiles/beatbox.wav")
50 10 Chris Cannam
count = sfile.nframes
51 10 Chris Cannam
samples = sfile.read_frames(count)
52 9 Chris Cannam
</pre>
53 9 Chris Cannam
54 1 Chris Cannam
and then does something with @samples@.
55 10 Chris Cannam
56 10 Chris Cannam
Now, for a lot of methods -- particularly spectral domain ones -- the first thing we want to do is chop up @samples@ into frames of a fixed length (1024 is a popular number), either overlapping or non-overlapping. (Draw diagram on whiteboard)
57 10 Chris Cannam
58 11 Chris Cannam
In this case the file has 253929 frames. At 1024 samples per frame, how many frames do we expect this file to consist of if the frames are not overlapping?
59 11 Chris Cannam
60 11 Chris Cannam
253929/1024 comes out (integer division) as 247. Is that the right answer? I've no idea!
61 12 Chris Cannam
62 12 Chris Cannam
h3. Stub function
63 12 Chris Cannam
64 13 Chris Cannam
Sketch the frame count function into @framer.py@:
65 12 Chris Cannam
66 12 Chris Cannam
<pre>
67 12 Chris Cannam
def get_frame_count(nsamples, hop):
68 1 Chris Cannam
    """Given the number of samples, return the number of non-overlapping frames of length hop we can extract from them."""
69 1 Chris Cannam
    return 0
70 1 Chris Cannam
</pre>
71 13 Chris Cannam
72 13 Chris Cannam
And we're going to write a test, in @test_framer.py@:
73 13 Chris Cannam
74 13 Chris Cannam
<pre>
75 13 Chris Cannam
import framer as fr
76 13 Chris Cannam
77 13 Chris Cannam
def test_get_frame_count():
78 13 Chris Cannam
    assert fr.get_frame_count(0, 10) == 0
79 13 Chris Cannam
</pre>
80 13 Chris Cannam
81 13 Chris Cannam
etc.