Junit--Junit In Action 笔记

阅读指引:
第一部分:Junit精粹
第1章:带着你为一个简单的对象创建测试。在此过程中介绍了单元测试的好处,理念,和方法。
随着测试越来越复杂,我们把用junit创建更好的测试方案来展现。
第2章:进一步深入研究了Junit的类,生命周期和框架。为了把这些联系起来,我们描述了一些
测试的方法和例子。
第3章:描述了一个复杂的TestCase,以使向你展示Junit较大的组件如何工作。这个案例中分析的是很多
应用中都有的组件:Controller.我们介绍了案例中的代码,标出哪些代码是要测试的,然后展示如何测试它。
我们一旦知道代码能按我们的意愿正常工作,就开始为异常条件编写测试,以确保哪怕出了问题代码也能
正常工作。
第4章:讲述不同类型的软件测试以及他们在应用生命周期中扮演的角色,并讲述如何为可测试性设计。如
何实施测试先行的开发。
第5章:探讨了将Junit整合进开发环境的各种方式。并提及了用Ant,maven和Eclipse来自动化Junit测试。

第二部分:测试策略
第6章:描述了如何用stub来执行单元测试。这一章介绍了一个连接到web服务器的示例应用,并描述了如何应用
Stub方法对远程URL的方法执行单元测试。
第7章:展示了Mock object方法,这是一种让你可以把代码从周围领域对象隔离出来的测试的方法。
第8章:展示了另外一种对J2EE组件很有用的单元测试方法,容器内测试,in-container testing.本章讲述了如何用cactus
来在容器内运行单元测试,此外我们分析了 in-container和mock objects方法的优劣。
第三部分: 测试组件
第9章:展示勒如何使用mock objects 和in-container 方法对servlet 和filter执行测试,强调了这两种方法如何互为补充。并给出了何时选择何种测试策略。
第10章:带我们走进单元测试JSP 和taglib的世界,这章展示了如何应用mock objects 和in-container策略。
第11章:则涉及了一个艰难但重要的主题,对通过ODBC访问数据库的应用程序进行单元测试,该章还展示了数据库代码和数据库隔离进行单元测试。
第12章:则研究了如何应用mock objects,单纯的Junit TestCase 和Cactus来测试各种EJB。




第一章: junit起步
1.2从头开始
Junit的基本原则: 若程序的某项功能没有经过测试,那么该功能基本等于不存在。
1.3理解单元测试框架
所有测试都遵守的3条原则
1.每个单元测试都必须独立于其他单元测试而运行;
2.必须以单元测试为单位来检测和报告错误;
3.必须易于定义要进行哪些单元测试;
1.4安装junit
步骤:
1. 从junit.org下载最新版本的junit ,在第二步中称http://junit.zip
2. 将junit解压到某个目录
3. 略。。。
1.5用junit测试
Junit很多功能可以简化测试的编写和运行。在本书中很多地方都讲述了这些功能在实践中的运用
1. 可供选择的front-end或者test-runner,用来显示你测试的结果;
2. 用单独的ClassLoader来运行每个单元测试,以避免发生副作用;
3. 标准的资源初始化和回收方法
4. 各种不通的assert方法,让你检查测试结果的操作变得更容易
5. 同流行工具比如ant和maven,和开发工具eclipse和jbuild的整合

1.6 小结
每个开发者都会执行某种类型的测试,以确保新写的代码能正常工作。使用自动单元测试的开发者能够根据需要
重复执行这些测试,以确保代码在后面依然正常工作
手工编写单元测试并非难事,但是当测试变得更为复杂,编写和维护测试就变得比较困难了,junit是一个单元测试框架,
使得创建,运行,维护单元测试变得简单。


第二章:探索junit
问题: 我们怎么运行多个TestCase?我们用什么运行这些测试?
在本章,我们将概览junit的核心类:TestCase ,TestSuite 以及BaseTestRunner。然后我们将更细致的探索各种TestRunner
和TestSuite,之后回头拜访我们的老朋友TestCase,最后我们将观察这些核心类是如何工作的
2.1探索junit的核心
当你需要一次执行多个TestCase时,你可以创建TestSuite
TestCase+testsuite +testrunner =testResult (Junit成员三重唱,共同产生测试结果)

testCase (测试用例):
扩展了junit的TestCase类的类,它以TestXXX方法的形式包含一个或多个测试。一个TestCase把具有公共行为的测试归入一组,

testsuite (测试集合)
一组测试,一个TestSuite把多个相关测试归入一组的便捷方式。
TestRunner(测试运行器)
执行TestSuite的程序,Junit提供了几个TestRunner,你可以用它来执行你的测试,没有TestRunner接口,只有一个所有TestRunner
都继承的BaseTestRunner,因此当我们编写TestRunner的时候,我们实际上是指任何继承BaseTestRunner 的Test Runner的类。

7个Junit核心类和接口
Assert 当条件成立时,assert方法保持沉默,但若条件不成立,则抛出异常。
TestResult TestResult包含了测试中所有的错误或者失败
Test 可以运行Test并把结果传递给TestResult
TestListener 测试中若产生事件(开始,结束,错误,失败)
TestCase 定义了可以运行多项测试的环境
TestSuite 运行一组TestCase它是Test的集合
TestRunner TestRunner用来启动测试的用户界面,BaseTestRunner是所有TestRunner的超类


2.2 用Test Runner来运行测试

2.2.1选择TestRunner
Keep the bar green to keep the code clean
Junit 定义了3个TestRunner类,一个用于文本控制台,一个用于Swing,甚至还有一个AWT的

2.2.2定义你自己的TestRunner


