Title: Unit Testing
1Unit Testing
- Quickly Designing and Writing Things That Work
By Jim Moore, Symantec Software, ETS
2Which Is the Culture Where You Work?
- I coded up the feature you wanted. I think it
works, but Im not sure.Just send it over to
QA and start working on the next thing. Well
deal with any issues they find later.
or - I coded up the feature you wanted, but I still
need to add a few more tests to make sure its
solid.Great, when do you think youll be
done? - The first relies on a testing and maintenance
cycle to handle issues later. The second assumes
that its as solid as possible before you stop
working on it. - Bugs found while the developer is still in the
mindset of that feature are much faster to fix
than ones found later. - The developer is always better off continuing to
develop from a solid foundation. (ie, Its best
not to develop against buggy code.)
3The Problem
Every programmer knows they should write tests
for their code. Few do. The universal response to
Why not? is Im in too much of a hurry. This
quickly becomes a vicious cycle the more
pressure you feel, the fewer tests you write. The
fewer tests you write, the less productive you
are and the less stable your code becomes. The
less productive and accurate you are, the more
pressure you feel.
- JUnit Test Infected Programmers Love Writing
Tests - http//junit.sourceforge.net/doc/testinfected/test
ing.htm
4Unit Testing Is About Going Faster
...Unit Testing is about delivering working
software quickly. That means you need to be able
to write the code quickly, get it to work (avoid
and fix defects) and to be able to change and
maintain the code in future. The fastest way to
deliver working software is to write correct
code, straight off, in one sitting, to a known
set of requirements. Most programmers cant write
code thats perfect, and most customers change
their minds about the features they want (this is
a good thing were embracing change). Your
response as a programmer to the reality of
imperfect code is to write unit tests to help
check your work.
- Mike Mason
- http//mikemason.ca/2004/01/29032UnitTestingIsAbo
utGoingFaster
5When To Write Tests
- Martin Fowler makes this easy for you. He says,
Whenever you are tempted to type something into
a print statement or a debugger expression, write
it as a test instead.... - Here are a couple of the times that you will
receive a reasonable return on your testing
investment - During Development - When you need to add new
functionality to the system, write the tests
first. Then you will be done developing when the
test runs. - During Debugging - When someone discovers a
defect in your code, first write a test that will
succeed if the code is working. Then debug until
the test succeeds.
- JUnit Test Infected Programmers Love Writing
Tests - http//junit.sourceforge.net/doc/testinfected/test
ing.htm
6What Is A Test?
- At the high-level, a test is a requirement. As
every trained project manager knows, a project
requirement must be clear and testable.
Automated tests are just automatic requirement
checks. - There are tools, like FITnesse and Mercurys
Quality Center, that handle this from the high
level (functional and acceptance tests). This
presentation will focus on the lower level unit
tests. - Probably the easiest way to think of what a good
unit test is, is that its something that
verifies a piece of functionalitys contract. - Pre-conditions Those things that must be true
before you run the code. (Example The objects
buffer has been initialized.) - Post-conditions Those things that must be true
after you run the code. (Example This method
will never return null.) - Invariants Those things that must always be
true. (Example The list size will always be
greater than or equal to 0.)
7What Makes a Good Unit Test? Part 1
- It sufficiently tests the contract. It doesnt
need to be complete, just sufficient. - If its complex code, then have lots of tests.
- If its a simple accessor, then you dont need a
test. - Most things are in between
- It runs quickly. Part of the point is to make it
so that youre able to run them regularly, so
speed plays into that. - Its much easier to fix a bug you introduced five
minutes ago than one you did five weeks ago - cont
8What Makes a Good Unit Test? Part 2
- Tests are independent.
- If you have dependencies between tests, or have
to do tons of setup, then its a pain to write
tests and to run them. - Loosely coupled functionality enables independent
tests. - Write loosely coupled, highly cohesive code!
- Run the tests regularly. (Ideally, use something
like CruiseControl to run the complete test suite
run automatically after you check code in.) - One of the seriously cool benefits of having the
automated tests is that it gives you automatic
regression testing. - You can be fearless when you need to make
changes, because you dont have to worry that you
mightve broken something.
9Writing Good Tests
- If you do all the stuff that you know youre
supposed to anyway (loose coupling, high
cohesion, etc.), writing tests is really easy.
-) - Seriously Loosely coupled and highly cohesive
software is much easier to test than tightly
coupled systems, or ones where its hard to tell
what something is really for (low cohesion). - Theres a lot of Patterns for making that easier,
like Inversion of Control, Strategies, etc. - Writing the test before you write the code
guarantees that your code is testable. - With very few exceptions, code that is hard to
test is a HUGE flag that the design is bad (ie,
tightly coupled, low cohesion).
10Example of How To Think of Tests
- The required functionality for a particular
service (getVersionsForProductId) is the ability
to get a list of all versions for a given product
id. If theres no product for that id, then an
exception is thrown. If theres no versions for
a valid product id, then an empty list is
returned.
11The Tests That Are Needed
- Starting from the easiest to the hardest
- If theres no product for that id, then an
exception is thrown (pre-condition) - If theres no versions for a valid product id,
then an empty list is returned (post-condition) - If there are versions for a product id, then a
non-empty list of all the versions is returned
(post-condition)
12Implementing the Tests
- def testInvalidProduct
- begin
- vers getVersionsForProductId(-99)
- assert(false, "Should have thrown an
exception, but got "vers.to_s) - rescue
- an exception should've been thrown
- end
- end
- def testNoVersionsForProduct
- 1234 is a legit product id, but we know it
doesn't have any versions - vers getVersionsForProductId(1234)
- assert(vers.length 0, "Got versions back
"vers.to_s) - end
- def testListOfVersionsForProduct
- 2345 is a legit product id, and we know it
has versions - vers getVersionsForProductId(2345)
- assert(vers.length 0, "Didn't get any
versions back")
13Tools To Help Make Testing Easy
- The de-facto framework that everybody uses is
JUnit. Theres clones in pretty much every
language, like NUnit for C. Heres a sample - using System
- using NUnit.Framework
- using System.Text.RegularExpressions
- namespace Notepad
- TestFixture
- public class TestRegex Assertion
- Test
- public void SimplePattern()
- Regex r new Regex("")
- Match m r.Match("contains here")
- Assert(m.Success)
- m r.Match("contains no para")
- Assert(!m.Success)
-
-
-
- For a good list of all the various languages, go
to http//www.xprogramming.com/software.htm
14What Is JUnit?
- JUnit is an open source Java testing framework
used to write and run repeatable tests. It is an
instance of the xUnit architecture for unit
testing frameworks. - JUnit features include
- Assertions for testing expected results
- Test fixtures for sharing common test data
- Test suites for easily organizing and running
tests - Graphical and textual test runners
- JUnit was originally written by Erich Gamma and
Kent Beck
- FAQ on junit.org
15Simple Example
- public class TestUser
- extends junit.framework.TestCase
- public void testSetName()
- User user new User()
- assertEquals(, user.getName())
- user.setName(Jim)
- assertEquals(Jim, user.getName())
-
16Notes On The Simple Example
- All JUnit tests implement the Test interface,
typically by extending the TestCase class. - The testing harness can automatically determine
what to run by looking for any method that is
public, returns void, and starts with test. - Test conditions are asserted. On the previous
screen, for example, we were asserting the
equality of two values. - If it makes it through the test without any
assertions failing, the test is considered to
have passed.
17Types of Assertions (part 1)
- assertTrue / assertFalse
- assertTrue(list.size() 10)
- assertTrue(list.size() 10 list.size(),
list.size() 10) - assertEquals
- assertEquals(10, list.size())
- assertEquals(Expected 10, Actual
list.size(), 10, list.size()) - assertTrue/False is more flexible, but if it
fails the message isnt very useful unless you
provide your own message. - assertEquals automatically shows the expected and
actual values so you dont have to write your own
message.
18Types of Assertions (part 2)
- assertNull / assertNotNull
- Convenience for assertTrue(obj null)
- assertSame / assertNotSame
- Convenience for assertTrue(obj1 obj2)
- fail(msg)
- Convenience for assertTrue(msg, false)
- Used to indicate that a point in the code should
not have been reached (eg, an exception should
have been thrown)
19Example Using fail
- public void testBadLoad()
- try
- User.load(-1234) // bad id
- fail(Should have thrown a PersistenceExceptio
n) -
- catch (PersistenceException exp)
- // should have happened
-
20Errors Failures
- At the end of testing, JUnit reports three
statistics (with an implied 4th) - Tests run the number of tests run
- Failures the number of assertXXX statements that
failed - Errors the number of uncaught Exceptions
- Passed (Tests run (FailuresErrors))
21Throwing Exceptions
- The test methods must be public, have no return
value or arguments, and begin with test.
(Theres actually a way to get around this
restriction, but its not worth it) It can
throw anything, so its common to have all the
tests throw Exception so you dont have to worry
about dealing with try/catch in your test code. - If an exception is thrown, its counted as an
error (as opposed to a failure)
22Running JUnit
- There are three basic ways to run a test
- Within your IDE
- As a GUI using junit.swingui.TestRunner
- As text using either junit.textui.TestRunner or
Ant
23Running JUnit in IDEA
24Running JUnit As a GUI
25Running JUnit as Text
- java junit.textui.TestRunner test.ast.titan.busi
ness.customer.TestCustomerDao..Time 4.031OK
(2 tests) - ant test -Dtestclasstest.ast.titan.business.cus
tomer.TestCustomerDaoRunning test.ast.titan.busin
ess.customer.TestCustomerDaoTests run 2,
Failures 0, Errors 0, Time elapsed 3.515 sec - Using Ant you can also easily have it do reports
using the junitreport task
26JUnitReport Task Output
27Other Options for Running Tests
- In IDEA you can tell it to run tests for an
entire package, a particular class, or a
particular test method. - In Ant you can use the batchtest subtask of the
junit task to run a specific batch of tests.
(The junitreport task it usually used to
process the results of a batchtest run.) - Both IDEA and Ant have lots and lots of options
for customizing what tasks are run and how.
28Putting Together A TestSuite
- public static Test suite()
- TestSuite suite new TestSuite()
- suite.addTest(new TestCustomerDao("testLoad"))
- suite.addTest(new TestCustomerDao("testCommit"))
- return suite
-
- This way you can specify exactly what tests are
run and in what order. If suite() is in your
test class, it wont use automatic detection of
tests. - Theres an alternate idiom for TestSuite where
you can pass it a Class instead of a TestCase,
which uses automatic detection of tests in a
TestCase, but allows you to string together
TestCases. - This can be handy if you have a few test classes
that should be run together (like for integration
testing).
29Why TestSuite Is Generally Evil
- Its duplication youve already declared the
test, let it do the work of finding it and
running it - The order of your tests shouldnt matter if they
do its a flashing red light with air-raid sirens
that the tests are either poorly done, or that
what they are testing is poorly designed - If you want to temporarily disable a test, its
easiest to just prepend XX to the name (eg,
XXtestLoad), which both causes it to not be run
automatically and makes it obvious when you look
at the test that its not being run - Its recommended practice to separate the tests
that you run all the time (ie, standard unit
tests) and those that are too expensive to run
all the time (eg, integration tests) by either
package or directory structure. That way it
reduces maintenance by not having to maintain
TestSuites, and makes it clear which tests are
for and how often they are run.
30Running Tests Automatically
- Whereas
- Running the complete test suite regularly makes
sure that the minimum amount of time has past
between a test breaking and discovering that fact - Running a complete test suite can take a long
time, thereby being impractical to run regularly
manually - People are forgetful, and even when it would be
quick and easy to run the tests they will forget - Therefore be it resolved
- A tool such as CruiseControl (http//cruisecontrol
.sf.net/) should be used to automate the
execution of regular builds and running of test
suites - When a developer checks in code that breaks the
test(s), notification should be sent so he/she
can fix it quickly - If the offending developer does not immediately
start fixing the problem, a wedgy should
administered once an hour, upon the hour, by all
the other developers who are being spammed
because the tests are broken
31Test Granularity
- The same principles that apply to coding in
general still apply to tests - Each test should be highly focused, testing just
one piece of functionality (high cohesion) - Each test should be independent of the others,
without side-effects (low coupling) - Example As you add tests to testLoad it may be
best to get rid of it and split it apart into
testLoadValid, testLoadInvalid,
testLoadDatabaseError, etc.
32Setting Up and Tearing Down
- Before each test method, the protected void
setUp() method is called - After each test method, the protected void
tearDown() method is called - This is extremely useful for doing common
configuration that every test in the TestCase
needs (eg, opening and closing a socket
connection) - Note Because its run for each test, its not
appropriate for expensive resource setup (eg,
connecting to a database). For that use either
the TestSetup decorator class, or a static
variable and initialize it in the class static
block. However, make sure that using that
resource doesnt cause side-effects between tests!
33Other Tools For Running Tests
- http//www.junit.org/news/extension/index.htm
links to hundreds of extensions for various needs - Some of the ones I like
- NoUnit Shows a report of what methods are
actually tested (by reading your byte-code) - JUnitPerf Lets you do everything from
performance testing (making sure a test returns
in a specific amount of time) to load testing
(running a test in X threads Y times), which can
also be very handy for finding intermittent
problems (eg, race-conditions) - JFCUnit Provides some nice tools to make it
almost reasonable to test Swing code - HttpUnit Provides some nice tools to make it
almost reasonable to test web code - StrutsTestCase Provides a mock of Struts that
can be run outside a servlet engine for testing - Cactus The best way to test J2EE, its
ridiculously complicated, but thats because
testing code in J2EE containers is very complex
(an alternative is to use MockEJB) its best to
avoid having to do this altogether by using
Dependency Injection!
34Naming Conventions
- You can name your TestCases anything you like,
but recommended practice is prepending Test
before the name of the class youre testing. (eg,
TestCustomerDao) - Test names should reflect what it is they are
testing, obviously, but what does that mean? - testXXX, where XXX is the method being tested
- This is nice because it makes it obvious what
method is being tested, and works well with tools - testXXX, where XXX is not a method name
- This is needed when testing several methods
together, or when it takes multiple tests to test
a method (like the testLoad example being
broken apart earlier)
35Showing Test Names
- Tools like TestDox (http//agiledox.sf.net/) can
be used to create pretty names from TestCases - Example
- public class TestFoo extends TestCase
- public void testIsASingleton()
- public void testAReallyLongNameIsAGoodThing()
-
- Generates
- Foo
- - Is a singleton
- - A really long name is a good thing
- There are plugins for IDEs (like IDEA) that will
do the same thing when they display tests
36Testing Protected/Private Code
- In general, you should only be testing public
methods - In Java, access privileges are a suggestion
- For protected code, you can override the class in
your test. - class A
- protected int m() return 2
-
- class B extends A
- _at_Override public int m() return super.m()
-
- For private and protected code, you can use
reflection - class A
- private int m() return 2
-
- class MyTestCase extends TestCase
- public void testM() throws Exception
- A a new A()
- Method method a.getClass().getMethod("m",
null) - method.setAccessible(true)
- Integer result (Integer)method.invoke(a,
null)
37Common Code
- Theres nothing that says you have to extend
directly from the TestCase class. If you have a
number of TestCases that requires common setup
and utility methods, create an abstract class
that extends from TestCase that your classes
extend from.
38What Is Test Driven Development?
We start by writing some client code as though
the code we want to develop already existed and
had been written purely to make our life as easy
as it could possibly be. This is a tremendously
liberating thing to do by writing a model client
for our code, in the form of a test, we can
define programmatically the most suitable API for
our needs.
- Dan NorthJava Developers Journal, Nov 2003
39Some Principles of TDD
- Main Ideas
- When youre done, youre done
- Do as little work as possible
- Corollaries
- By having tests that demonstrate that things
work, you know when youre done - Assume that the code you write should have APIs
that exist to make your life easier - By having tests, you can refactor things to make
them even simpler and be assured of the safety of
doing so
40Doing As Little Work As Possible
Baby steps Baby steps
- Bill Murray, What About Bob?
41Sample Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
42Loading Users
- The first item is that I need a way to load
users - Whats the simplest way that I can ask a user to
load? - Well, first thing that comes to mind is
user.load() - Lets write a test to see how well doing that
would work
43Writing the Test
- public void testLoad() throws Exception
- User user createUser()
- user.load()
-
- Since user.load() hasnt been written yet, this
wont even compile
44Compiling
- Whats the fastest way to get the test to at
least compile? - Add this to the User class
- public void load()
-
- Woo-hoo! It compiles!
45Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
46Writing the Test
- public void testLoad() throws Exception
- User user createUser()
- user.setModelID(9999L)
- assertTrue(!"jmoore".equals(user.getUserName()))
- user.load()
- assertEquals("jmoore", user.getUserName())
-
47Setting Username
- The test fails.
- Whats the fastest way to get a working test?
- public void load()
- setUserName("jmoore")
-
- The test passes! Were done! Seriously. Just
walk away
48Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
49First Name
- Lets addassertEquals("Jim", user.getFirstName())
to the test case - Bummer, it failed.
- public void load() setUserName("jmoore")
setFirstName("Jim") - Yay! It passes now!
50Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
51Other User Test
- public void testLoadSean() throws Exception
- User user createUser()
- user.setModelID(9998L)
- assertTrue(!"smoore".equals(user.getUserName()))
- user.load()
- assertEquals("smoore", user.getUserName())
-
- When I run it, it fails.
52Other User Load
- public void load() if (getModelId() 9999L)
setUserName("jmoore")
setFirstName("Jim") else if (getModelId()
9998L) setUserName("smoore")
setFirstName("Sean") - Yay! All tests pass now!
53Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
54Invalid User Test
- public void testLoadInvalidUser() throws
Exception - User user createUser()
- user.setModelID(-1234L) // known bad id
- try
- user.load() fail("Should have thrown an
exception") -
- catch (IllegalStateException exp) // should
have happened -
-
- When I run it, it fails.
55Invalid User Load
- public void load() throws IllegalStateException
if (getModelId() 9999L)
setUserName("jmoore") setFirstName("Jim")
else if (getModelId() 9998L)
setUserName("smoore") setFirstName("Sean")
else throw new IllegalStateException(
"There was a problem loading user
"getModelId()) - Yay! All tests pass now!
56Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
57Problem Loading
- Well say that IllegalStateException should also
be used if theres a problem loading - What could go wrong while loading?
- Well, the database or network could be down
- Shoot, we need to make sure that the values come
from the database. Better add that to the list
58Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
- Load values from the database
59Load from Database
- public void load() throws IllegalStateException
- try
- // lots and lots of code for getting the
- // values from the database...
-
- catch (SQLException e)
- IllegalStateException exp new
IllegalStateException( - "There was a problem loading user
"getModelId()) - exp.initCause(e)
- throw exp
-
- finally
- // close all the open resources
-
-
- Ouch, that was a lot of work between runs of
tests - Fortunately, all the tests we had before still
pass. Yay!
60Work List
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
- Load values from the database
61Work List
Finished!
- Need way to load users
- Username for user 9999 is jmoore
- Firstname for user 9999 is Jim
- Username for user 9998 is smoore
- What if user doesnt exist?
- Was there a problem loading?
- Load values from the database
62Some Principles of TDD (Review)
- Main Ideas
- When youre done, youre done
- Do as little work as possible
- Corollaries
- By having tests that demonstrate that things
work, you know when youre done - Assume that the code you write should have APIs
that exist to make your life easier - By having tests, you can refactor things to make
them even simpler and be assured of the safety of
doing so
63Results
- When youre done, youve got
- code that you know works
- an API that is easy to use (since it was designed
by actually being used) - highly cohesive, loosely coupled components
(writing tests for anything else is too hard, and
since the point is to do things the easiest way
possible, this happens naturally) - more time, since youre not over-designing (if it
isnt needed to make a test pass, why write it?)
and not under-designing (you still have to make
sure all the tests pass) - cont
64Results (cont.)
- When youre done, youve got
- examples on how the code is meant to be used
developers like to see code examples for an API.
You also know the documentation is accurate! - documentation on what kinds of problems you were
trying to solve when you wrote the code (which
also nicely solves the What the heck was I
thinking when I wrote this?? problem) - a suite of regression tests, making maintenance
much easier since you can make changes without
fear - freedom to try things, since changes are made in
small increments and there are tests to make sure
nothing breaks
65Patterns in Testing 1
- Child Test If a test case is getting too big,
break it into smaller, more easily maintained
tests - Mock Objects If something is too expensive or
unreliable to test with (for example, a database
or other network resource), then mocking it out
can improve speed and reliability. To guard
against relying too much on the mock, make sure
it can easily be switched out with the real thing.
66Patterns In Testing 2
- Log String If you need to make sure a sequence
is called in a particular order, append the fact
that a call was made to a common string, then
compare the string to whats expected. - Crash Test Dummy If you want to test that your
code reacts appropriately to a condition that you
cant easily replicate (eg, database crash,
network failure, etc.), artificially create the
error. For example, test the screen that loads a
user by testing with a version of User that
throws IllegalStateException every time its
called.
67What About Things That Are Hard To Test?
- Some things that are traditionally hard to test
are - GUIs
- Databases and other external services
- Things that need to run in containers (eg,
J2EE) - Using a combination of Separation of Concerns and
Mock Objects, its not hard to do this. - When it really is hard, its almost always an
indication that your object is doing too much
(ie, low cohesion).
68Resources 1
- The FAQ is excellent, and really does answer
pretty much any question about JUnit itself that
youre likely to come across http//junit.sourcef
orge.net/doc/faq/faq.htm - There are a lot of great resources (extensions,
articles, use cases, etc.) referenced from the
http//junit.org site. - A great book about JUnit, a number of its
extensions, Ant, and more is Java Extreme
Programming Cookbook from OReilly.
69Resources 2
- http//www.junit.org/news/article/index.htmLots
and lots of articles on how to test - http//junit.sourceforge.net/doc/testinfected/test
ing.htmJUnit Test Infected Programmers Love
Writing Tests - Lots and lots and lots of discussions, blogs, etc.
70QUESTIONS
ANSWERS