Title: An Introduction to Test Driven Development Julian Zappala
1An Introduction to Test Driven DevelopmentJulian
Zappala
2Why test at all?
- As software developers we are only human
- It's inevitable that we'll make mistakes
- Data from industry experience suggests we might
expect, on average, 15 to 50 errors per 1000
lines of source code - McConnell, Steven C.
- Code Complete A practical handbook of software
construction. - 2nd ed. Microsoft Press, 2004.
3Typical Software Development
- CODE FIRST... then test (hopefully...)?
- Developer testing activities often include
- Unit Testing Does the new unit work by itself?
- Integration Testing Does the new unit work with
other units? - Regression Testing Does the new unit break
other units which used to work? - These are distinct from other testing roles
- Functional Testing e.g. User Acceptance Testing
- Non-functional Testing e.g. Performance,
Usability, Scalability, Security etc...
4About Test Driven Development
- A different approach to Designing, Developing and
Testing software - Conceived by Kent Beck as part of eXtreme
Programming - Increasingly popular as part of many Agile
methodologies - Often achieved through the use of a unit testing
framework such as the xUnit family - A unit is single, atomic, software component
typically this means a class
5Steps in Test Driven Development
- TEST FIRST... then code
- First write a test for some, typically very
small, unit of code - The test will fail as the code is not yet written
- Write just enough code such that the test should
pass - Test again to be sure that the new (and any
existing tests) pass - Refactor make sure the new code is tidy
- Repeat
6Implications of Test Driven Development
- By writing the test first, the initial focus is
on how we'd like to use a class rather than how
we'd like to make the class - This approach encourages Coding by Intention
- By running tests frequently we can be more
confident about the quality of our work - By using automated testing tools we can discover
early on if new code introduces integration or
regression issues - Let's look at an example...
7Example Use Case
Roll Dice
Preconditions The actor has a n-sided fair
dice Steps 1. The actor rolls the
dice Postconditions The actor has a random
number between 1 and the number of sides
8A naive design for the Dice Class
Dice
- numSides int - faceValue int
getNumSides() int setNumSides(numSides
int) void getFaceValue() int roll() void
Would anyone care to comment on the design of
this class?
9Some Issues in the naive design
- It is possible to invoke roll without first
setting the number of sides - It is possible to invoke getFaceValue without
first invoking roll (or setting the number of
sides)? - It is possible to change the number of sides that
a dice has (!)? - etc...
- So, can TDD give us a better design?
10TDD Step 1 Write a test
- But what test?
- How about instantiation?
- OK we're going to make a new dice how many
sides will it have? - What can we test? - Maybe that we have an
object...
public void testDiceConstructor() Dice d
new Dice(6) assertNotNull(d)
11TDD Step 2 Fail the Test
- Attempt to run the test
- The test will fail in fact it won't even
compile because we haven't created the Dice class
yet - Error Dice cannot be resolved to a type
- This counts as a failure in TDD
- Now that we have a test that fails it's time to
write some code...
12TDD Step 3 Write just enough code
- We are only concerned with passing the test, so
public class Dice Dice(int numSides)
13TDD Step 4 Test Again
- This time the test passes!
14TDD Step 5 Refactor
- Refactoring is a disciplined technique for
restructuring an existing body of code, altering
its internal structure without changing its
external behavior. - Fowler, Martin
- http//www.refactoring.com/
- There isn't much to refactor at this stage, so
let's press on...
15Repeat From Step 1
- Write a test How many sides does the dice have?
- public void testDiceSixSides()
- Dice d new Dice(6)
- assertEquals(6,d.getNumSides())
-
- The test fails (will not compile) as there is no
getNumSides method...
16Writing Just Enough code...
- To pass the test we only need
- public int getNumSides()
- return 6
-
- Run the test - This will pass the test
- There's still no requirement to refactor
- So let's write another test
17Another Test...
- What about a ten sided dice?
- public void testDiceTenSides()
- Dice d new Dice(10)
- assertEquals(10,d.getNumSides())
18Which Fails...
19So we (try to) fix it...
- We could just return 10 from getNumSides() but
that would break the test for Six sides, so... - public class Dice
- private int numSides
- Dice(int numSides)
- this.numSides numSides
-
- public int getNumSides()
- return numSides
-
20That passes - Great!
21Choose your tests carefully...
- Let's test something that shouldn't happen
- This is sometimes referred to as
- Test what might break
- Ever heard of a one sided dice?
- public void testDiceOneSide()
- Dice d new Dice(1)
- assertTrue("No One Sided Dice!",
- 1 ! d.getNumSides())
22Good - That fails too!
Assume that we fix this (and pass the
test)? Let's move on...
23Yet Another Test
- Assuming we're confident that we can now make a
good dice, let's try rolling it... - public void testRollSixSides()
- Dice d new Dice(6)
- int v d.roll()
- assertTrue("Not Between 1 and 6",
- (v gt 1) (v lt 6))
24Write some more code...
- Just enough code to pass could be
- public int roll()
- return 5
-
- That passes the test too!
- Let's see where we're at...
25The Test Driven Development Dice
Dice
- numSides int
Dice(numSides int) this roll() int
Compare to the naive Dice
Dice
- numSides int - faceValue int
getNumSides() int setNumSides(numSides
int) void getFaceValue() int roll() void
26Can we test that the Dice is fair ?
- Testing things that are unpredictable is very
hard. In software there are many things that are
unpredictable e.g. - Data in a database, Networks and network
resources - Other software developers
- Typically anything beyond the boundary of the
unit that is being tested - One solution to this is to use Mock Objects
when testing
27About Mock Objects
- Used in unit testing where an action does not
have a guaranteed result - One solution is to define the action(s) through
an interface - In testing circumstances we use a Mock Object to
implement the interface, for production code use
the real object - The real object can be tested through (automated)
integration testing at some later stage
28Summary Some ve's of TDD
- Coding by intention can produce clearer, simpler
code - Focusing on one thing (the failed test) can make
programming more efficient and reduce time spent
debugging - Making classes that are Unit Testable typically
means that the overall system is more Modular and
more Loosely Coupled - Every unit of code is tested and so should be
more robust and contain less errors
29Summary Some -ve's of TDD
- Writing the tests means there is more code to
write, maintain and debug - Managers often find this hard to come to terms
with - Writing the right tests isn't always easy, and
wrong tests lead to wrong code - Although 100 testing coverage is possible even
this may not exercise every code-path - TDD is not a panacea for testing
30Further Reading/Resources
- Test Driven Development By Example
- Kent Beck
- Addison-Wesley (2002)?
- Refactoring Improving the design of existing
code - Fowler, Beck, Brant, Opdyke, Roberts
- Addison-Wesley (1999)?
- JUnit
- http//www.junit.org/