Professional Documents
Culture Documents
LiuBing bliu76@yeah.net
Agenda
Junit Background Usage Junit Junit architecture and design pattrens Conclusion
Junit
JUnit is an open source Java testing framework used to write and run repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks. JUnit features include: * Assertions for testing expected results * Test fixtures for sharing common test data * Test suites for easily organizing and running tests * Graphical and textual test runners JUnit was originally written by Erich Gamma and Kent Beck.
Martin Fowler
Never in the field of software development was so much owed by so many to so few lines of code" Martin Fowler
Martin FowlerUMLXP ...ThoughtWorksMartin Fowler4Analysis Patterns : Reusable Object Models UML Distilled: Applying the Standard Object Modeling Language Refactoring: Improving the Design of Existing CodePlanning Extreme Programming
JavaWorld
* 2002 JavaWorld Editors' Choice Awards (ECA) Best Java Performance Monitoring/Testing Tool * 2001 JavaWorld Editors' Choice Awards (ECA) Best Java Performance Monitoring/Testing Tool
Developer-Kent Beck
Kent Beck
Kent BeckXP Extreme Programming17 CRCHotDraw xUnit Kent Beck The Smalltalk Best Practice Patterns Extreme Programming Explained Planning Extreme ProgrammingMartin FowlerXP Three Rivers InstituteTRI Agile AllianceAgile Alliance
XP
Extreme Programming is a discipline of software development based on values of simplicity, communication, feedback, and courage. It works by bringing the whole team together in the presence of simple practices, with enough feedback to enable the team to see where they are and to tune the practices to their unique situation.
Usage Junit
see Test Infected: Programmers Love Writing Tests, Java Report, July 1998, Volume 3, Number 7
IDE
JB7
test
public class test { public int add(int a,int b){ return a+b; } }
TestCase
public class Testtest extends TestCase { public Testtest(String s) { super(s); } protected void setUp() { System.out.println("setUp"); } protected void tearDown() { System.out.println("tearDown"); } public void testAdd() { test test = new test(); this.assertEquals(12,test.add(9, 1)); } }
Junit Result
Goals
Microkernel
extensions
FrameWork
Patterns
Encapsulate a request as an object, thereby letting you queue or log requests" Command tells us to create an object for an operation and give it a method "execute".
TestCase
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 algorithms structure
TestCase
Here is the template method: public void run() { setUp(); runTest(); tearDown(); } The default implementations of these methods do nothing: protected void runTest() { } protected void setUp() { } protected void tearDown() { } Since setUp and tearDown are intended to be overridden but will be called by the framework we declare them as protected
SubClass TestCase
public class Testtest extends TestCase { public Testtest(String s) { super(s); } protected void setUp() { System.out.println("setUp"); } protected void tearDown() { System.out.println("tearDown"); } public void testAdd() { test test = new test(); this.assertEquals(12,test.add(9, 1)); } }
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly
TestSuite
public class TestSuite implements Test { private Vector fTests= new Vector(10); public void addTest(Test test) { fTests.addElement(test); } public void run(TestResult result) { for (Enumeration e= tests(); e.hasMoreElements(); ) { if (result.shouldStop() ) break; Test test= (Test)e.nextElement(); runTest(test, result); } } }
JB TestSuit
results over several methods, you should add a parameter to the method and pass an object that will collect the results for you.
TestResult
TestCase Run
public void run(TestResult result) { result.startTest(this); setUp(); try { runTest(); } catch (AssertionFailedError e) { //1 result.addFailure(this, e); } catch (Throwable e) { // 2 result.addError(this, e); } finally { tearDown(); } }
Extend TestResult
JUnit comes with different implementations of TestResult. The default implementation counts the number of failures and errors and collects the results. TextTestResult collects the results and presents them in a textual form. Finally, UITestResult is used by the graphical version of the JUnit Test Runner to update the graphical test status. TestResult is an extension point of the framework. Clients can define their own custom TestResult classes, for example, an HTMLTestResult reports the results as an HTML document.
AssertionFailedError
An AssertionFailedError is triggered by the assert method provided by TestCase. JUnit provides a set of assert methods for different purposes. Here is the simplest one: protected void assert(boolean condition) { if (!condition) throw new AssertionFailedError(); } The AssertionFailedError is not meant to be caught by the client (a testing method inside a TestCase) but inside the Template Method TestCase.run(). We therefore derive AssertionFailedError from Error. public class AssertionFailedError extends Error { public AssertionFailedError () {} } The methods to collect the errors in TestResult are shown below: public synchronized void addError(Test test, Throwable t) { fErrors.addElement(new TestFailure(test, t)); } public synchronized void addFailure(Test test, Throwable t) { fFailures.addElement(new TestFailure(test, t)); }
AssertionFailedError
Observer
TestResult
public class TestResult extends Object { protected Vector fFailures; protected Vector fErrors; protected Vector fListeners; public synchronized void addError(Test test, Throwable t) { fErrors.addElement(new TestFailure(test, t)); for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { ((TestListener)e.nextElement()).addError(test, t); } } public synchronized void addFailure(Test test, AssertionFailedError t) { fFailures.addElement(new TestFailure(test, t)); for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { ((TestListener)e.nextElement()).addFailure(test, t); } } public synchronized void addListener(TestListener listener) { fListeners.addElement(listener); } public synchronized void removeListener(TestListener listener) { fListeners.removeElement(listener); } }
TestListener
public interface TestListener { /** * An error occurred. */ public void addError(Test test, Throwable t); /** * A failure occurred. */ public void addFailure(Test test, AssertionFailedError t); /** * A test ended. */ public void endTest(Test test); /** * A test started. */ public void startTest(Test test); }
TestRunner
public class TestRunner extends BaseTestRunner { public synchronized void addError(Test test, Throwable t) { writer().print("E"); } public synchronized void addFailure(Test test, AssertionFailedError t) { writer().print("F"); } public synchronized void startTest(Test test) { writer().print("."); if (fColumn++ >= 40) { writer().println(); fColumn= 0; } } public void endTest(Test test) { } }
Adapter
Convert the interface of a class into another interface clients expect
Code
public class TestMoneyEquals extends MoneyTest { public TestMoneyEquals(){ super("testMoneyEquals"); } protected void runTest () { testMoneyEquals(); } }
Decorator
Test
TestCase
TestDecorator
TestSetup
RepeatedTest
TestDecorator
public class TestDecorator extends Assert implements Test { protected Test fTest; public TestDecorator(Test test) { fTest= test; } /** * The basic run behaviour. */ public void basicRun(TestResult result) { fTest.run(result); } public int countTestCases() { return fTest.countTestCases(); } public void run(TestResult result) { basicRun(result); } }
TestSetup
public class TestSetup extends TestDecorator { public TestSetup(Test test) { super(test); } public void run(final TestResult result) { Protectable p= new Protectable() { public void protect() throws Exception { setUp(); basicRun(result); tearDown(); } }; result.runProtected(this, p); } }
Summary
Conclusion
Patterns
We found discussing the design in terms of patterns to be invaluable, both as we were developing the framework and as we try to explain it to others. You are now in a perfect position to judge whether describing a framework with patterns is effective. If you liked the discussion above, try the same style of presentation for your own system.
Pattern density
There is a high pattern "density" around TestCase, which is the key abstraction of JUnit. Designs with high pattern density are easier to use but harder to change. We have found that such a high pattern density around key abstractions is common for mature frameworks. The opposite should be true of immature frameworks - they should have low pattern density. Once you discover what problem you are really solving, then you can begin to "compress" the solution, leading to a denser and denser field of patterns where they provide leverage.
We spent far more time reading the JUnit code than we spent writing it, and nearly as much time removing duplicate functionality as we spent adding new functionality. We experimented aggressively with the design, adding new classes and moving responsibility around in as many different ways as we could imagine. We were rewarded (and are still being rewarded) for our monomania by a continuous flow of insights into JUnit, testing, object design, framework development, and opportunities for further articles.