29
JUnit The framework

JUnit The framework. Goal of the presentation showing the design and construction of JUnit, a piece of software with proven value

Embed Size (px)

Citation preview

JUnit

The framework

Goal of the presentation

showing the design and construction of JUnit,

a piece of software with proven value

Goals of JUnit to write a framework

– within which developers will actually write tests

The framework has to

– use familiar tools, so there is little new to learn.

– require no more work than absolutely necessary to write a new test.

– eliminate duplicated effort.

Goals of JUnit Creation of tests that retain their value over time.

– Someone other than the original author has to be able • to execute the tests • and interpret the results.

– It should be possible to • combine tests from various authors • and run them together without fear of interference

leverage existing tests to create new ones.

– Creating a setup or fixture is expensive and a framework has to

• enable reusing fixtures to run different tests

TestCase applies Command

public abstract class TestCase implements Test {public abstract void run();

}

Issues– an object (TestCase) to represent the basic concept

– creating tests that retain their value over time

– making test writing more inviting • object developers are used to developing with objects

TestCase applies Command

public abstract class TestCase implements Test {public abstract void run();

}

Command Pattern– "Encapsulate a request as an object,

thereby letting you… queue or log requests…"

– create an object for an operation – give the object a method "execute".

TestCase.run() applies Template Method

Issues

– giving the developer a convenient "place" to put • their fixture code • and their test code

– common structure to all tests• set up a test fixture, • run some code against the fixture,• check some results, • clean up the fixture

TestCase.run() applies Template Method

Template Method– "Define the skeleton of an algorithm in an operation, deferring some

steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure."

public void run() {     setUp();     runTest();     tearDown(); }

// default implementations of these methods do nothing: protected void runTest() { } protected void setUp() { } protected void tearDown() { }

TestResult applies Collecting Parameter

Issues– After the test has run,

need for a record, a summary of what did and didn’t work

Collecting Parameter

– when you need to collect results over several methods, you should add a parameter to the method and pass an object that will collect the results for you.

– TestResult : Reporting results

Failures and errors Failure

– anticipated and checked for with assertions. – signaled with an AssertionFailedError exception

Errors – unanticipated problems like an ArrayIndexOutOfBoundsException.

public void run(TestResult result) {     result.startTest(this);     setUp();     try {         runTest();     }     catch (AssertionFailedError e) {result.addFailure(this, e);     }     catch (Throwable e) {result.addError(this, e);     }     finally {         tearDown();     } }

Failures and errors

The canonical form of collecting parameter requires us to pass the collecting parameter to each method.

– each of the testing methods would require a parameter for the TestResult.

– a "pollution" of these method signatures.

– as a benevolent side effect of using exceptions to signal failures we can avoid this signature pollution.

TestCase applies either Adapter with an anonymous inner class or Pluggable Selector

Issues– To avoid the unnecessary proliferation of classes

• All test cases are implemented as different methods in the same class. • A given test case class may implement many different methods,

each defining a single test case. • Each test case has a descriptive name

like testMoneyEquals or testMoneyAdd.

– The test cases don’t conform to a simple command interface. • Different instances of the same Command class need to be invoked

with different methods.

– The problem is to make all the test cases look the same from the point of view of the invoker of the test

TestCase applies either Adapter with an anonymous inner class or Pluggable Selector

Adapter– "Convert the interface of a class into another interface clients expect".

– a class adapter, which uses subclassing to adapt the interface • implement a subclass for each test case

// explicit subclasspublic class TestMoneyEquals extends MoneyTest {     public testMoneyEquals() {

super("testMoneyEquals"); }

    protected void runTest () { testMoneyEquals(); } }

//anonymous inner classTestCase test= new MoneyTest("testMoneyEquals ") {

    protected void runTest() { testMoneyEquals(); } };

TestCase applies either Adapter with an anonymous inner class or Pluggable Selector

PluggableSelector– use a single class which can be parameterized to perform different logic without requiring

subclassing • stores a method selector in an instance variable • the Java reflection API allows us to invoke a method from a string representing the method’s

