Transcript Document

Junit Architecture
LiuBing
[email protected]
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 Fowler在面向对象分析设计、UML、模式、软件开发方法学、XP、重
构...方面,都是世界顶级的专家,现为ThoughtWorks首席科学家。Martin
Fowler著有4本经典书籍:“Analysis Patterns : Reusable Object Models”、
“UML Distilled: Applying the Standard Object Modeling Language”、
“Refactoring: Improving the Design of Existing Code”、“Planning 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 Beck先生是软件开发方法学的泰斗,是XP
(Extreme Programming)的创始人,有17年的面
向对象的编程经验。 他倡导软件开发的模式定义,
CRC卡片在软件开发过程中的使用,HotDraw软件的
体系结构,基于xUnit的测试框架,重新评估了在
软件开发过程中测试优先的编程模式。 Kent Beck
是《The Smalltalk Best Practice Patterns》、
《Extreme Programming Explained》和
《Planning Extreme Programming(与Martin
Fowler合著)》的作者,并且承担了XP的领导工作。
他现在是Three Rivers Institute的总裁。TRI从
事于技术和商业接合的应用研究,是是新近成立的
Agile Alliance的创始成员,Agile Alliance的使
命就是要创建更好的软件开发方法。
Developer- Erich Gamma
Erich Gamma is the Technical Director of the Software Technology
Center of ObjectTechnologyInternational (OTI) in Zurich.
Some of his recent projects are:

EclipseIde - IBM's new platform for development tools

JFace - the Eclipse UI Framework

Eclipse Java Tooling

IBM VisualAge MicroEdition? IDE

ULC - Ultra Light Client an infrastructure for thin Java clients.
Erich pairs as often as possible with KentBeck to work on JavaUnit.
He's author number 1 of the GangOfFour and feels more and more guilty
that there isn't a 2nd edition...
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
JUnit Infected: Programmers Love Writing Tests
see Test Infected: Programmers Love
Writing Tests, Java Report, July 1998,
Volume 3, Number 7
IDE







JBuilder
Eclipse
Forte/Netbeans
IntelliJ
TogetherJ
Visual Age
JDeveloper Integration
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
Junit Architecture & Patterns
Goals

What are the goals of JUnit?
Junit Architecture -Microkernel
Microkernel
extensions
FrameWork
Patterns Generate Architectures
The design of JUnit will be presented in a
style first used in (see "Patterns Generate
Architectures", Kent Beck and Ralph Johnson,
ECOOP 94). The idea is to explain the design
of a system by starting with nothing and
applying patterns, one after another, until you
have the architecture of the system. We will
present the architectural problem to be
solved, summarize the pattern that solves it,
and then show how the pattern was applied
to JUnit
Patterns
Getting started- TestCase
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
Blanks to fill in- run()
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
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));
}
}
Don’t care about one or many TestSuite
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
Reporting results- TestResult
Collecting Parameter suggests that 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
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(); }
}
TestCase test= new MoneyTest("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
•Command
•Template method
•Collecting Parameter
•Adapter
•Pluggables Selector
•Composite
•Observer
•Decorator
•MicroKernel (SA Pattern)
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.
Eat your own dog food
As soon as we had the base unit testing
functionality implemented, we applied it
ourselves. A TestTest verifies that the
framework reports the correct results for
errors, successes, and failures. We found this
invaluable as we continued to evolve the
design of the framework. We found that the
most challenging application of JUnit was
testing its own behavior.
Intersection, not union
There is a temptation in framework development to include
every feature you can. After all, you want to make the
framework as valuable as possible. However, there is a
counteracting force- developers have to decide to use your
framework. The fewer features the framework has, the easier it
is to learn, the more likely a developer will use it. JUnit is
written in this style. It implements only those features
absolutely essential to running tests- running suites of tests,
isolating the execution of tests from each other, and running
tests automatically. Sure, we couldn’t resist adding some
features but we were careful to put them into their own
extensions package (test.extensions). A notable member of this
package is a TestDecorator allowing execution of additional code
before and after a test.
Framework writers read their
code

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.