Unit Testing

Unit Testing
Topics
• Motivation
• JUnit framework
• Basic JUnit tests – static methods
• Ensure exceptions thrown
• @Before and @BeforeClass –
instance methods
• Test floating point values
JUnit Motivation
• Goal: relieve human from task of comparing
expected and actual values
public class Utilities {
public static int calcPerimeter(int length, int width)
{
return 2 * (length + width);
}
public static void main(String [] args) {
// Ensure output is 16
System.out.println(Utilities.calcPerimeter(2,6));
}
}
Test-Driven Development (TDD)
1. Add a test (or tests) for an new unit
of functionality (unit === method)
2. Run prior tests, see new test fail
– may write stubs so code compiles
3. Implement new functionality
4. Run tests, see them succeed
5. Refactor – clean code rocks!
6. Repeat
Tests First
• Some “easy” examples
– How would you test a function to
calculate the area of a triangle?
– How would you test a function to
calculate the circumference of a circle?
– How would you test a function that
calculates wind chill?
JUnit Framework
• Test Case. Method that runs one or more
tests.
• Test Suite. Collection of tests run at the
same time. Includes one or more test
cases/methods.
• Test Runner. Utility used to run a test
suite.
– In Eclipse, shows a graphical presentation.
– May also be a command-line version (Ruby).
• JUnit framework available from
http://junit.org
Packages in Java
• A package is a way to bundle classes
by functionality
Customers don’t really want to see all your test code!
Create a Package
In Eclipse:
• Create a Java project, click on src
• Select new package icon
• Enter an appropriate name
• For this example, create a package
named windchill*
• Select the package, create a new
class named MyTempConverter
* for distribution, you’d want to ensure unique
http://www.efsavage.com/blog/posts/java_package_conventions/
More on Packages
• The package statement must be the
first line in the file, e.g.:
package windchill;
public class MyTempConverter {
…
}
• Each package has its own directory
(in the file structure)
TDD Example: Windchill
See the formula and chart:
http://www.nws.noaa.gov/om/windchill/index.shtml
Windchill formula:
• 35.74 + 0.6215T - 35.75V (**0.16) + 0.4275TV(**0.16)
The TempConverter class
•
First we create the “failing” methods – so that our tests
will at least compile
package windchill;
public class MyTempConverter {
public static long windChill(int speed, int temperature)
{
return -1;
}
}
Why does it make sense for this method to be static?
Adding the tests
• Create a package for your tests
• Add your test classes to the package
– Select the test package
– Create a new class of type JUnit Test
Case
Eclipse and JUnit
1. Select JUnit Test Case 2. Select JUnit Test Case
We’ll use JUnit 4
Pick a
meaningful
name
click
Eclipse and JUnit continued
Dialog for class to test:
You must type in package
Then you’ll see possible
classes
Eclipse prompts for library
Build Path
If Eclipse creates a JUnit test for you, you’ll be prompted to add library.
To add a library later:
Right-click on project name (in package explorer)
Select Build Path
Select Add Libraries
Select JUnit 4
JUnit v4
• New Java Feature: annotations
• Places a “marker” in code that is
interpreted by another tool
• For JUnit, marker/annotation is
@Test
• No need to extend TestCase (JUnit3)
• Must import org.junit.*;
Some windchill tests
package tests;
import junit.framework.Assert;
import static org.junit.Assert.*;
import Windchill.MyTempConverter;
public class TestWindchillCalcs {
@Test
public void testWindchill()
{
long expected = -11;
long actual = MyTempConverter.windChill(5, 0);
assertEquals(expected, actual);
// local variables not needed
assertEquals(3, MyTempConverter.windChill(10, 15));}
}
The TempConverter class
•
Now add code so that the tests pass
package Windchill;
public class MyTempConverter {
public static long windChill(int speed, int temperature)
{
double newTemp = 35.74 + 0.6215*temperature 35.75 * Math.pow(speed, 0.16) + 0.4275 * temperature
* Math.pow(speed,0.16);
return Math.round(newTemp);
}
public static void main(String[] args) {
System.out.println(MyTempConverter.windChill(10, 30));
}
}
Run as JUnit Test
JUnit Execution
All is well!
Error! Look at failure
trace for explanation
Can also test for expected exceptions
package Windchill;
public class BadInputException extends RuntimeException {
public BadInputException() {}
public BadInputException(String msg){
super(msg);
}
}
@Test (expected = BadInputException.class)
public void testWindchillLowSpeed() throws BadInputException
{
long actual = MyTempConverter.windChill(4, 10);
}
Exception
class
JUnit test
public static long windChill(int speed, int temperature)
{
if (speed < 5)
throw new BadInputException("Windchill not valid if speed < 5");
double newTemp = 35.74 + 0.6215*temperature 35.75 * Math.pow(speed, 0.16) + 0.4275 * temperature * Math.pow(speed,0.16);
return Math.round(newTemp);
}
Discuss with a partner
• When would we need the throws
clause on the test?
• What happens if the (expected)
phrase is removed?
Testing instance methods
• When testing a class that has instance methods
(most often), the test will need to create an
instance
• @BeforeClass and @AfterClass annotated
methods will be run exactly once during your test
run - at the very beginning and end of the test as
a whole, before anything else is run. In fact,
they're run before the test class is even
constructed, which is why they must be declared
static.
• The @Before and @After methods will be run
before and after every test case, so will probably
be run multiple times during a test run
Example to illlustrate @Before
package game;
public class Location {
private int x, y;
public void move(int dx, int dy)
{
x += dx;
y += dy;
}
// Also has constructors and getters
}
Test it – see @Before message
public class TestLocation {
private Location location;
@Before
public void setUp(){
System.out.println("In @Before");
location = new Location();
}
@Test
public void testMove() {
location.move(5, 10);
assertEquals(5, location.getX());
assertEquals(10, location.getY());
}
@Test
public void testMove2() {
location.move(5, 10);
assertEquals(5, location.getX());
assertEquals(10, location.getY());
location.move(5, 10);
assertEquals(10, location.getX());
assertEquals(20, location.getY());
}
}
Example of @BeforeClass
• Assume the board set up is complex, and
you don’t need to reset between tests.
package game;
public class Board {
private String gameStatus;
public void init() {
gameStatus = "Long game set up is done!";
}
public String getGameStatus() {
return gameStatus;
}
}
The @BeforeClass test
import game.Board;
public class TestBoard {
static Board board;
@BeforeClass not run for each test instance
– just the entire test suite – so variables
must be static
@BeforeClass
public static void setUpBeforeClass() throws Exception {
System.out.println("In @BeforeClass");
board = new Board();
board.init();
}
@Test
public void test1() {
assertEquals(board.getGameStatus(), "Long game set up is done!");
}
@Test
public void test2() {
assertEquals(board.getGameStatus(), "Long game set up is done!");
}
}
This is an advanced detail – will not be covered on tests,
BUT may be helpful in your projects
Testing floating points
• Remember that floating point values should not
be compared exactly.
public class MyConverter {
public static double INCHES_TO_METERS = 0.0254;
public static double englishToMeters(int feet, int
inches)
{
return 0;
//int totalInches = feet * 12 + inches;
//return totalInches * INCHES_TO_METERS;
}
}
The test
• Use a tolerance
public class TestConversions {
public static double EPSILON = .0001;
@Test
public void testFeetToMeters()
{
double expected = 0.0508;
double actual = MyConverter.englishToMeters(0, 2);
assertEquals(expected, actual, EPSILON);
expected = 0.3556;
actual = MyConverter.englishToMeters(1, 2);
assertEquals(expected, actual, EPSILON);
}
What tests could you write for…
• A chess game
• A linked list library
• String library
• UPC code
Advanced Topic
• The static import construct allows unqualified
access to static members
import static org.junit.Assert.assertEquals;
@Test
public void testMove3() {
location.move(5, 10);
assertEquals(5, location.getX());
assertEquals(10, location.getY());
}