- 浏览: 897028 次
- 性别:
- 来自: 大连
文章分类
- 全部博客 (319)
- Thinking / 反思 (27)
- 我读的技术类图书 (3)
- 我读的非技术图书 (3)
- Java & Groovy (55)
- Ruby/Rails (9)
- Python (10)
- C/C++ (14)
- C# & .net (9)
- 互联网相关技术 (6)
- Database (6)
- Unix/Linux (6)
- WindowsDev (21)
- 工具使用 / Tips (62)
- 编程技术杂谈/咨讯 (6)
- 软工 / 敏捷 / 模式 (6)
- 易筋经 / 各种内功 (3)
- 充电 / 他学科知识 (6)
- 外语学习 (16)
- 我和宝宝的甜蜜生活 (24)
- 八卦 (3)
- 健康 (0)
- 无类别 (0)
- mTogether (4)
- 一页纸 (3)
- SAP (7)
- baby (2)
- abap (2)
- temp (1)
- network (1)
- 生活 (1)
最新评论
-
daliang1215:
收藏一下,好东西。 xp 的快捷键用的非常爽,到win7缺没有 ...
Windows7: 右键任务栏上的一个窗口, 用快捷键c关闭它 -
Alice南京:
感谢
Java GC 监视方法与工具 -
wjason:
今天在excel 2010上面写了一些代码,果然lookup有 ...
Excel 公式: 根据一个单元格的用户输入值, 自动设置另一个单元格的值 -
wjason:
因式分解:http://zh.wikipedia.org/wi ...
教孩子学编程: 数学题1 -
bbls:
不错 找了好久了
VS2010: 在Solution Explorer中,自动关联当前正在编辑的文件
最近要做一个关于设计模式的介绍。 我认为这个 J U nit A Cook's Tour 是一个很好的教程。
而且还能借此机会,把TDD也给一并讲了,一讲两得。
但我发现这篇文章的中译本都不易于阅读。
于是便拿到JavaEye上,对其重新排版。转载如下。
注:
本来是想存在草稿,但既然误发表,建议JavaEye修改保存草稿的按钮顺序。
第一次写草稿的时候,右边的按钮时保存,可当第二次编辑这个草稿的时候,右边的按钮就变成了发布。
我已经不止n此的犯同样的错误了。。。本来是想简单排版,因为这个误操作就不得不多用点心了。。。
JUnit
的框架设计及其使用的模式
翻译:胡拥军
hu.yong.jun@ctgpc.com.cn
〔有所添加〕
1
原文:
JUnit A
Cook's Tour
见
www.junit.org 1
1
、介绍
2
2
、目标
2
3
、
JUnit
设计
3
3.1
、从测试用例
TestCase
开始
3
3.2
、在
run()
方法中填写方法体
4
3.3
、用
TestResult
对象报告结果
5
3.4
、
No stupid
subclasses - TestCase again 8
3.5
、不用担心是一个测试用例还是许多测试用例-
TestSuite 10
3.6
、概要
12
4
、结论
14
1
、介绍
在较早的文章(
Test Infected: Programmers Love Writing Tests
)中,我们描述了如何用一个简单
的框架编写可重复的测试;本文则说明这个框架是如何构造的。
仔细地学习
JUnit
框架,从中可以看出我们是如何设计这个框架的。我们看到不同层次的
JUnit
教程,
但在本文中,我们希望更清楚地说明问题。弄清
JUnit
的设计思路是非常有价值的。
我们先讨论一下
Junit
的目标,这些目标会在
JUnit
的每个细小之处得到体现。围绕着
JUnit
的目标,我
们给出
Junit
框架的设计和实现。我们会用模式和程序实现例子来描述这个设计。我们还会看到,在开发这
个框架时,当然还有其它的可选途径。
2
、目标
什么是
JUnit
的目标?
首先,我们回到开发的前提假设。我们假设如果一个程序不能自动测试,那么它就不会工作。但有更
多的假设认为,如果开发人员保证程序能工作,那么它就会永远正常工作,与与这个假设相比,我们的假
设实在是太保守了。
从这个观点出发,开发人员编写了代码,并进行了调试,还不能说他的工作完成了,他必须编写测试
脚本,证明程序工作正常。然而,每个人都很忙,没有时间去进行测试工作。他们会说,我编写程序代码
的时间都很紧,那有时间去写测试代码呢?
因此,首要的目标就是,构建一个测试框架,在这个框架里,开发人员能编写测试代码。框架要使用
熟悉的工具,无需花很多精力就可以掌握。它还要消除不必要的代码,除了必须的测试代码外,消除重复
劳动。
如果仅仅这些是测试要作的,那么你在调试器中写一个表达式就可以实现。但是,测试不仅仅这些。
虽然你的程序工作很好,但这不够,因为你不能保证集成后的即使一分钟内你的程序是否还会正常,你更
不能保证
5
年内它还是否正常,那时你已经离开很久了。
因此,测试的第二个目标就是创建测试,并能保留这些测试,将来它们也是有价值的,其它的人可以
执行这些测试,并验证测试结果。有可能的话,还要把不同人的测试收集在一起,一起执行,且不用担心
它们之间互相干扰。
最后,还要能用已有的测试创建新的测试。每次创建新的测试设置或测试钳(
test fixture
)是很花
费代价的,框架能复用测试设置,执行不同的测试。
3
、
JUnit
设计
最早,
JUnit
的设计思路源于
"
用模式生成架构(
Patterns Generate
Architectures
)
"
一文。它的思
想就是,从
0
开始设计一个系统,一个一个地应用模式,直到最后构造出这个系统的架构,这样就完成一个
系统的设计。我们马上提出要解决的架构问题,用模式来解决这个问题,并说明如何在
JUnit
中应用这些模
式的。
3.1
、从测试用例
TestCase
开始
首先我们创建一个对象来表示基础概念:测试用例(
TestCase
)。
测试用例常常就存在于开发人员的
头脑中,他们用不同的方式实现测试用例:
・
打印语句
・
调试表达式
・
测试脚本
如何我们想很容易地操纵测试,那么就必须把测试作为对象。
开发人员脑海中的测试是模糊的,测试作
为对象,就使得测试更具体了,测试就可以长久保留以便将来有用,这是测试框架的目标之一。同时,对
象开发人员习惯于对象,因此把测试作为对象就能达到让编写测试代码更具吸引力的目的。
在这里,命令模式(
command
)满足我们的需要。该模式把请求封装成对象,即为请求操作生成一个对
象,这个对象中有一个
“
执行(
execute
)
”
方法。命令模式中,请求者不是直接调用命令执行者,而是通
过一个命令对象去调用执行者,具体说,先为命令请求生成一个命令对象,然后动态地在这个命令对象中
设置命令执行者,最后用命令对象的
execute
方法调用命令执行者。这是
TestCase
类定义代码:〔此处译者
有添加〕
public abstract class TestCase implements Test { ... }
因为我们希望通过继承复用这个类,我门把它定义成
“public abstract”
。现在我们先不管它实现
Test
接口,在此时的设计里,你只要把
TestCase
看成是一个单个的类就行了。
每个
TestCase
有一个名字属性,当测试出现故障时,可以用它来识别是哪个测试用例。
public abstract class TestCase implements Test { private final String fName; public TestCase(String name) { fName= name; } public abstract void run(); … }
为了说明
JUnit
的演化进程,我们用图来表示各个设计阶段的架构。我们用简单的符号,灰色路标符号
表明所使用的模式。当这个类在模式中的角色很明显时,就在路标中只指明模式名称;如果这个类在模式
中的角色不清晰,则在路标中还注明该类对应的参与模式。这个路标符号避免了混乱,见图
1
所示。
图
1 TestCase
类应用了命令模式
3.2
、在
run()
方法中填写方法体
下面要解决的问题就是给出一个方便的地方,让开发人员放置测试用的设置代码和测试代码。
TestCase
定义为抽象的,表示开发人员要继承
TestCase
来创建自己的测试用例。如果我们象刚才那样,只
在
TestCase
中放置一个变量,没有任何方法,那么第一个目标,即易于编写测试代这个目标就难以达到。
对于所有的测试,有一个通用的结构,在这个结构中,可以设置测试钳夹(
fixture
),在测试钳夹下
运行一些代码,检查运行结果,然后清除测试钳夹。这表明,每个测试都运行在不同的钳夹下,一个测试
的结果不会影响其它的测试结果,这点符合测试框架的价值最大化的目标。
模板方法(
template method
)模式很好地解决了上面提出的问题。模板方法模式的意图就是,在父类
中定义一个算法的操作的骨架,将具体的步骤推迟到子类中实现。模板方法在子类中重新定义一个算法的
特定步骤,不用改变这个算法的结构,这正好是我们的要求。我们只要求开发人员知道如何编写
fixture
(即
setup
和
teardown
)代码,知道如何编写测试代码。
fixtue
代码和测试代码的执行顺序对所有的测试都
是一样的,不管
fixture
代码和测试代码是如何编写的。
这就是我们需要的模板方法:
public void run() { setUp(); runTest(); tearDown(); }
这个模板方法的默认实现就是什么也不作。
protected void runTest() { } protected void setUp() { } protected void tearDown() { }
既然
setUp
和
tearDown
方法要能被覆写,同时还要能被框架调用,因此定义成保护的。这个阶段的设计
如图
2
所示。
图
2
TestCase.run()
方法应用了模板方法模式
3.3
、用
TestResult
对象报告结果
如果一个
TestCase
在原始森林中运行,大概没人关心它的测试结果。你运行测试是要得到一个测试记
录,说明测试作了什么,什么没有作。
如果一个测试成功和失败的机会是相同的,或者我们只运行一个测试,那么我们只用在测试中设置一
个标志,当测试结束后检查这个标志即可。然而,测试成功和失败机会是不均衡的,测试通常是成功的,
因此我们只注重于测试故障的记录,对于成功的记录我们只做一个总概。
在
SmallTalk Best Practice Patterns
中,有一个叫
“
收集参数(
collecting
parameter
)
”
的模式,
当你需要在多个方法中收集结果时,你可以传给方法一个参数或对象,用这个对象收集这些方法的执行结
果。我们创建一个新对象,测试结果(
TestResult
),去收集测试的结果。
public class TestResult extends Object { protected int fRunTests; public TestResult() { fRunTests= 0; } }
这里一个简单的
TestResult
版本,它只是计数测试运行的数量。为了使用
TestResult
,我们必须把它
作为参数传给
TestCase.run()
方法,并通知
TestResult
当前测试已经开始。
public void run(TestResult result) { result.startTest(this); //通知TestResult测试开始 setUp(); runTest(); tearDown(); }
TestResult
会跟踪计数运行了多少个测试:
public synchronized void startTest(Test test) { fRunTests++; }
我们把
TestREsult
中的
startTest
方法定义成同步的,即线程安全的,那么一个
TestREsult
对象就可以
收集不同线程中的测试的结果。我们想让
TestCase
的接口保持简单,因此我们创建了一个无参数版本的
run()
方法,它创建自己的
TestResult
对象。
public TestResult run() { TestResult result= createResult(); run(result); return result; } protected TestResult createResult() { return new TestResult(); }
这里用到的设计如图
3
所示。
图
3
:
TestResult
应用了收集参数模式
如果测试一直都是运行正确的,那么我们就不用写测试了。我们对测试的故障感兴趣,特别是那些我
们未预料到的故障。当然,我们可以期望故障以我们所希望的方式出现,例如计算得出一个不正确的结
果,或者一个更奇特的故障方式,例如编写一个数组越界错误。不管测试如何出现故障,我们还要能继续
进行其后的测试。
JUnit
在故障(
failure
)和错误(
error
)之间作了区分。故障是可预期的,用断言来检测,错误是
不可预期的,如数组越界例外(
ArrayIndexOutOfBoundsException
)。故障标识为
AssertionFailedError
错误。为了从故障中区分不可预料的错误,故障用第一个
catch
语句捕获,故障之外的错误用第二个
catch
语句捕获,这样就保证了本测试之后的其它测试得以运行。
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(); } }
AssertionFailedError
故障是由
TestCase
提供的
assert
方法触发的。
JUnit
为不同的用途提供了许多
assert
方法,这里有一个简单的例子:
protected void assert(boolean condition) { if (!condition) throw new AssertionFailedError(); }
AssertionFailedError
故障不是由测试客户(测试的请求者,即
TestCase
中的测试方法)捕获的,而
是在模板方法
TestCase.run()
内捕获的。
AssertionFailedError
继承自
Error
。
public class AssertionFailedError extends Error { public AssertionFailedError () {} }
在
TestResult
中收集错误的方法如下:
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)); }
在框架中,
TestFailure
是一个内部帮助类,它将不成功的测试以及其运行中发生的例外对应起来,以
备将来报告。
public class TestFailure extends Object { protected Test fFailedTest; protected Throwable fThrownException; }
收集参数要求把它传递给每一个方法。如果我们这样作,每个测试方法需要有一个
TestResult
作为参
数,这会导致测试方法的签名型构受到破坏;利用例外,我们可以避免签名型构受到破坏,这也是对例外
的副作用的一个利用吧。测试用例方法,或者测试用例调用的帮助方法抛出例外来,它不用知道
TestResult
的信息。
MoneyTestSuite
中的测试方法就可以作为例子,它表明测试方法不用知道
TestResult
的任何信息。
public void testMoneyEquals() { assert(!f12CHF.equals(null)); assertEquals(f12CHF, f12CHF); assertEquals(f12CHF, new Money(12, "CHF")); assert(!f12CHF.equals(f14CHF)); }
JUnit
中有很多不同用途的
TestResult
实现,默认的实现很简单,它计数发生故障和错误的数量,并收
集结果。
TextTestResult
用文本的表现方式表示收集到的结果,而
JUnit
测试运行器利用
UITestResult
,用
图形界面的方式表示收集的结果。
TestResult
是
JUnit
框架的扩展点。客户可以定义它们自己的
TestResult
类,比如,定义一个
HTMLTestResult
类,用
HTML
文档的形式报告测试结果。
3.4
、
No stupid
subclasses - TestCase again
我们应用命令模式来表示一个测试。命令执行依赖一个这样的方法:
execute()
,在
TestCase
称为
run()
,通过它使命令得到调用,这使得我们能用这个相同的接口实现不同的命令。
我们需要一个普遍的接口来运行我们的测试。然而所有的测试用例可能是在一个类中用不同的方法实
现的,这样可以避免为每一种测试方法创建一个类,从而导致类的数量急剧增长。某个复杂测试用例类也
许实现许多不同的测试方法,每个测试方法定义了一个简单测试用例。每个简单测试用例方法有象这样的
名字:
testMoneyequals
或
testMoneyAdd
,测试用例并不需要遵守那个简单的命令模式接口,同一个
Command
类的不同实例可以调用不同的测试方法。因此,下一个问题就是,在测试客户(测试的调用者)的
眼里,要让所有的测试用例看起来是一样的。
回顾一下,这个问题被设计模式解决了,我们想到了
Adapter
模式。
Adapter
模式的意图就是,将一个
已经存在的接口转变为客户所需要的接口。这符合我们的需要,
Adapter
有几种不同的方式做到这一点。一
个方式就是类适配(
class
adapter
),就是用子类来适配接口,具体说就是,用一个子类来继承已有的
类,用已有类中的方法来构造客户所需要的新的方法。例如,要将
testMoneyequals
适配为
runTest
,我们
继承
MoneyTest
类,覆写
runTest
方法,这个方法调用
testMoneyEquals
方法。
public class TestMoneyEquals extends MoneyTest { public TestMoneyEquals() { super("testMoneyEquals"); } protected void runTest () { testMoneyEquals(); } }
使用子类适配的方式要求为每个测试用例实现一个子类,这增加了测试者的负担。
JUnit
框架的一个目
标就是,在增加一个用例时尽量保持简单。另外,为每个测试方法创建一个子类也会导致类膨胀,如果有
许多类,这些类中就那么一个方法,这是不值得的,为它们取有意义的名字都很困难。
Java
提供了匿名内隐类机制,解决了命名问题。我们用匿名内隐类来达到
Adapter
目的,且不用命名:
TestCase test= new MoneyTest("testMoneyEquals ") { protected void runTest() { testMoneyEquals(); } };
这比通常的子类继承方便多了,它仍然在编译时进行类型检查,代价是增加了开发人员的负担。
Smalltalk Best Practice Patterns
描述了这个问题的另外一个解决方案,不同的实例在相同的
pluggable behavior
下行为表现不同。其思想就是,使用一个类,这个类可以参数化,即根据不同的参数
值执行不同的逻辑,因此避免了子类继承。
最简单的可插入行为(
pluggable behavior
)形式是可插入选择子(
Pluggable Selector
)。在
SmallTalk
中,
Pluggable
Selector
是一个变量,它指向一个方法,是一个方法指针。这个思想不局限于
SmallTalk
,也适用于
Java
。在
Java
中没有方法选择子的概念,然而,
Java
的反射(
reflection
)
API
能根
据方法名这个字符串来调用方法,我们能利用
Java
的反射特性实现
Pluggable
Selector
。通常我们很少使
用
Java
反射,在这里,我们要涉及一个底层结构框架,它实现了反射。
JUnit
提供给测试客户两种选择:或者使用
Pluggable Selector
,或者使用匿名内隐类。默认地,我们
使用
Pluggable
Selector
方式,即
runTest
方法。在这种方式中,测试用例的名字必须与测试方法的名字一
致。如下所示,我们用反射特性调用方法。首先,我们查看方法对象,一旦有了这个方法对象,我们就可
以传给它参数,并调用它。由于我们的测试方法不带参数,因此,我们传进一个空的参数数组:
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 }
JDK1.1
反射
API
只让我们查找
public
方法,因此你必须把测试方法定义为
public
,否则你会得到
NoSuchMethodException
例外。
这是该阶段的设计,
Adapter
模式和
Pluggable Selector
模式。
图
4
:
TestCase
应用了
Adapter
模式(匿名内隐类)和
Pluggable Selector
模式
〔
begin
译者添加〕
由于
TestCase
中只有一个
runTest
方法,那么是不是说一个
TestCase
中只能放一个测试方法呢?为此引入
Pluggable Selector
模式。在
TestCase
中放置多个名为
testXxx()
的方法,在
new
一个
TestCase
时,用
selector
指
定哪个
testXxx
方法与模板方法
runTest
对接。
〔
end
译者添加〕
3.5
、不用担心是一个测试用例还是许多测试用例-
TestSuite
一个系统通常要运行许多测试。现在,
JUnit
能运行一个测试,并用
TestResult
报告结果,下一步就是
扩展
JUnit
,让它能运行许多不同的测试。如果测试的调用者并不在意它是运行一个测试还是许多测试,即
它用同样的方式运行一个测试和运行许多测试,那么这个问题就解决了。
Composite
模式可以解决这个问
题,它的意图就是,将许多对象组成树状的具有部分
/
整体层次的结构,
Composite
让客户用同样的接口处
理单个的对象和整体组合对象。部分
/
整体的层次结构在此很有意义,一个组合测试可能是有许多小的组合
测试构成的,小的组合测试可能是有单个的简单测试构成的。
Composite
模式有以下参与者:
・
Component
:是一个公共的统一的接口,用于与测试交互,无论这个测试是简单测试还是组合测试。
・
Composite
:用于维护测试集合的接口,这个测试集合就是组合测试。
・
Leaf
:表示简单测试用例,遵从
Component
接口。
这个模式要求我们引入一个抽象类,该类为简单对象和组合对象定义了统一的接口,它的主要作用是
定义这个接口,在
Java
里,我们直接使用接口,没有必要用抽象类来定义接口,因为
Java
有接口的概念,
而象
C++
没有接口的概念,使用接口避免了将
JUnit
功能交付给一个特定的基类。所有的测试必须遵从这个
接口,因此测试客户所看到的就是这个接口:
public interface Test { public abstract void run(TestResult result); }
Leaf
所代表的简单
TestCase
实现了这个接口,我们前面已经讨论过了。
下面,我们讨论
Composite
,即组合测试用例,称为测试套件(
TestSuite
)。
TestSuite
用
Vector
来存
放他的孩子(
child
test
):
public class TestSuite implements Test { private Vector fTests= new Vector(); }
测试套件的
run()
方法委托给它的孩子,即依次调用它的孩子的
run()
方法:
public void run(TestResult result) { for (Enumeration e= fTests.elements(); e.hasMoreElements(); ) { Test test= (Test)e.nextElement(); test.run(result); } }
图
5
:测试套件应用了
composite
模式
测试客户要向测试套件中添加测试,调用
addTest
方法:
public void addTest(Test test) { fTests.addElement(test); }
注意,上面的代码是如何依赖于
Test
接口的。既然
TestCase
和
TestSuite
都遵从同一个
Test
接口,因此
测试套件可以递归的包含测试用例和测试套件。开发人员可以创建自己的
TestSuite
,并用这个套件运行其
中所有的测试。
这是一个创建
TestSuite
的例子:
public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest(new MoneyTest("testMoneyEquals")); suite.addTest(new MoneyTest("testSimpleAdd")); }
〔
begin
为有助于理解,此处为译者添加〕
以上代码中,
suite.addTest(new MoneyTest("testMoneyEquals"))
表示向测试套件
suite
中添加一个测
试,指定测试类为
MoneyTest
,测试方法为
testMoneyEquals
(由
selector
选定该方法,与模板方法
runTest
对接)。
在
MoneyTest
类中没有声明
MoneyTest(String)
的构造器,那么
MoneyTest(“testMoneyequals”)
执行时调
用
super(String)
构造器,它定义于
MoneyTest
的父类
TestCase
中。
TestCase
(此处也即
MoneyTest
)把
“testMoneyEquals”
字符串存放在私有变量中,这个变量是一个
方法指针,使用的是
Pluggable
Selector
模式,表明它所指定的方法
testMoneyEquals
要与模板方法
runTest
对接。表明该测试用例实例中起作用的是
testMoneyEquals()
,利
用
Java
的反射特性实现对该方法的调用。
因此以上代码向
suite
中添加了
2
个测试实例,类型均为
MoneyTest
,但测试方法不同。
〔
end
为有助于理解,此处为译者添加〕
这个例子工作很好,但要我们手工添加所有的测试,这是很笨的办法,当你编写一个测试用例时,你
要记得把它们添加到一个静态方法
suite()
中,否则它就不会运行。为此,我们为
TestSuite
增加了一个构
造器,它用测试用例的类作为其参数,它的作用就是提取这个类中的所有测试方法,并创建一个测试套
件,把这些提取出来的测试方法放进所创建的测试套件中。但这些测试方法要遵守一个简单的协定,即方
法命名以
“test”
作为前缀,且不带参数。这个构造器利用这个协定,使用
Java
的反射特性找出测试方
法,并构建测试对象。如果使用这个构造器,上面的代码就很简单:
public static Test suite() { return new TestSuite(MoneyTest.class); }
即为
MoneyTest
类中中的每一个
testXxx
方法都创建一个测试实例。〔此处为译者添加〕
但前一种方式仍然有用,比如你只想运行测试用例的一个子集。
3.6
、概要
JUnit
的设计到此告一段落。下图显示了
JUnit
设计中使用的模式。
图
6
:
JUnit
中的模式
注意
TestCase
(
JUnit
框架中的核心功能)参与了
4
个模式。这说明在这个框架中,
TestCase
类是
“
模
式密集(
pattern
density
)
”
的,它是框架的中心,与其它支持角色有很强的关联。
下面是查看
JUnit
模式的另外一个视角。在这个情节图中,你依次看到每个模式所带来的效果。
Command
模式创建了
TestCase
类,
Template Method
模式创建了
run
方法,等等。这里所用的符号都来自
图
6
,只是去掉了文字。
图
7
:
JUnit
中的模式情节板
要注意一点,当我们应用
Composite
模式时,复杂性突然增加了。
Composite
模式功能很强大,使用当
心。
4
、结论
为了得出结论,我们作一些一般的观察:
・模式
以前,当我们开发框架和试图向其它人解释框架时,我们发现用模式来讨论设计是无用的。现在,你处于一个极好的处境来判断用模式来描述框架是否有效,如果你喜欢上述讨论,那么也用这样的方式来表示你的系统。
・模式密集度
围绕着TestCase有很高的模式密集度,TestCase是JUnit设计中的关键抽象,它易于使用,但难以改变。我们发现围绕关键抽象有很高的模式密集度,是成熟框架的普遍现象。对于不成熟的框架,情形相反,它们模式密集度不高。一旦你发现你要解决的是什么问题,你就开始“浓缩”你的解决方案,达到高的模式密集度。
・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.
・交集,而非合并
在框架开发中,总想包含进每一个特性,想让框架尽可能有价值,但有另一个因素作用相反:你希望开发人员使用你的框架。框架的特性越少,学习就越容易,开发人员就越可能使用它。JUnit的设计就是这样的思路,它实现那些对于运行测试而言是必不可少的特性,如运行测试套件、将不同的测试互相隔离、自动运行测试等等。当然我们还会添加新的特性,但我们会仔细地加以选择,并把它们放进JUnit扩展包中。在扩展包中,一个值得注意的成员就是TestDecorator类,它使用了Decorator模式,可以在测试代码运行之前或运行之后执行其它的代码。〔此处译者有添加〕
・框架作者要花很多时间阅读框架代码
我们阅读框架代码的时间要比编写代码的时间多得多;我们为框架增加功能,但我们花同样多的时间为删除框架中的重复功能。我们用各种途径为框架设计、增加类、移动类职责,只要我们能考虑到的各种途径。在JUnit、测试、对象设计、框架开发和写文章的工作中,我们不断地提高洞察力,并受益无穷。
相关推荐
JUnit是Java编程语言中最常用的单元测试框架之一,它在软件开发过程中起着至关重要的作用,确保代码的质量和可维护性...对于任何Java开发者来说,理解和掌握JUnit框架及其设计模式是提高工作效率和代码质量的重要一环。
《Junit设计模式分析》这本书深入探讨了如何在单元测试框架Junit中巧妙地应用设计模式,以提高代码的可测试性和可维护性。在软件开发过程中,设计模式是解决常见问题的最佳实践,它们能够帮助开发者创建灵活、可扩展...
JUnit 测试框架的使用 JUnit 是一个流行的单元测试框架,广泛应用于 Java 项目中,本文将详细介绍 JUnit 测试框架的使用,并提供了两个示例来帮助读者更好地理解和掌握 JUnit 的使用。 一、JUnit 测试框架的使用 ...
### JUnit源码及其涉及的设计模式 #### 一、引言 JUnit作为一款广泛应用于Java项目的单元测试框架,其设计理念和实现方式对于软件开发者来说具有很高的学习价值。本文将深入探讨JUnit源码,并重点关注其中使用的...
JUNIT作为Java编程语言中最广泛使用的单元测试框架,能够帮助开发者验证代码的正确性和设计模式的有效应用。本篇将详细介绍如何使用JUNIT来验证设计模式。 一、设计模式与JUNIT结合的重要性 设计模式的引入是为了...
JUnit框架是Java编程语言中最广泛使用的单元测试框架,它为开发者提供了一种便捷的方式来编写可重复执行的测试用例,确保代码的质量和稳定性。在JavaScript的开发环境中,虽然JUnit主要用于Java,但通过一些工具和库...
本文将对 JUnit 设计模式进行深入分析,探讨 JUnit 中的设计模式应用,了解 JUnit 是如何使用设计模式来实现测试框架的。 一、JUnit 概述 JUnit 是一个优秀的 Java 单元测试框架,由 Erich Gamma 和 Kent Beck ...
JUnit作为一个强大的单元测试框架,它的设计理念、核心特性和背后的优秀设计模式,对Java开发人员来说是不可或缺的工具。通过理解和熟练运用JUnit,开发者可以更有效地进行测试,提升代码质量,降低维护成本,从而...
本资源"Junit设计模式分析(带源码)"旨在深入探讨JUnit在设计上的模式和最佳实践,通过源码分析帮助开发者更好地理解和应用这个工具。 1. 单元测试基础: 单元测试是对软件中的最小可测试单元进行检查,如函数、...
* 设计性:JUnit 框架可以帮助程序员编写简单高效的测试,达到验证代码正确性的目的。 2. JUnit 框架的组成 * TestCase 类:当继承了 TestCase 类之后,就可以使用框架的单元测试功能。 * Assert 类:想要验证功能...
JUnit的框架设计受到了“模式生成架构”的影响,这是一种自底向上、逐步构建系统架构的方法。在JUnit中,我们会遇到各种设计模式,这些模式用于解决特定的架构问题,例如如何组织测试、如何处理测试环境的初始化和...
尽管标题提及了“Junit设计模式分析”,但实际内容并未涉及Junit的设计模式分析,而是侧重于JBoss 5的新特性介绍。因此,下面将围绕JBoss 5新特性进行深入解析。 ### JBoss 5 微容器(Microcontainer)介绍 #### ...
设计模式、框架及其实践是软件开发领域中提升设计能力的重要工具。通过本书,作者张永吉旨在引导读者从基础知识到高级技巧,逐步成为一名精通软件设计的专家。 首先,成为象棋高手的过程与成为软件设计高手有...