Unit Testing
Topics Motivation JUnit framework Basic JUnit tests – static methods Ensure exceptions – 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
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
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: Windchill formula: T V (**0.16) TV(**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 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 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 = *temperature * Math.pow(speed, 0.16) * 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); (expected = BadInputException.class) public void testWindchillLowSpeed() throws BadInputException { long actual = MyTempConverter.windChill(4, 10); } public static long windChill(int speed, int temperature) { if (speed < 5) throw new BadInputException("Windchill not valid if speed < 5"); double newTemp = *temperature * Math.pow(speed, 0.16) * temperature * Math.pow(speed,0.16); return Math.round(newTemp); } Exception class JUnit test
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 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. methods will be run before and after every test case, so will probably be run multiple times during a test run
Example to 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 – message public class TestLocation { private Location public void setUp(){ location = new Location(); public void testMove() { location.move(5, 10); assertEquals(5, location.getX()); assertEquals(10, location.getY()); 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 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; }
test import game.Board; public class TestBoard { static Board public static void setUpBeforeClass() throws Exception { board = new Board(); board.init(); public void test1() { assertEquals(board.getGameStatus(), "Long game set up is done!"); public void test2() { assertEquals(board.getGameStatus(), "Long game set up is done!"); not run for each test instance – just the entire test suite – so variables must be static 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 = ; 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 public void testFeetToMeters() { double expected = ; double actual = MyConverter.englishToMeters(0, 2); assertEquals(expected, actual, EPSILON); expected = ; 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 public void testMove3() { location.move(5, 10); assertEquals(5, location.getX()); assertEquals(10, location.getY()); }