2.3用TestSuite来组合测试
TestSuite被设计成可以运行一个或多个TestCase,TestRunner负责启动TestSuite,而要运行哪些TestCase由TestSuite决定

2.3.1 运行自动TestSuite

2.3.2编写你自己的TestSuite
TestCase 和TestSuite 都实现了Test接口。
Junit 的设计目标:
用Junit创建的测试必须具有长久的价值。若你持续的运行测试,那么你就把测试的投资最小化,并把投资的回报最大化了。
典型的TestAll类


1. 创建一个Suite方法,以便调用所有其他的Test或Suite.
2. 给这个TestSuite一个标识,以便区分.
3. 可以调用addTestSuite方法来增加想要一起运行的TestCase对象或者TestSuite对象.
2.4 用TestResult来收集参数.
TestResult负责收集TestCase的执行结果.TestResult储存了所有的测试的详细情况.是通过还是失败.
TestRunner使用TestResult来报告测试结果.如果TestResult中没有TestFailure对象,那么代码就是干净的.

Junit区分失败和错误.失败是可以预期的,代码中的改变不会造成断言失败.你只要修正代码,断言就可以通过.
但是错误(比如常规程序抛出的异常)则是测试时不能预料的.
当遇到错误,好的分析步骤是:
检查环境;
检查测试;
检查代码;
2.5 用TestListener来观察结果
TestResult收集了测试的信息,TestRunner负责报告信息. Junit 提供了TestListener接口,以帮助对象访问TestResult
并创建有用的报告.
TestRunner实现了TestListener,很多特定的Junit扩展也实现了TestListener 可以有任意数量的TestListener向Junit
框架注册.这些TestListener可以根据TestResult提供的信息做它需要做的任何事情

2.6 用TestCase来工作
概括的说,Junit的工作过程就是由TestRunner来运行包含一个或多个TestCase的TestSuite,但是在常规工作中,你只和
TestCase打交道.
典型的TestCase包含两个部件:fixture和单元测试
2.6.1 用Fixture来管理资源
有些测试需要一些资源,要把这些资源配置好是一件麻烦事,比如像数据库连接这样的资源就是典型的例子.
把通用的资源配置代码放在测试里可能不是一个好主意,
TestCase通过setUp 和tearDown方法来创建和销毁fixture.TestCase会在运行前调用setUp,并在每个测试完成后调用tearDown
把不止一个测试方法放入同一个TestCase的好处就是可以共享Fixture代码.
Junit通过Assert接口提供的工具方法来复用代码.
2.6.2 创建单元测试方法
使用Fixture是复用代码的好方法,但是还有很多常见的测试任务,很多测试都会重复执行这些任务,Junit框架用一组assert方法封装了
最常见的测试任务.这些assert方法可以极大的简化单元测试的编写.
Assert超类型
TestCase成员
除了Assert提供的方法之外,TestCase还实现了10个它自己的方法,
保持测试的独立性
你在开始编写自己的测试的时候,记住第一条规则:每个单元测试都必须独立于其他单元测试而运行.单元测试必须能以任何顺序来进行,一项测试不能依赖与前面的测试造成的改变,下面是会相互依赖的测试所造成的问题:
不具可移植性
难以维护
不够清晰

2.7创建TestCaculator的全过程


2.7.1创建TestSuite
TestRunner一开始寻找TestCaculate类中的suite方法.若找到了就直接调用它,这个suite方法会创建不同的
TestCase类,并把他们加入到不同的TestSuite.
因为TestCaculate没有suite方法,所以TestRunner创建了一个默认的TestSuite对象.

2.7.2 创建TestResult


步骤如下:
1. 在图中1处,TestRunner实例化了一个TestResult对象,在测试顺序执行的时候,这个对象将用来存放测试结果;
2. TestRunner像TestResult注册测试,这样在执行测试的过程中TestRunner就可以收到各种事件,这是Observe模式的典型例子,TestResult会广播如下方法:
测试开始(startTest);
测试失败(addFailure);
测试抛出未被预期的异常(addErr);
测试结束(endTest);

3. 知道了这些事件,TestRunner就可以随着测试的进行而显示进度条,并且在测试失败和错误的时候显示出来.
4. TestRunner通过调用TestSuite的run方法开始测试.
5. Testsuite为它拥有的每个TestCase实例调用Run(TestResult)方法.
6. TestCase通过传递给它的TestTesult实例来运行run(Test)方法,并把自身作为参数传递给Run方法,这样TestResult就梢后用runBare回调它
2.7.3 执行测试方法

图2.10描绘了执行单个测试方法的步骤.步骤如下:
1. 在图中5处,runBare方法顺序执行setup,testAdd,和tearDown方法.
2. 如果在执行这3个方法的过程中发生了任何失败和错误,那么TestResult就会分别调用addFailure和addError
来通知所有的Listener
3. 如果发生任何错误,那么TestRunner会列出这些错误,否则进度条就是绿色的,从而让你知道没有问题.
4. 当tearDown执行完成后,整个测试过程就完成了,Testresult会调用endTest把这个结果通知给所有的listener.
2.7.4 复习完整的Junit生命周期

第三章:Junit实例
Tests are the programmer’s stone, transmuting fear into boredom.
测试是程序员之石,把恐惧变成厌倦;
3.1 引入Controller组件
3.1.1设计接口
public interface Request {
String getName();
}
public interface Response {

}
public interface RequestHandler {
public Response process(Request request) throws Exception;
}
public interface Controller {
Response processRequest(Request request);
void addHandler(Request request,RequestHandler handler);
}
3.1.2 实现基类

