200 likes | 344 Views
JUnit intro. Kalvis Apsitis. What are “Programmer Tests”?. Programmer Testing is the testing performed by a developer with the goal of verifying the correct functioning of his/her own code
E N D
JUnit intro Kalvis Apsitis
What are “Programmer Tests”? • Programmer Testing is the testing performed by a developer with the goal of verifying the correct functioning of his/her own code • Programmer Tests are automated unit tests that exercise program units such as classes and methods , not the complete features or requirements supported by the software as a whole • Important distinction: if it’s not automated, it’s not really programmer testing – that’s exploratory testing.
Safety Net • Having a good test coverage with an automated unit test harness… • prevents a system from becoming legacy(*), and • enables effective refactoring, because it also prevents regression • Metaphor: Mold • Michael Feathers defines “legacy code” as code without tests
Michael Feathers (2004) • I don't care how good you think your design is. If I can't walk in and write a test for an arbitrary method of yours in five minutes its not as good as you think it is, and whether you know it or not, you're paying a price for it: • You think your design is good? Pick a class, any class, and try to instantiate it in a test harness. • Can you make this class work outside the application? • Can you get it to the point where you can tinker with it, in real time, build it alone in less than a second, and add tests to find out what it really does in certain situations.
Available tools for Java • JUnit (www.junit.org) • The de facto Java unit testing framework • Extremely simple, suitable for unit/component testing • Plenty of extensions for J2EE, for example • TestNG (testng.org) • Very close to JUnit but not quite • Can configure test groups/suites in a more flexible way (it is usable for some integration testing)
JUnit Concepts • Test suite: Executable row of several Test cases • Test case: Java class containing test methods • Test fixture: the initial state of a Test Case, consisting of the member variables initialized through a method annotated with @Before. E.g. @Before public void setUp() { … } • Test method: a no-argument method of a TestCase class annotated with @Test (represents a single logical test) For example: @Test public void someMethod() { … } ============ Only things to do in “@Test testA(){...}” methods is business logic + assertions. Test fixture contains everything else needed to run the tests (it is initialized in "@Before setUp(){...}").
TestCase lifecycle @Before public void setUp() {...} gets called once before each test method is executed @After public void tearDown() {...} gets called once after each test method has been executed – regardless of whether the test passed or failed (or threw some other exception). • Each test method gets called exactly once in some order (test methods should be isolated, so the order does not matter). • The same instance of a TestCase may or may not be used for executing the tests – don’t depend on the constructor, use setUp() for setup!
Suite Example import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses(value = {TestCase1.class, TestCase2.class}) public class SomeSuite { // any code; // may contain TestCase1, TestCase2 // as inner classes, if their fixtures have much // in common }
Simple Annotations in a TestCase import org.junit.*; public class TestCase1 { @Before protected void setUp() {super.setUp();} @After protected void tearDown() {super.tearDown();} @Test public void methodOne() { ... } @Test(expected = RuntimeException.class) public void testSomeException() { ... } @Ignore(value = "Test method not ready") @Test public void ignoredTestCase() { ... } }
Simple TestCase (3) import org.junit.*; public class TestCalculator { private Calculator calculator; protected void setUp() { super.setUp(); calculator = new Calculator(); } @Test public void addingPositiveIntegers() { int expected = 5; int actual = calculator.add(2, 3); assertEquals("2 + 3 should be 5", expected, actual); } }
Test anything that could fail import org.junit.*; public class TestCalculator { private Calculator calculator; @Before protected void setUp() { … } @After protected void tearDown() { … } @Test public void addingPositiveIntegers() { … } @Test public void addingNegativeIntegers() { … } @Test public void addingZeroes() { … } @Test public void addingPositiveToNegative() { … } } How many method calls are executed?
Test Granularity • Each unit test should check one specific piece of functionality. Do not combine multiple, unrelated tests into a single testXXX( ) method. • If the first assertEquals( ) in some method fails, the remainder is not executed. You won't know if the other (unrelated) asserts are functional. • One method may contain several asserts, if they are related, i.e. if the failure of one assert would cause failures in other asserts anyway.
Exercise 1:Write a TestCase • Task: Write a JUnit TestCase for the Template class, place it under sem02-demo/src/main/java/exercise1/TemplateTest.java • There’s a bug in Template class. Try to find it!
Simple TestSuite import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses(value = {CalculatorIntegerTest.class, CalculatorFloatingPointTest.class, CalculatorLogarithmsTest.class }) public class CalculatorTestSuite { // Can leave empty }
Exercise 2:Write a TestSuite • Task: • Write a JUnit TestSuite exercise2.AllTests that collects all the TestCase classes from sem02_demo/src/test/java/exercise2.
What if I need to do something expensive in setUp()? • If you need to perform some kind of one-time setup before running the test methods of a TestCase (and can’t/won’t replace the expensive stuff with a mock implementation), use @BeforeClass annotation: public class Example { @BeforeClass public static void onlyOnce() { } @Before public void beforeEachTest() {} @Test public void one() { ... } @Test public void two() { ... } }
Example of @BeforeClass public class InterestCalculatorTestWithTestSetup { private static double interestRate, loanAmount; private static int loanDuration; public static Test suite() { TestSuite suite = new TestSuite(InterestCalculatorTestWithTestSetup.class); TestSetup wrapper = new TestSetup(suite) { public void setUp() throws IOException { ResourceBundle bundle = ResourceBundler.getBundle( InterestCalculatorTestWithTestSetup.class.getName()); inrerestRate = Double.parseDouble(bundle.getString(“interest.rate”)); loanAmount = Double.parseDouble(bundle.getString(“loan.amount”)); loanDuration = Interest.parseInt(bundle.getString(“loan.duration”)); } }; return wrapper; } public void testInterestCalculation() { … } }
Assertions • We already saw one assertion in action, the assertEquals() method • There are a bunch others – take a look at the Javadocs for org.junit.Assert • The most often used assertions –assertEquals(), assertNull(), assertSame(), assertTrue() and their opposites – are enough for most situations and the rest you can easily write on your own. • Assert.fail() is used, if control should not reach that line in the test - this makes the test method to fail.
Testing Expected Exceptions @Test public void method1() { try { Template t = new Template( "My ${f\\oo template" ); fail("Should NOT allow unterminated variables in " + t); } catch (IllegalArgumentException expected) {} } @Test(expected=IllegalArgumentException.class) public void method2() { new Template( "This is my ${f\\oo template" ); } First approach is longer, but allows more precise control, where exactly the test method fails.
What happens when an assertion fails? • The failed assertions create a org.junit.runner.notification.Failure when they fail. • JUnit tags the test as “failure” for later reference. • If a test throws any other exception (e.g. NullPointerException), JUnit again catches these but tags the test as “error” instead of “failure”.