120 likes | 328 Views
TDD Basics. Tom Clune presiding. Principles. Tests should not duplicate implementation Tests should strive to be orthogonal Tests do not uniquely determine implementation Tests should be fine-grained (small increments). Specific tests should be derived from: Basic requirements
E N D
TDD Basics Tom Clune presiding
Principles • Tests should not duplicate implementation • Tests should strive to be orthogonal • Tests do not uniquely determine implementation • Tests should be fine-grained (small increments). • Specific tests should be derived from: • Basic requirements • Simplest cases • Limiting cases • Triangulation • Only implement to just pass the last test. • Always check that tests initially fail.
Tests vs Implementation • Writing tests is a somewhat different art than writing an implementation. • Requires practice and creativity • Tests drive implementation, but … • Tests do not completely constrain implementation • Examples of bad/useless tests: • Using method to be tested to specify the “expected” clause in an assertion:expected = f(3.)call assertEqual(expected, f(3.)) • Reimplementing method to be tested to specify “expected” clause in an assertion • Sometimes a test is based upon an alternate implementation. • E.g. brute force implementation.
Example Geometric Sum a0 = 1. ratio = 0.5 s = 0 n = 2 do i = 0, n - 1 s = s + a0 * ratio ** i end do expectedSum = s call assertEqual(expectedSum, geometricSum(a0, ratio, n)) real function geometricSum(a, ratio, n) geometricSum = sum( (/ (a * ratio ** (i-1),i=1,n) /) ) end function geometricSum real function geometricSum(a, ratio, n) a_1 = a a_n = a * ratio ** (n-1) geometricSum = (a_n - a_1) / (1 - ratio) end function geometricSum
Orthogonality • Developers should strive to create tests which are mutually orthogonal. • Strive for each test to cover just one implementation “feature”. • Tests which are already covered by other tests are wasteful. • Non-orthogonal tests may be limited in their ability to clearly isolate a single defect. • I believe that exceptions to this rule are frequent, but the goal should be born in mind.
Determinism • Many aspects of implementations are not driven directly by requirements. • Tests only verify behavior of implementations. (Tests are functional implementation of requirements.) • Tests aid the development of an implementation by focusing attention on smaller bits of algorithm. There is no magic. • Especially true for performance - consider multiple algorithms for • Sorting (quick, bubble, …) • Random number generation (LCG, …) • Fourier transform (fast vs slow) • Caveat: may need to “invent” tests to cover intermediate steps • One step/sweep of sorting algorithm • Butterfly operation for FFT • Row reduction for matrix solver
Test Granularity • Each test should generally only drive a few lines of implementation. • Developers often overestimate their amount of code which can be written without introducing a defect. • Discipline requires practice. • The first test for a unit often drives creation of a large amount of “boiler-plate” code. This is ok. • New module, derived type, constructor, and at least one method. • We should try to create templates for most of these items. • If implementation bogs down, backup and make test even smaller/simpler.
Minimal Implementation • Avoid the temptation to implement beyond what is required by the test. • Any such extension is not itself being tested, so might have defects. • “You ain’t gonna need it!” - XP mantra • Save time for creating more/better tests. This is where creative energies should mostly be applied for TDD. • Following this minimal approach requires practice and discipline.
Detailed Example • Implement a “stack” (LIFO) of integers. • A stack is a structure which enables addition and retrieval of items where retrieval is always of the last item added. • Methods: • new()/clean() ! Constructor and Destructor • push() ! Add n to stack • pop() ! Return n from stack • numElements() ! Return number of elements currently in stack.
Excercises • Start from scratch and implement a “queue” (FIFO) analogous to the “stack” in the previous examples. • If you are strong with F90, stretch yourself by using pointers to implement a linked list instead of fixed maximum size. The differences are interesting. • Bonus exercise: Starting from scratch use TDD to create a routine which returns the prime factors of a given integer as an array.