public class DefaultController implements Controller {
private Map<String,RequestHandler> requestHandlers = new HashMap<String,RequestHandler>();

protected RequestHandler getHandler(Request request){
if(!requestHandlers.containsKey(request.getName())){
String message="Can't find handler for request name["+
request.getName()+"]";
throw new RuntimeException(message);
}
return (RequestHandler)this.requestHandlers.get(request.getName());
}
public void addHandler(Request request, RequestHandler handler) {
if (this.requestHandlers.containsKey(request.getName())){
String message="A handler has already been registered for request name["+
request.getName()+"]";
throw new RuntimeException(message);
}else{
requestHandlers.put(request.getName(), handler);
}

}

public Response processRequest(Request request) {
Response response;
try{
response = getHandler(request).process(request);
}
catch(Exception ex){
response = new ErrorResponse(request,ex);


}
return response;
}


}

3.2让我们来测试吧
3.2.1 测试DefaultController

3.2.2 增加处理器
测试来自何方?
为了创建一个单元测试,你需要两种类型的对象:你需要测试的领域对象和同被测试的对象交互的测试对象

测试类存在于何方
你把测试类存放在何方?Java提供了几种解决方案.
把它们作为package中的共有类
把它们作为test Case类的内部类

Junit最佳实践:选择有意义的测试方法名

遵循的步骤有章可寻:
1. 在开始测试时把环境设置为已知状态.测试前的状态常称为Test Fixture;
2. 调用待测试的方法;
3. 确认测试结果,这里通常是调用一个或多个Assert方法实现的;
3.2.3处理请求
Junit最佳实践:在assert中解释失败原因
分离出初始化逻辑

3.2.4 改进testProcessRequest


3.3 测试异常处理
Junit最佳实践:测试任何可能失败的事物
3.3.1模拟异常条件
异常Test Case才是单元测试真正闪光的地方

3.4 建立测试项目
Junit最佳实践:同一个包,分离的目录


第四章:探索软件测试
崩溃指的是你的计算机程序的死亡,当你的程序死亡的时候,这就是一个”特性”,
通常,紧跟崩溃之后的是一个像”ID 02”这样的消息,”ID”是对特性的一个简称,而跟随的消息
数目又指出了这个产品所再需要的测试的月数. ---------------Guy Kawasaki

在本书的前面几章,给出了非常实际的设计和部署单元测试的指导.本章则后退一步.带你从
更远的地方领略软件测试的各种各样的类型,已经它们在应用生命周期中所扮演的角色.
本章告诉你如何为了可测试性而设计,以及怎样实现测试先行的开发.
为什么你需要知道所有这些呢,因为进行单元测试不是件心血来潮的事情.为了成为一个
好的开发者,你必须理解为什么一定要做这件事,以及你为什么要写单元测试而不是编写功能测试,
集成测试和其他种类的测试.一旦你理解了你为什么要写单元测试,你就知道你应该进行多远,何时你才
具备足够的测试,测试不是一个最终的目标.
最后,我们将会向你展示测试驱动开发将会怎样潜移默化地改善你的应用程序的性能和设计,
这是通过把单元测试放在开发过程的中心地位而做到的.
4.1 单元测试的必要性
功能测试也能够做到这点,但是,单元测试是更强大和多方面的,它能够做的不仅仅是简单地验证应用程序正常工作.
他还能:
带来比功能测试更广范围的测试覆盖.
让团队协作成为可能.
能够防止衰退,降低调试的需要.
能为我们带来重构的勇气.
能改进实现设计.
当作开发文档使用.
单元测试非常有趣.
4.1.1 带来更大的测试范围
功能测试应该是应用程序所应有的第一种测试类型.如果你必须在写单元测试和功能测试之间做出选择,
那么你应该选择后者.在我们的经验中,功能测试能发现70%的应用程序代码错误.如果你希望进行更深入一点,想
提供更大测试覆盖范围,那么你就需要写单元测试了.
单元测试能够很容易地模拟错误条件,这点在功能测试中很难办到.尽管如此,若你使得要不要进行软件测试
完全取决于测试覆盖范围,这也是一个错误,单元测试比单纯地测试能提供更多东西,这将会在一下地小节中说到.
4.1.2 带来团队协作的可能
设想一下,你是一个团队中的一员,在整个应用程序的某一个部分上工作.单元测试使你能够递交高质量代码
而不需要等到其他部分都完成以后.另一方面,功能测试也更粗糙,而且需要整个应用程序完成之后才能进行测试.
4.1.3 防止衰退,减少调试
一组好的单元测试能够给你带来自信,让你确信自己的代码能很好的工作,它也给你去修改你现存的代码的勇气,
要么是重构的目的,要么是为了增加或是修改特性.作为一个开发者,没有什么比这更好的感觉了;知道有人正站在你背后,
而且在你损坏一些东西的时候将会给你提醒.
一个必然的推论就是:一组好的测试能减少用调试程序来发现错误的必要.

Junit最佳实践:重构

4.1.4 使得重构可行
没有单元测试的话,要证明重构是可行的将会是一件很困难的事,因为你总是可能损坏一些东西.为什么要花好几个
小时的时间只是为了改进实现设计,改变一个变量的名字诸如此类的事情呢?单元测试能提供一个安全网,它能为你提供重构的勇气.

4.1.5 改进实现设计
单元测试是客户要进行的代码测试的第一步.它们要求被测试的API是很灵活的,而且在孤立的状态下也是能进行
单元测试的.你通常需要重写你的代码来使它能进行单元测试.

4.1.6 当开发者文档来使用.

4.1.7 非常有趣
单元测试很容易让人上瘾.一旦你迷上了Junit绿色状态条,离开它就会变得很困难.它给你带来了思想上的安宁.
刚开始的时候可能你会有所怀疑,但是一旦你迷上了测试,那么写代码而不写测试就是就会变成一件不可思议的事.

