23
Programmer Testing Testing all things Java using JUnit and extensions

Programmer Testing Testing all things Java using JUnit and extensions

Embed Size (px)

Citation preview

Programmer TestingTesting all things Java using JUnit and extensions

The Big Why

• “If you don’t have tests, how do you know your code is doing the thing right and doing the right thing?”

What are “Programmer Tests”?• Programmer Testing is the testing performed

by a developer with the goal of verifying the correct functioning of her 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

Available tools for Java

• JUnit– The de facto Java unit testing framework

– Extremely simple

– Plenty of extensions for J2EE, for example

• TestNG– Very close to JUnit but not quite

– Employs @metadata instead of naming conventions

JUnit

• What is the difference between a framework and a library?

• Is JUnit (www.junit.org) a framework or a library?

JUnit 101• Essential concepts:

– Test suite: Java class that extends junit.framework.TestSuite or implements a static method named suite(), returning a TestSuite

– Test case: Java class extending junit.framework.TestCase

– Test fixture: the initial state of a Test Case, consisting of the member variables initialized through a method named setUp()

– Test method: a method of a TestCase class of which signature looks like “public void testFoo()”, representing a single logical test

TestCase lifecycle

• setUp() gets called once before each test method is executed

• 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 testSomething() method gets called exactly once – in an arbitrary order!

• 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!

Java 1.5 annotations

JUnit 3.8Java 1.4

public void setUp() { ... }

public void testSomething() { ... }

public void tearDown() { ... }

JUnit 4.1Java 1.5

@Before

someSetUp() { ... }

@Test

public void something() { ... }

@After

public void someTearDown() { ... }

What else is new in JUnit 4.1?

• Parametrized tests - same testcase is repeated for various argument sets.

• Annotated way to write test suites and to filter out testcases which (not) to execute

JUnit 101:

Simple TestCase (1)import junit.framework.*;

public class TestCalculator extends TestCase {

protected void setUp() { super.setUp(); }

protected void tearDown() { super.tearDown(); }}

JUnit 101:

Simple TestCase (2)import junit.framework.*;

public class TestCalculator extends TestCase {

private Calculator calculator;

protected void setUp() { super.setUp(); calculator = new Calculator(); }

}

JUnit 101:

Simple TestCase (3)import junit.framework.*;

public class TestCalculator extends TestCase {

private Calculator calculator;

public void testAddingPositiveIntegers() { int expected = 5; int actual = calculator.add(2, 3); assertEquals(“2 + 3 should be 5”, expected, actual); }}

JUnit 101:

Simple TestCase (4)import junit.framework.*;

public class TestCalculator extends TestCase {

private Calculator calculator; protected void setUp() { … } protected void tearDown() { … }

public void testAddingPositiveIntegers() { … } public void testAddingNegativeIntegers() { … } public void testAddingZeroes() { … } public void testAddingPositiveToNegative() { … }

}

JUnit 101:

Simple TestSuite (1)import junit.framework.*;

public class CalculatorTestSuite extends TestCase {

public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest(CalculatorIntegerTest.class); suite.addTest(CalculatorFloatingPointTest.class); suite.addTest(CalculatorLogarithmsTest.class); return suite; }}

Assertions

• We already saw one assertion in action, the assertEquals() method

• There are a bunch others – take a look at the Javadocs for junit.framework.TestCase at http://www.junit.org/junit/javadoc/3.8.1/

• The most often used assertions – assertEquals(), assertEquals(), assertNull(), assertSame() and their opposites – are enough for most situations and the rest you can easily write on your own.

What happens when an assertion fails?

• The assertions throw a junit.framework.AssertionFailedError when they fail.

• JUnit’s TestRunner catches these (runtime)exceptions and 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”.

Mock Objects

• At some point, you will face a problem where the class/method you should be testing needs to collaborate with an object that is either difficult to create/obtain or simply sooo slooow that your test takes forever to execute (even a 100ms adds up when you’ve got thousands of tests…).

• The solution for this kind of problems is often to use a mock object.

What are Mock Objects?

• A mock object is an object that claims to implement an interface but doesn’t really.

• There are variations on what the “doesn’t really” part actually means (these variations are called “fake”, “stub” and “mock”) but for now, we’ll just call them all mock objects.

• Since there’s nothing like a good example…

Example: Mock Objectspublic class Item { public float getPriceAfterDiscounts(PricingService ps,

DiscountService ds) { float basePrice = ps.getPriceFor(this); float discountedPrice = ds.applyDiscounts(this, basePrice); return discountedPrice; }}

public interface PricingService { float getPriceFor(Item item);}

public interface DiscountService { float applyDiscounts(Item item, float basePrice);}

How to test that without the real PricingService & DiscountService?

public class TestItemUsingMockObjects extends TestCase { private Item item; private PricingService pricingService; private DiscountService discountService;

protected void setUp() { item = new Item(); pricingService = new PricingService() { public float getPriceFor(Item item) { return 12.34f; } }; discountService = new DiscountService() { public float applyDiscounts(Item item, float basePrice) { … } }; }

public void testCalculateDiscountsOnBasePrice() { assertEquals(10.95f, item.getPriceAfterDiscounts(pricingService, discountService); }}

Static vs. Dynamic Mocks

• That example used “static” mock objects – the other school of mock objects is “dynamic” mock objects

• Static mock objects fake their own behavior while dynamic mock objects also verify that the class under test collaborated with the mock objects as expected

• Let’s see an example to illustrate this behavior-oriented approach to using mock objects (we’ll use the EasyMock framework but there are some alternatives)

Using EasyMock anddynamic mock objects

public class TestItemUsingMockObjects extends TestCase { private Item item; private MockControl pricingControl, discountControl; private PricingService pricingService; private DiscountService discountService;

protected void setUp() { item = new Item(); pricingControl = MockControl.createControl(PricingService.class); // obtain a “remote control” pricingService = (PricingService) pricingControl.getMock(); // let EasyMock create the mock

object pricingService.getPriceFor(item); // specify expected method call to our mock PricingService pricingControl.setReturnValue(12.34f); // fake the behavior by setting return value pricingControl.replay(); // switch from recording mode to replay mode // similar setup for DiscountService... }

public void testCalculateDiscountsOnBasePrice() { assertEquals(10.95f, item.getPriceAfterDiscounts(pricingService, discountService); pricingControl.verify(); // verify expected interactions actually happened discountControl.verify(); // verify expected interactions actually happened }}