name

• the pluggable selector is the default implementation of the runTest method.

protected void runTest() throws Throwable {     Method runMethod= null;     try {         runMethod= getClass().getMethod( fName,

new Class[0]);     } catch (NoSuchMethodException e) {         assert("Method \""+fName+"\" not found", false); }     try {         runMethod.invoke(this, new Class[0]);   }     // catch InvocationTargetException and IllegalAccessException }

TestSuite applies Composite

Issues– to get confidence in the state of a system

need to run many tests

– to support suites of suites of suites of tests

Structural integrity tests : - this type of test is usually performed on a single unit of the software at a time - test procedures written to exercise all the important elements of the code- this may include exercising all the paths in the module

TestSuite applies Composite

Composite– "Compose objects into tree structures to represent part-whole hierarchies.

Composite lets clients treat individual objects and compositions of objects uniformly."

Participants– Component: declares the interface to use to interact with tests. – Composite: implements this interface and maintains a collection of tests. – Leaf: represents a test case in a composition that conforms to the

Component interface.

TestSuite applies Composite

Participants– Component: declares the interface to use to interact with tests.

public interface Test {     public abstract void run(TestResult result); }

TestSuite applies Composite

Participants– Composite: implements this interface and maintains a collection of tests.

public class TestSuite implements Test {     private Vector fTests= new Vector(); //children

// delegate to childrenpublic void run(TestResult result) {

    for (Enumeration e= fTests.elements(); e.hasMoreElements(); ){         Test test= (Test)e.nextElement();         test.run(result);     } }

// add test to a test suitepublic void addTest(Test test) {

    fTests.addElement(test); }

}

TestSuite applies Composite

Participants– Leaf: represents a test case in a composition that conforms to the Component

interface.

// JUnit v 3.2public abstract class TestCase implements Test {

public void run(TestResult result) {result.run(this);}

public void runBare() throws Throwable {setUp();try { runTest();}finally {tearDown();}

}

Creating a test suite Static specification of a test suite

public static Test suite() {     TestSuite suite= new TestSuite();     suite.addTest(new MoneyTest("testMoneyEquals"));     suite.addTest(new MoneyTest("testSimpleAdd")); }

Dynamic extraction of the test methods and creation of a suite containing them.

– convention • methods that start with the prefix "test" • take no arguments.

– constructing the test objects by using reflection.

public static Test suite() {     return new TestSuite(MoneyTest.class); }

/*** Constructs a TestSuite from the given class. * Adds all the methods starting with "test" as test cases. */public TestSuite(final Class theClass) {

fName= theClass.getName();

Constructor constructor= getConstructor(theClass);if (!Modifier.isPublic(theClass.getModifiers())) { addTest(warning("Class "+theClass.getName()+" is not public")); return;}if (constructor == null) { addTest(warning("Class "+theClass.getName()

+" has no public constructor TestCase(String name)"));return;}

Class superClass= theClass;Vector names= new Vector();while (Test.class.isAssignableFrom(superClass)) {

Method[] methods= superClass.getDeclaredMethods(); for (int i= 0; i < methods.length; i++) { addTestMethod(methods[i], names, constructor);} superClass= superClass.getSuperclass();}

if (fTests.size() == 0) addTest(warning("No tests found in "+theClass.getName()));}

Summary

References

Erich Gamma, Kent Beck, JUnit A Cook's Tour– http://junit.sourceforge.net/doc/cookstour/cookstour.htm

JUnit Primer– http://www.clarkware.com/articles/JUnitPrimer.html

Refactorisation de TestCase… Failure

– anticipated and checked for with assertions. – signaled with an AssertionFailedError error

Errors – unanticipated problems like an ArrayIndexOutOfBoundsException.

public void run(TestResult result) {     result.startTest(this);     setUp();     try {         runTest();     }     catch (AssertionFailedError e) {result.addFailure(this, e);     }     catch (Throwable e) {result.addError(this, e);     }     finally {         tearDown();     } }

TestResults

Protectable

TestResults

TestCase