4.2 不同种类的测试
图4.2 大致地勾勒出了现在软件测试地五种类型,有其他划分软件测试地种类的方法.

4.2.1 软件测试的4种类型
我们已经提到郭了单元测试所关注的是每一个不同的工作单元.但是当几个不同的工作单元合并到一个工作流中的
时候,测试此时将要发生的事情会怎么样呢?工作流的最后结果会是你所期待的吗?应用程序能够满足所有人的需要吗?
以上每一个问题将会由一个不同的软件测试类型来回答.针对讨论的问题,我们可以将软件测试分成四类:
集成测试
功能测试
压力/负荷测试
验收测试



集成测试
单个的单元测试是一个很重要的质量控制手段,但是当几个不同的工作单元合并到一个工作流中的时候,
将会发生什么事情呢?一旦你对一个类创建了一个测试并且已经执行了,下一步就是关注其他方法和其他服务.检查组件之间的相互影响,这就是进行集成测试.
因为可能你想要集成的东西有很多,集成这个词在不同的环境下代表了不同的事物.一些环境在表4.1中给出了.

功能测试
在对象之间的测试交互作用是很重要的.但是结果就是你想要的那个吗/
功能测试在公共API的边界处的代码.通常情况下,这等于测试应用程序用例.
功能测试通常总是和集成测试结合在一块的.

