Programmer Testing Testing all things Java using JUnit and

Download Report

Transcript Programmer Testing Testing all things Java using JUnit and

Programmer Testing
Testing 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.8
Java 1.4
public void
setUp() { ... }
public void
public void
testSomething() tearDown() { ...
{ ... }
}
JUnit 4.1
Java 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 Objects
public 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 and
dynamic 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
}
}