压力/负荷测试
应用程序的功能能够正确执行是很重要的,但是当多个用户同时执行时的效果又会如何呢?大多数压力测试
检验应用程序是否能在短时间内响应大量的用户请求.通常,这是由一些特定的软件来执行.
比如:JMeter(http://jakarta.apache.org/jmeter).能够自动地发送设定好的请求以及跟踪应用程序的响应时间.通常请求是否被
正确的执行是不被测试的.
压力测试通常在一个单独的环境中进行.这种测试环境往往具有比典型的开发环境更多的控制.它必须跟运行的环境尽量相似.如果两个环境很不一样的话,测试也就没有意义了.
其它类型的性能测试可以在开发环境下执行.
从测试的角度看,用profiler能够让你先知先觉.帮助你在真正的测试之前就发现一些潜在的问题.首先你必须能够证明一个特定的瓶颈存在,然后你必须能够证明这个瓶颈已经被消除了.在两种情况下,profiler都是重要的工具.
单元测试也可以帮助你把一个应用程序分割成一个个的自然的部分.一些Junit扩展,比如JunitPerf能构帮助你建立一套跟你的单元测试相匹配的性能测试.你可以断言一个重要方法不能花太长的时间,作为性能单元测试的一部分你还可以指定一段时间作为执行的上限.当应用程序并重构,如果有什么变化影响到你的话,这些测试就会向你报警.
验收测试
应用程序能够运行流畅是很重要的,但是最终唯一一个问题是:这个应用程序满足了用户的要求了吗?验收测试就是这方面的测试.这些操作通常直接由用户或者用户的代理人来进行.验收测试是确保应用程序已经满足了用户提出的任何要求.
验收测试是所有其他测试的超集.

4.2.2 单元测试的3种类型
本书主要内容是通过程序进行自动单元测试.当我们使用单元测试术语的时候,主要关注的是从内部测试代码.这种行为不可避免的和编写代码相互联系并且同时发生.单元测试能够确保你的应用程序在一开始就处在测试中.
当然,你的应用程序应该能够经受住其他各种软件测试形式.从单元测试开始,以验收测试结束.前面的章节介绍了对你的应用程序应当做的其他类型的测试.
许多应用程序都被分成许多的子系统.作为一个开发者,你想确保你的每一个子系统都能够正确地执行.当你写代码时,你的第一个测试大概是单元测试.当你写了越来越多的测试和越来越多的代码,你会发现,你需要开始添加集成和功能单元测试.在任何时刻,你都可能正在埋头于逻辑单元测试,集成测试或者是功能测试.


4.3 判断测试质量

4.3.1 衡量测试覆盖面
一种衡量方法是数一下你的测试中用到了多少方法.这不会告诉你你是否在做正确的事情,但它能告诉你是否有
测试.
没有单元测试的话,你只能依靠应用程序的公共API方法来写测试.因为你不需要仔细研究应用程序内部并以此来创造测试,这些就被成为黑盒测试,图
单元测试的创建依赖于对一个方法实现的了解.如果在这个方法中存在一个条件分支,你能创建两个单元测试,每个分支各一个.你创建这样的测试需要仔细研究那个方法,这就是被称为白盒测试.
有了白盒单元测试,要达到一个更高的测试水平就变得简单一些,主要因为你已经访问了更多的方法而且因为你能控制每个方法的输入以及被调用的次级对象的行为(通过使用stub或者mockbojects你将会在后面的章节看到怎么做),白盒测试能够测试到包的保护方法和公有方法.

4.3.2 产生测试覆盖情况报告
对于Junit,可以找到一些工具帮你分析你的应用程序并且提供一个关于你的应用程序测试覆盖情况的确切报告.图4.7
给出了这样的一个报告,这个报告是由Clover(http://www.coretex.net/clover)工具创建的.
知道哪些类被进行了测试(DefaultController),知道哪些类还没有进行测试,这是重要的信息.但是如果能知道为什么DefaulgController只有94.7%的测试覆盖那就更好了.幸运的是,clover能够在方法实现层次深入追踪.
知道哪些代码还没有进行测试是有好处的.尽管如此,一个好的测试工具(比如Clover)应该提供历史报告来显示整个开发过程中的测试经过,也应该提供与你喜欢的构建系统的集成.它也应该有能力来停止构建,如果标准没有达到的话.
在我们的项目中,我们喜欢进行一段开发迭代之后测试一下覆盖百分率.我们调整构建失败标准,以使下一次迭代至少有与前一迭代一样的覆盖百分率.这个策略就确保了我们测试覆盖率能稳步上升.
虽然我们所展示的报告有帮助也很有趣,但是他们并不能告诉你你的测试有多么的好. 他们只会告诉你哪个方法已经被测试过了, 哪个还没有.就算你的测试是完全错误的并且没有测试任何东西,你的报告仍然还是一样的!确保测试的质量是很困难的,测试工具能提供一定的帮助.Jester 是通过对被测试代码进行随意的变动来工作的;它会验证你的代码在这种情况下是否依然能通过.如果他们通过了, 那就意味着测试还是够好.Jester通过多次反复来做这件事,它还会产生一个报告来揭示测试的质量.
记住100%的测试覆盖并不能保证你的应用程序得到了100%的测试,这一点是很重要的.你的测试覆盖只是和你的测试一样好!如果你的测试构想很糟糕,那么你的应用程序就得不到足够的测试,不管你进行了多少测试.

4.3.3 测试交互
如果我们应用白盒测试能达到更高的测试覆盖,而且我们能够生成几个精彩的报告来证明这一点,那么我们还需要进行黑盒测试吗?
如果你想要完全的测试你的应用程序,包括运行对象是如何相互影响的,你就需要把黑盒子测试作为整个测试的一部分.
4.4 测试驱动开发
在第三章中,你设计了一个应用程序,而且快速地创建了几个测试来验证你的设计.当你创建这些测试的时候,这些测试帮助改善了最初的设计.随着你创建更多的单元测试,正反馈的良性循环会鼓励你尽早地编写单元测试.很快,当你设计实现的时候,要想知道你将要如何测试一个类就变得自然而然.在这个方法论下,越来越多的开发者正在从利于测试的设计跃迁到测试驱动开发.
4.4.1 调整周期
当你开发代码的时候,你会设计一个应用程序变成接口,然后实现接口所提出的功能.当你进行单元测试的时候,你通过一个方法的API来验证功能

4.4.2 TDD两步走
前面,我们说过通过[测试,编码,(重复),提交]这样一个流程,TDD加快了开发的周期.可问题在于这还遗漏了一个重要的步骤.更应该像这样来运作:[测试,编码,重构,(重复),提交].
TDD的核心原则是:
1. 在写新代码之前写一个失败的自动测试.
2. 消除重复.

消除重复这个步骤确保了你写的代码不仅能通过测试,还具备可维护性.当你清除了重复之后,你能够增加内聚,减少依赖.这些都是长时间保存而不变质的代码的特点.
别的编码实践鼓励我们编写可预知改变从而达到可维护性的代码.与他们不同的是,TDD鼓励我们通过消除重复来编写可维护的代码.遵循这个实践的开发者发现.有测试撑腰的并且划分良好的代码自然而然地可以简单而且安全地承受改变.TDD给了我们自信,让我们今天的问题今天解决,明天的问题明天解决.


4.5 在开发周期中的测试
在开发周期的不同地方和不同时间都会有测试.让我们首先介绍一下开发生命周期,然后以它为基础来决定何种类型的测试将在何时被运行.图4.9给出了一个典型的开发周期,这样的一个开发周期我们在小型或大型的开发中都广泛应用.
生命周期被分成四到五个平台(platform)
开发平台: 这就是编码发生的场所.它包含开发者的工作站.它的一个重要的功能就是提交(commit) 或check in(取决于所使用的术语);这样的提交一天会有几次,提交到你的公共源代码控制管理工具.一旦你提交了,其他人就能开始使用你所提交的东西了.但是只能提价能工作的部分也是很重要的.为了能知道它是否能正常工作, 一个典型的策略是采用自动构建,并且在每次上传之后都运行它.
集成平台(integration platform):这个平台的目的是集成各个不同部分来构建应用程序而且要确保他们在一起能协调地工作.这一步是很有价值的,因为通常能在这儿发现问题.就是因为它很重要.所以我们希望它能自动执行.它被成为持续集成.并且,通过把自动构建应用程序作为构建过程的一部分,这也是可以达到的
验收平台/压力测试平台:取决于你的项目预算,这可以是一个或两个平台.压力测试平台在加载以后执行应用程序,并验证它是正确的.验收平台就是项目的客户.
成品(前)平台
让我们来看一下测试是如何在开发周期中发挥作用的.图4.10说明了在每一个平台上你能进行的不同种类的测试:
在开发平台,你执行逻辑单元测试(这些测试是能脱离环境进行的).这些测试执行得非常快,你通常从你的IDE来执行它们以验证你对你的代码所做得任何改动没有损坏其他部分.在你提交代码到你的SCM之前,你也可以通过你的自动构造来执行它们.你应该执行集成单元测试
集成平台通常会自动运行构造过程,生成包,配置应用程序,并且执行单元测试和功能测试,通常只是所有功能测试得一个子集在集成开发平台下运行,因为相对于目标平台,它只是一个简单的平台,缺少一些元素
在验收平台/压力平台上,你将执行和集成平台相同的测试,此外,你还要运行有压力测试和负荷测试.验收平台同最终的运行平台非常相似,并且能够执行更多的功能测试.
尽管在预期的运行平台下测试是一个很好的习惯.这样的话会保证你是在一个一切都是正确的环境下校验的.

4.6 小结
改变的步伐在加快,项目的时间框架在缩短,而我们需要对变化作出快速的反应.另外,开发过程正在改变----让开发成为代码的艺术还不够,还应当让开发成为编写解决方案的艺术.
为了能跟上迅速变化的脚步,我们必须打破异步开发模式,在这种模式中,软件测试是在开发完成以后由另一个独立的团队进行的,当改变和速度变得极为重要时,把测试留到最后就不那么合适了.


第五章:Junit自动化
在这一章里,我们将学习直接支持Junit的三个产品:Ant,Maven以及Eclipse.Ant和Maven是可以和任何Java编程环境配合使用的构建工具,Eclipse是一个集成开发环境(IDE).我们将会展示如何高效地结合使用Junit和这些环境,以及如何把Junit测试的执行自动化.在这章的最后,你将会知道,如何在你的电脑上配置环境来构建Java项目,包含如何执行Junit测试以及如何创建Junit报告.
5.1 生命中的一天
为了使单元测试有效实用,它们必须是开发流程的一部分.大部分的开发周期开始于从项目源代码中导出模板.在进行任何修改之前,谨慎的开发者会首先运行全套的测试工具.许多团队有工作库必须通过所有单元测试的规定.在开始你自己的任何开发前,你必须留意没有谁违反这条”全绿规定”.你必须保证你的工作进展是已知的基线开始.
接下来就是编写新的用例的代码.如果你是测试驱动开发的实践者,你就会先开始针对用例编写新的测试.一般来说,这项测试表明你的用例未被支持.一旦你写下了实现用例的代码,状态条将变绿,这样你就可以提交你的代码了.
非TDD实践者将实现用例并且编写测试验证.一旦状态条变绿,代码和测试就可以提交了.
5.2 从Ant中执行测试
编译和测试像第三章中的DefaultController类这样一个单独的类是不难的.如果你唯一的工具只是库中Javac编译器,那么编译一个包含有多个类的大项目将是令人头痛的.在数目不断增加的类之间互相牵扯.于是越来越多的类得处于classpath上以便编译器能找到它们.在每次构建中,只有一小部分类会被修改,该编译哪些类也是问题.在编译之后手动重新编译你的Junit测试也同样不方便.
幸运的是,以上问题的答案就是令人难以置信的Ant.ant不仅是一个编译程序的强有力工具,也是执行Junit回归测试的解决之道.

5.2.1 不可缺少的Ant
Apache的Ant是个让你轻松地编译和测试程序的构建工具.它是构建Java程序的事实标准.让Ant如此流行的一个原因是它不仅仅是个工具;ant是运行工具的架构.除了可以使用Ant配置和启动编译器,你还可以使用它来生成代码,执行JDBC查询,还有你将看到的Junit整套测试工具.
像许多现在的项目一样,Ant使用一个XML文件来进行配置.这个文件也就是构建文件(buildfile),默认情况下被命名为build.xml.Ant的编译文件描述了你想应用在你的项目中的每一个任务.任务可能是编译java源码.生成java文档.


第二部分:测试策略
第一部分介绍用Junit进行单元测试的基本知识.然而,仅仅知道了Junit如何工作,或者如何把Unit用于简单的例子,这都是远远不够的.尤其是当把它用来测试一个真正的应用程序的时候.所以,单独的Junit是不够的,你需要发展出一套测试策略,使你能够对一些真枪实弹的程序进行单元测试.主要问题在于如何孤立地测试每个部分.你在写单元测试代码时,想要一点一点地测试应用程序.那么,你如何把各个功能分离出来,从而独立地测试它们呢?第2部分解决了这个重要问题..
第6章介绍了stub策略,它允许你孤立测试粗粒度的代码部分.在第7章中,你会学习到叫mock objects的新技术,它可以进行孤立的细粒度测试.通过使用mock objects,你会发现这不仅仅是一种对代码进行单元测试的新方法,而且还是一种新的编程方法.第8章把你带入一个全新的领域---在容器中对代码进行单元测试.现在,几乎所有的代码运行在可与之交互的某些容器中.你会学到,当J2EE代码运行在它的容器中时,如何进行单元测试.你还可以通过与mock objects方法进行比较,从而了解这种策略的利弊.
读完第二部分,你就会熟悉用隔离方法对代码进行单元测试的这三种策略了.你将做好准备,可以去应付我们旅行的最后一步:单元测试各种类型的J2ee组件.

第六章:用stub进行粗粒度测试
本章内容:
介绍stub
使用嵌入式服务器代替真正的网络服务器
用stub单元测试一个HTTP连接案例

当你开发自己的应用程序时,你可能会发现你想要测试的代码段依赖于其它的类,而它们本身也依赖于另一些类,这些类则要依赖于开发环境.
由于程序依赖于开发环境,编写单元测试成为了一个挑战.你所做的测试需要具有稳定性,当你一遍又一遍地运行它们,产生的结果必须是一致的.所以,你需要找到一个方法来控制运行环境.一个解决的办法是建立真正的需求环境作为测试的一部分,并在其中运行测试.在某些情况下,这种方法是可行的,同时带来了额外的价值.但是,只有你在开发平台上建立真实环境,测试才工作得好.事实上,这种情况不是总是会出现的.
例如,你的应用程序使用http连接由第三方提供web服务器,但是在你的开发环境里通常并不存在那样一个可用的服务器程序.所以,你需要一种方法模仿服务器,这样便可以编写测试代码了.
还有另外一种情况,假设你同其它开发者一起开发一个项目.你想测试项目中你的那一部分.但其中部分还没有完成,那该怎么办呢?解决的办法是用一个仿造品模拟缺失的部分.
这里有两个策略,供我们生成模拟对象:stub技术和使用mock objects. Stub是个原始的方法,但如今仍很流行,很大程度上是因为,它们使得你可以测试代码,而不必特意为了测试而修改代码,Mock objects则是另外一种情况.在这章我们专门介绍stub技术,第7章讲到mock objects.
6.1 stub简介
Stub这种机制是用来模拟可能存在或还没写完的真实代码所产生的行为.它使你能顺利地测试系统的一部分,而无须考虑其他部分是否可行.通常,stub不会改变你测试的代码,只是加以适配以提供无缝整合.
Stub----stub是代码的一部分.在运行时我们用stub替换真正代码,忽略调用代码的实现.目的是用一个简单一点的行为替换一个复杂的行为,从而允许独立地测试代码的某一部分.
这里有一些用到stub的例子:
你能不修改一个现有的系统,因为它很复杂,很容易崩溃.
粗粒度测试,如在不同子系统之间进行集成测试.

通常,stub给测试的系统以相当好的可靠性.使用stub,你并没有修改被测试的对象,你所测试的对象就同将来产品中要运行的一样.用stub进行测试一般是运行环境中完成的.这就保证了系统运行的可靠性.
从另一面来说,stub通常难以编写,尤其当仿真系统很复杂的时候.stub需要实现和替代的代码一样的逻辑,而准确地再现复杂逻辑是一件很困难的事.而结果常常是需要调试stub!下面列出了不使用stub的理由:
Stub常常会很复杂,它们本身需要调试.
因为stub的复杂性,它们可能会很难维护
Stub不能很好的运用于细粒度测试.
不同的情况需要不同的策略.
一般而言,stub更适合代替代码中粗粒度的部分.你通常会愿意用stub代替成熟的外部系统,诸如,文件系统,到服务器的连接,数据库等.用stub替代对单一类的方法调用可以做到,但是比较难实现.
6.2 一个HTTP连接的例子
为了演示stub能做些什么,我们为一个简单的程序创建了一些stub,它根据URL打开了一个http连接,同时读取其中的信息.图6.1显示了一个程序例子,它通过HTTP连接远程web资源.
在这一章里我们的目标是通过用stub替换远程web资源来对getContent方法执行单元测试.
这种方法允许你独立地测试执行web资源的getContent方法.
关于stub最重要的一点是,getContent没有为接收stub而作修改.对于被测试的程序而言是透明的.为了实现这点,被替换的外围代码需要有定义完善的接口,并允许插入不同的实现.
让我们来以一个简单HTTP连接的例子来看看stub是如何运行的把.代码6.1中的样本程序给出了一个代码片断,它为给定的URL打开HTTP连接,读取找到的URL上的信息.设想一下,这个方法是你想对其执行单元测试的一个大项目的一部分.现在,让我们对这个方法进行单元测试吧.

6.2.1 选择一个替换方案
在案例程序中有两个可能的情况:远程web服务器位于开发平台的外围,或者,本身就是程序配置平台的一部分.不管怎样,为了能够对webClient类进行单元测试,你必须在开发平台上建立一个服务器.相对容易的解决办法是为其安装一个Apache测试服务器,在它的文档根目录下放一些测试web页面.这是典型的,广泛使用的替换方法.但它有缺陷:
依赖环境---在测试前确保运行环境已经准备好了.如果web服务器关闭了,但测试被执行了,结果必然是错误的.那时你便会试着检查出错的原因.接着,你发现代码工作正常—这只是运行环境的问题,导致一个错误的警告.这种事情既浪费时间又令人厌烦.所以,你在单元测试时,重要的一点是尽可能地控制测试执行中的环境,这样才能保证测试结果的可再现性.
分散的测试逻辑---测试逻辑被分散到两个不同的地方:一是在Junit TestCase,二是测试web页面.这两种资源都需要在测试中保持同步.
测试难以实现自动化—自动执行测试还是很困难,因为它需要在web服务器上自动配置web页面,自动启动web服务器,而完成这一切仅仅是为了运行单元测试.

幸运地,有一个更好的解决办法---使用嵌入式服务器.你在java中进行测试,所以最容易的选择就是使用可以嵌入test
Case类的Java web服务器.确实存在这样的好东西,它叫Jetty.
为什么是Jetty?因为它有较快的运行速度,它是轻量级的,而且在java中可以从test case中完全控制其运行.另外,它还是一个很好的web/servlet容器,可以在产品中使用它.这对于测试而言不是特别重要,但是使用最好的技术始终是一个好的策略.
使用Jetty能让你消除前文提到的不足之处:服务器从Junit test case开始运行,所有测试都在同一个位置用java编写,把test suite自动化也成了一个微不足道的问题.得益于Jetty的模块性,要做的事情只是用stub替换Jetty处理器,而不是替换整个服务器.
6.2.2 用Jetty作为嵌入式服务器
为了更好地了解如何从测试中建立和控制Jetty,我们实现了一个从Java代码中启动Jetty的简单例子.代码6.2演示如何从java中启动以及如何定义一个文档根目录(/)以启动服务文件.图6.3说明了当你运行程序并在URL http://localhost:8080上打开浏览器的结果.

代码6.2以嵌入模式启动Jetty――JettySample类


6.3 替换web服务器资源
现在你知道如何简单地启动和配置Jetty了,下一步我们把视线放在HTTP连接的testcase上,你会先写一个测试来证明可以调用一个有效的URL,并能取得它的内容。

6.3.1 建立第一个stub测试
为了证明webClient工作于有效的URL,你需要在测试前启动Jetty服务器,这些你能在Junit test case里setUp方法中实现。你还可以用tearDown方法停止服务器的运行。代码6.3列出了具体做法。
为了实现setUp和rearDown方法,你有两条解决之道。准备一个包含文本“it works”的静态页面。把该文本放在你的文档根目录中。另一种方法是,配置Jetty以使用你自己定制的处理器,它不用在文件系统的文件中取字符串,而是直接返回一个字符串。这是一个更强大的技术,即便远程http服务器在你的webclient客户应用程序上抛出一个错误代码的时候,你还是能对这个test case进行单元测试。
创建一个Jetty处理器
代码6.4显示了如何创建一个返回“it works”的jetty处理器。
每个test suite 启动和停止jetty各一次


6.4 替换连接
现在,你替换了服务器的资源。改为http连接又会如何呢?这样做会妨碍你有效的测试连接,但这没有关系,这一点不正是你真正的目标
。你真正感兴趣的是孤立地测试代码逻辑。在以后的阶段可以用功能测试或集成测试来检验连接。
当你需要不改变代码就替换连接的时候,你会发现自己很幸运,因为JDK的URL和HttpURLConnection类允许你引入自定义的协议处理器,处理任何类型的通讯协议。你可以使任何对HttpURLConnection类的调用指向你自己的测试类,这些类会返回测试中任何需要的东西。

6.4.1 创建自定义RUL协议处理器
为了实现自定义的URL协议处理器,你需要调用以下的JDK方法,并把它传递给自定义的URLStramHandlerFactory对象:
Java.net.setURLStreamHandlerFactory


第7章:用mock object孤立测试
孤立于其他方法和环境而单元测试每一个方法,这显然是个值得追求的目标。但如何实现呢?在第六章中你知道了stub技术是怎样把代码和环境隔离起来而进行单元测试的。那么诸如隔离调用其他类的方法此类的细粒度隔离又如何呢?可行吗?实现这些会不会需要付出很大的努力,从而抵消进行测试所带来的收益?
回答是:“可以实现的”这项技术叫做mock objects。假设你要单元测试每一个方法,mock objects策略允许你在可能的最细等级上进行单元测试,逐个方法地进行测试。
7.1 mock objects 简介
隔离测试有着巨大的好处,如可以测试还未写完的代码,另外,隔离测试能帮助团队单元测试代码的一部分,而无需等待全部代码的完成。
也许最大的好处在于编写专门测试单一方法的代码,免去了被测试的方法调用其他对象而带来的副作用。小就是美。编写小而专一的测试是有用的;小的测试容易理解,当代码的其他部分改变时也不会被破坏。记住,进行成组测试的一个好处是,它给了你重构的勇气――单元测试就像反对衰退的卫士。如果你的测试粒度比较大,那么一旦重构引入了一个bug,就会有一系列的测试失败;结果就是测试告诉你出现了一个错误,但你并不知道它在哪里。
Mock objects (简称mocks)非常适合把部分代码逻辑的测试同其他的代码隔离开来。Mocks替换了测试中你的方法协作的对象,从而提供了隔离层。从这个意义来说,它跟stub类似。
7.2 体验mock objects ; 一个简单例子
让我们体验一下第一个mock

7.3 把Mock objects 用作重构手法
有一些人曾经说过,单元测试应该对测试中的代码透明;你不应为了简化测试而改变运行时代码。这是错误的!单元测试是对运行时代码的最好应用,应该同其他运用同等看待。如果你的代码不能在测试中应用,你应该纠正代码。


第8章: 使用Cactus进行容器内测试
本章内容
对组件进行单元测试时用mock objects的缺点
介绍使用Cactus进行容器内测试
Cactus的工作原理
在恰当的时候进行优秀的设计,让程序要么不能运行,一旦运行起来就是正确运行
从本章开始,我们将研讨如何对J2EE组件进行单元测试.对组件进行单元测试要难于对普通Java类进行单元测试.组件要和它们的容器打交道,而容器只有在运行时才能提供服务.Junit并没有像其他J2EE组件那样被设计成在容器内执行,那么我们该如何测试组件呢?
本章介绍了一种对J2EE组件进行单元测试的方法:容器内单元测试,也称作集成单元测试.更确切地说,我们讲讨论使用cactus框架在容器内运行J2EE测试的好处和坏处.我们将展示,使用第7章介绍的mock objects方法能做到什么,而这种方法无能为力时容器内测试方法如何使得你可以编写集成单元测试.
8.1 对组件进行单元测试的问题
想象一下,你有一个web应用程序,它使用了servlet,你希望对sampleservlet servlet的isAuthenticated 方法进行单元测试.
为了能够测试这个方法,你需要得到一个合法的HttpServletRequest对象.不幸的是,不可能调用new HttpServletRequest 来创建一个可用的请求.HttpServletRequest 的生命周期是由容器管理的,无法单独使用Junit为isAuthenticated 方法编写测试.
定义: 组件/容器—组件是在容器内部执行的一段代码.容器则是为存放在内的组件提供有用服务的器皿.
幸运的是,有几种可供选择的解决方案,我们将在后续的小节中谈到这些方案.概括地说,我们将展现两种核心方案:用mock objects进行容器外测试和用Cactus进行容器内测试
这两种方法的一个变体就是使用stub容器,比如HttpUnit中的ServletUnit模块.不幸的是,我们没有找到J2EE容器的完整stub实现.ServletUnit只实现了一部分Servlet规约
Mock objects和Cactus都是可行的方法.在第7章中,我们讨论了mock objects这种容器外策略的优势和劣势.在本章中,我们将我们专注于Cactus这种容器内策略,并展示它的相对优势和劣势.第9章我们将专注于对servlet进行单元测试.在那一章中,我们将讨论当测试servlet的时候何时使用mock objects,何时使用Cactus.

8.2 用mock objects测试组件
让我们试着用mock objects来测试servlet,然后讨论这种方法得优势和劣势.在第7章中,你创建了你自己得mock objects.幸运的是,有好几个框架可以自动生成mock objects.在本章中,你将使用EasyMock(http://www.easymock.org)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值