JUnit
![](/icons/13675de.gif)
出现为开发人员带来了福音
![](/icons/13675dou2.gif)
遗憾
![](/icons/13675de.gif)
是
![](/icons/13675dou.gif)
许多人仍然认为学会 JUnit API
![](/icons/13675dou.gif)
编写几个测试
![](/icons/13675dou.gif)
最后得到
![](/icons/13675yi.gif)
个测试良好
![](/icons/13675de.gif)
应用
![](/icons/13675chengxu.gif)
就足够了
![](/icons/13675dou2.gif)
这种想法比不进行任何测试还要糟
![](/icons/13675dou.gif)
![](/icons/13675yinwei.gif)
这会导致对代码健康状态
![](/icons/13675de.gif)
误解
![](/icons/13675dou2.gif)
学习 JUnit 是测试中最容易
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
部分
![](/icons/13675dou2.gif)
编写优秀
![](/icons/13675de.gif)
测试则是较困难
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
个环节
![](/icons/13675dou2.gif)
本文将介绍
![](/icons/13675yi.gif)
些常见
![](/icons/13675de.gif)
JUnit 反模式
![](/icons/13675dou.gif)
并介绍说明如何解决它们
![](/icons/13675dou2.gif)
两个月前
![](/icons/13675dou.gif)
我和妻子决定在厨房里装上木镶板
![](/icons/13675dou2.gif)
这是我第
![](/icons/13675yi.gif)
次装修房子
![](/icons/13675dou.gif)
我带着
![](/icons/13675yi.gif)
股盲目乐观主义精神
![](/icons/13675dou.gif)
使用铁锤和钉子干起了装修
![](/icons/13675dou2.gif)
但这样做几乎是
![](/icons/13675yi.gif)
场灾难
![](/icons/13675dou.gif)
![](/icons/13675yinwei.gif)
我用不好铁锤
![](/icons/13675dou2.gif)
最后
![](/icons/13675dou.gif)
妻子不得不重新修整被我敲打得高低不平
![](/icons/13675de.gif)
镶板和出现
![](/icons/13675de.gif)
裂缝
![](/icons/13675dou2.gif)
在装修卧室时
![](/icons/13675dou.gif)
我认为已学到了
![](/icons/13675yi.gif)
些经验教训
![](/icons/13675dou.gif)
这次借来了岳父
![](/icons/13675de.gif)
气钉枪
![](/icons/13675dou2.gif)
仅用了装修厨房十分的
![](/icons/13675yi.gif)
![](/icons/13675de.gif)
时间
![](/icons/13675dou.gif)
就装修完了卧室
![](/icons/13675dou.gif)
但是气钉枪不能弥补我在其他方面
![](/icons/13675de.gif)
失误 —— 例如忘记了保持木板顶部
![](/icons/13675de.gif)
水平
![](/icons/13675dou.gif)
切割木板时切错了位置
![](/icons/13675dou.gif)
忘记检查木板
![](/icons/13675dou.gif)
将有裂纹
![](/icons/13675de.gif)
木板钉了上去
![](/icons/13675dou.gif)
等等
![](/icons/13675dou2.gif)
还出现了其他许多问题
![](/icons/13675dou.gif)
这些问题幸好都被细心
![](/icons/13675de.gif)
妻子注意到了
![](/icons/13675dou2.gif)
通过此事
![](/icons/13675dou.gif)
我认识到:气钉枪不如
![](/icons/13675yi.gif)
个木匠
![](/icons/13675dou2.gif)
JUnit:气钉枪式测试工具
我认为
![](/icons/13675dou.gif)
JUnit 很像爸爸
![](/icons/13675de.gif)
气钉枪
![](/icons/13675dou2.gif)
JUnit 出现的前
![](/icons/13675dou.gif)
测试不是不可能
![](/icons/13675de.gif)
![](/icons/13675dou.gif)
但是非常困难
![](/icons/13675dou2.gif)
事实上
![](/icons/13675dou.gif)
它困难到了致使通常没有人愿意进行测试
![](/icons/13675dou2.gif)
即使进行测试
![](/icons/13675dou.gif)
也仅仅是对那些看起来特别复杂或脆弱
![](/icons/13675dou.gif)
以致人们有理由进行额外测试
![](/icons/13675de.gif)
那部分
![](/icons/13675dou2.gif)
JUnit 就是专门解决此问题
![](/icons/13675de.gif)
工具
![](/icons/13675dou2.gif)
这里不可告人
![](/icons/13675de.gif)
秘密是
![](/icons/13675dou.gif)
此现象致使许多编程人员实际上乐于 编写
![](/icons/13675yi.gif)
些测试
![](/icons/13675dou2.gif)
这样就造成了编程人员编写测试
![](/icons/13675dou.gif)
而客户期盼测试
![](/icons/13675de.gif)
情形
![](/icons/13675dou2.gif)
尽管仍有
![](/icons/13675yi.gif)
些坚持者
![](/icons/13675dou.gif)
但多数客户现在开始倾向于使用我们在测试领域
![](/icons/13675de.gif)
新霸主 JUnit
![](/icons/13675dou2.gif)
(有关热爱测试
![](/icons/13675de.gif)
更多信息
![](/icons/13675dou.gif)
请参阅 参考资料)
![](/icons/13675dou2.gif)
问题是
![](/icons/13675dou.gif)
JUnit 不是万能药
![](/icons/13675dou.gif)
它是
![](/icons/13675yi.gif)
种名副其实
![](/icons/13675de.gif)
工具
![](/icons/13675dou2.gif)
像其他优秀
![](/icons/13675de.gif)
工具
![](/icons/13675yi.gif)
样(JUnit 是最优秀
![](/icons/13675de.gif)
工具的
![](/icons/13675yi.gif)
)
![](/icons/13675dou.gif)
JUnit 只做
![](/icons/13675yi.gif)
件事情
![](/icons/13675dou.gif)
并且能出色地完成
![](/icons/13675dou.gif)
它提供
![](/icons/13675yi.gif)
个用于执行测试
![](/icons/13675de.gif)
框架
![](/icons/13675dou2.gif)
具体表现在:
JUnit 提供
![](/icons/13675yi.gif)
个用于编写测试
![](/icons/13675de.gif)
模板
![](/icons/13675dou.gif)
该模板可以安装、执行和卸载
它允许您在层次结构中组织测试
它允许您自动而又方便地执行测试
它减少了来自执行过程中
![](/icons/13675de.gif)
测试报告量
![](/icons/13675dou.gif)
允许使用同
![](/icons/13675yi.gif)
测试套件中
![](/icons/13675de.gif)
区别测试操作
![](/icons/13675chengxu.gif)
![](/icons/13675dou2.gif)
尽管 JUnit 功能强大
![](/icons/13675dou.gif)
使用起来很简单
![](/icons/13675dou.gif)
但是
![](/icons/13675dou.gif)
它也存在许多不足的处
![](/icons/13675dou.gif)
需要其他工具来填补这些缺陷
![](/icons/13675dou2.gif)
以下是 JUnit 无法做到
![](/icons/13675de.gif)
:
对被测试
![](/icons/13675de.gif)
单元自动生成测试
提供覆盖条件
编写了劣质
![](/icons/13675de.gif)
测试时进行提示
阐明
![](/icons/13675de.gif)
观点
Robert Binder 编写了
![](/icons/13675yi.gif)
本好书
![](/icons/13675dou.gif)
名称为 Testing Object-Oriented
![](/icons/13675System.gif)
s: Models, Patterns, and Tools
![](/icons/13675dou2.gif)
Binder 是
![](/icons/13675yi.gif)
位少有
![](/icons/13675de.gif)
天才人物 ——
![](/icons/13675yi.gif)
个测试圣人
![](/icons/13675dou2.gif)
作为
![](/icons/13675yi.gif)
本测试方面
![](/icons/13675de.gif)
参考资料
![](/icons/13675dou.gif)
该书
![](/icons/13675de.gif)
价值是无法衡量
![](/icons/13675de.gif)
![](/icons/13675dou2.gif)
Binder 在本书
![](/icons/13675de.gif)
开头再次谈及 Scott Meyers
![](/icons/13675de.gif)
测试问题
![](/icons/13675dou2.gif)
这个问题就是为 Triangle 对象编写单元测试
![](/icons/13675dou2.gif)
Java™ 技术实现了
![](/icons/13675yi.gif)
个采用 3个边长
![](/icons/13675de.gif)
构造
![](/icons/13675hanshu.gif)
![](/icons/13675dou2.gif)
每
![](/icons/13675yi.gif)
边各有
![](/icons/13675yi.gif)
个 getters 和
![](/icons/13675set.gif)
ters
![](/icons/13675dou2.gif)
该技术实现有 3种思路方法:isIsosceles
![](/icons/13675kh.gif)
、isScalene
![](/icons/13675kh.gif)
和 isEquilateral
![](/icons/13675kh.gif)
![](/icons/13675dou.gif)
其中每
![](/icons/13675yi.gif)
种思路方法都可以返回 true 或 false
![](/icons/13675dou.gif)
具体情况取决于 3角形
![](/icons/13675de.gif)
配置
![](/icons/13675dou2.gif)
triangle 还是 Polygon 类型
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
个子类, 后者由 Figure 类派生而来
![](/icons/13675dou2.gif)
Figure 是代表对象
![](/icons/13675de.gif)
抽象类
![](/icons/13675dou.gif)
该类可以通过光栅显示描绘
![](/icons/13675dou2.gif)
现在面临
![](/icons/13675de.gif)
挑战是如何编写此类
![](/icons/13675de.gif)
测试
![](/icons/13675dou2.gif)
Binder 从 Meyers
![](/icons/13675de.gif)
原始
![](/icons/13675chengxu.gif)
解决方案中列出了 33 个测试
![](/icons/13675dou.gif)
并提供了 32 个和面向对象
![](/icons/13675de.gif)
问题属性有密切关系
![](/icons/13675de.gif)
测试
![](/icons/13675dou2.gif)
所以现在
![](/icons/13675yi.gif)
共有 65 个测试
![](/icons/13675dou2.gif)
除非是影响生命安全
![](/icons/13675de.gif)
重要软件Software
![](/icons/13675dou.gif)
否则您可能从来不会如此详细地测试代码
![](/icons/13675dou.gif)
也不会了解到原来它是如此测试
![](/icons/13675de.gif)
![](/icons/13675dou2.gif)
原因不是您有生理缺陷或者懒惰
![](/icons/13675dou2.gif)
而是您没有受过测试方面
![](/icons/13675de.gif)
训练
![](/icons/13675dou.gif)
还
![](/icons/13675yinwei.gif)
您将专用开发时间都消耗在了编程窍门技巧上
![](/icons/13675dou.gif)
而不是消耗在测试能力上
![](/icons/13675dou2.gif)
该如何办呢?JUnit 可让测试变得简单易行
![](/icons/13675dou2.gif)
反模式
本部分将介绍几个反模式
![](/icons/13675dou.gif)
其中
![](/icons/13675de.gif)
![](/icons/13675cuowu.gif)
现象是我们经常遇到
![](/icons/13675de.gif)
或易犯
![](/icons/13675de.gif)
![](/icons/13675dou2.gif)
愉快路径测试
愉快路径测试 可以验证被测系统
![](/icons/13675de.gif)
行为是否为所期望
![](/icons/13675de.gif)
行为
![](/icons/13675dou2.gif)
它们遵循每个正确
![](/icons/13675de.gif)
执行路径
![](/icons/13675dou2.gif)
在功能测试中
![](/icons/13675dou.gif)
愉快路径和实际用例相同或相近
![](/icons/13675dou2.gif)
在单元测试中
![](/icons/13675dou.gif)
它和实际用例相同或更小
![](/icons/13675dou.gif)
![](/icons/13675yinwei.gif)
单元服从于“单
![](/icons/13675yi.gif)
职责原则”
![](/icons/13675dou.gif)
您是测试它
![](/icons/13675de.gif)
单
![](/icons/13675yi.gif)
职责
![](/icons/13675dou2.gif)
实际上
![](/icons/13675dou.gif)
愉快路径测试并不是
![](/icons/13675yi.gif)
个反模式
![](/icons/13675dou2.gif)
反模式是指在进行愉快路径测试时开发
![](/icons/13675chengxu.gif)
![](/icons/13675de.gif)
停止行为
![](/icons/13675dou2.gif)
愉快路径不测试系统
![](/icons/13675de.gif)
![](/icons/13675cuowu.gif)
部分(不愉快路径)
![](/icons/13675dou2.gif)
编写代码时
![](/icons/13675dou.gif)
通常考虑使用愉快路径进行编写
![](/icons/13675dou2.gif)
甚至在头脑中用
![](/icons/13675yi.gif)
些愉快路径数据对它进行测试
![](/icons/13675dou2.gif)
边界条件将等待未测试
![](/icons/13675de.gif)
、范围的外数据
![](/icons/13675dou.gif)
允许它们将您
![](/icons/13675de.gif)
应用
![](/icons/13675chengxu.gif)
带到其管辖范围的内
![](/icons/13675dou2.gif)
假设您正在编写
![](/icons/13675yi.gif)
个包含思路方法 eval
![](/icons/13675de.gif)
Factorial 类
![](/icons/13675dou.gif)
该思路方法携带
![](/icons/13675int.gif)
并返回该
![](/icons/13675de.gif)
阶乘
![](/icons/13675dou2.gif)
![](/icons/13675yi.gif)
个愉快路径测试会确认 Factorial.eval(3) 返回
![](/icons/13675de.gif)
是 6
![](/icons/13675dou2.gif)
此代码
![](/icons/13675de.gif)
实现不正确
![](/icons/13675dou.gif)
但它仍返回正确
![](/icons/13675de.gif)
结果(误报)
![](/icons/13675dou.gif)
这种几率非常小:
public
Factorial {
public
eval(
_num) {
(_num
1) {
1; }
_num * eval(_num - 1);
}
}
有些人会对此测试感到满意并继续操作
![](/icons/13675dou.gif)
但是
![](/icons/13675dou.gif)
请考虑下面这个实现:
public
Factorial {
public
eval(
_num) {
6;
}
}
出现误报(false positive)会怎样呢?如果您从未接触过由测试驱动
![](/icons/13675de.gif)
开发(请参阅 参考资料)
![](/icons/13675dou.gif)
那么您可能也会认为人人都能编写如此头脑简单
![](/icons/13675de.gif)
实现
![](/icons/13675dou2.gif)
测试驱动
![](/icons/13675de.gif)
开发 (TDD) 中
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
个练习就是首先编写测试
![](/icons/13675dou.gif)
然后执行可能运行
![](/icons/13675de.gif)
最简单
![](/icons/13675de.gif)
操作 —— 如本例中
![](/icons/13675return.gif)
6
![](/icons/13675dou2.gif)
即使没有使用 TDD 思路方法执行操作
![](/icons/13675dou.gif)
并在正确
![](/icons/13675de.gif)
实现中出现
![](/icons/13675yi.gif)
个
![](/icons/13675cuowu.gif)
![](/icons/13675dou.gif)
您仍会得到误报
![](/icons/13675dou2.gif)
请考虑以下实现:
public
Factorial {
public
eval(
_num) {
(_num
1) {
1; }
_num + eval(_num - 1);
}
}
除了数字
![](/icons/13675de.gif)
序列是相加
![](/icons/13675de.gif)
![](/icons/13675dou.gif)
而不是相乘
![](/icons/13675de.gif)
的外
![](/icons/13675dou.gif)
这个算法和第
![](/icons/13675yi.gif)
个算法几乎是相同
![](/icons/13675de.gif)
![](/icons/13675dou.gif)
对于值 3 和值 1(恰好出现这样
![](/icons/13675yi.gif)
个值)
![](/icons/13675dou.gif)
返回
![](/icons/13675de.gif)
值是
![](/icons/13675yi.gif)
样
![](/icons/13675de.gif)
![](/icons/13675dou.gif)
但是
![](/icons/13675dou.gif)
对于其他任何值则会失败
![](/icons/13675dou2.gif)
关键是碰巧通过
![](/icons/13675yi.gif)
个测试并不困难
![](/icons/13675dou2.gif)
这就是为什么
![](/icons/13675yi.gif)
定要进行两次以上
![](/icons/13675de.gif)
愉快路径测试
![](/icons/13675dou2.gif)
测试两次可以明显地减少
![](/icons/13675yi.gif)
致通过
![](/icons/13675de.gif)
机率
![](/icons/13675dou2.gif)
尤其是测试值是 orthogonal (相互独立或没有关系)
![](/icons/13675de.gif)
情况下
![](/icons/13675dou2.gif)
例如
![](/icons/13675dou.gif)
编写
![](/icons/13675yi.gif)
个值为 3 和 5
![](/icons/13675de.gif)
测试
![](/icons/13675dou.gif)
将很就可以看出前面
![](/icons/13675de.gif)
两个实现是
![](/icons/13675cuowu.gif)
![](/icons/13675de.gif)
![](/icons/13675dou2.gif)
确认测试和边界测试
还需要考虑其他两个测试类型:validity(或 do
![](/icons/13675main.gif)
)和 boundary
![](/icons/13675dou2.gif)
前者声明无效数据(或域外数据)
![](/icons/13675de.gif)
正确行为
![](/icons/13675dou.gif)
后者是愉快路径测试
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
种形式
![](/icons/13675dou.gif)
但它声明实现在域
![](/icons/13675de.gif)
边界上可以正确地运行
![](/icons/13675dou2.gif)
在这个举例中
![](/icons/13675dou.gif)
请考虑在
![](/icons/13675diaoyong.gif)
Factorial.eval(-3) 时
![](/icons/13675dou.gif)
将会发生什么情况
![](/icons/13675dou2.gif)
很有可能用尽堆栈空间
![](/icons/13675dou.gif)
造成
![](/icons/13675chengxu.gif)
崩溃
![](/icons/13675dou2.gif)
当然 -3 不是
![](/icons/13675yi.gif)
个有效
![](/icons/13675de.gif)
输入
![](/icons/13675dou.gif)
所以使用它毫无意义
![](/icons/13675dou2.gif)
但是
![](/icons/13675dou.gif)
在正确和
![](/icons/13675cuowu.gif)
的间还有
![](/icons/13675yi.gif)
个中间思路方法
![](/icons/13675dou.gif)
称为 IllegalArgumentException
![](/icons/13675dou.gif)
演示如下:
public
Factorial {
public
eval(
_num) {
(_num < 1) {
throw
IllegalArgumentException(
"Parameter must be greater than 0: " + _num);
}
(_num
1) {
1; }
_num * eval(_num - 1);
}
}
编写了阶乘代码后
![](/icons/13675dou.gif)
您可能发现该代码仍有
![](/icons/13675cuowu.gif)
![](/icons/13675dou2.gif)
所以
![](/icons/13675dou.gif)
让我们谈
![](/icons/13675yi.gif)
下边界测试
![](/icons/13675dou2.gif)
如果存在
![](/icons/13675yi.gif)
个边界
![](/icons/13675dou.gif)
那么输入参数为 0
![](/icons/13675dou.gif)
这是
![](/icons/13675yi.gif)
个有效
![](/icons/13675de.gif)
输入
![](/icons/13675dou.gif)
从数学上说
![](/icons/13675dou.gif)
0
![](/icons/13675de.gif)
阶乘是 1
![](/icons/13675dou2.gif)
执行前面
![](/icons/13675de.gif)
实现会导致测试失败
![](/icons/13675dou.gif)
![](/icons/13675yinwei.gif)
您希望
![](/icons/13675de.gif)
返回值是 1
![](/icons/13675dou.gif)
但得到
![](/icons/13675de.gif)
却是 IllegalArgumentException
![](/icons/13675dou2.gif)
还应该检查边界
![](/icons/13675de.gif)
另
![](/icons/13675yi.gif)
边 -1
![](/icons/13675dou.gif)
以验证可以得到期望
![](/icons/13675de.gif)
IllegalArgumentException
![](/icons/13675dou.gif)
而不是
![](/icons/13675yi.gif)
个整数
![](/icons/13675dou2.gif)
对其他边界
![](/icons/13675de.gif)
相应测试将留做练习供您操练
![](/icons/13675dou2.gif)
提示:如果执行 Factorial.eval(100) 将会发生什么情况?
简单测试
和愉快路径反模式
![](/icons/13675yi.gif)
样
![](/icons/13675dou.gif)
简单测试反模式讲
![](/icons/13675de.gif)
不是有关“是什么”而是“不是 什么”
![](/icons/13675dou2.gif)
若开发人员没有经验
![](/icons/13675dou.gif)
并且代码难以测试
![](/icons/13675dou.gif)
则通常会出现这种症状
![](/icons/13675dou2.gif)
结果
![](/icons/13675dou.gif)
您会看到对容易测试 (equals 和 toString 往往很突出
![](/icons/13675dou.gif)
参见清单 1)
![](/icons/13675de.gif)
内容进行多次
![](/icons/13675de.gif)
测试
![](/icons/13675dou.gif)
而被测单元
![](/icons/13675de.gif)
真正逻辑却被忽略了
![](/icons/13675dou2.gif)
结果出现了许多不能检测系统
![](/icons/13675de.gif)
传递测试
![](/icons/13675dou.gif)
这会导致对代码健康状态
![](/icons/13675de.gif)
误解
![](/icons/13675dou2.gif)
清单 1.
![](/icons/13675yi.gif)
些容易测试
![](/icons/13675de.gif)
签名
testEqualsReflexive![](/icons/13675kh.gif)
testEqualsSymmetric![](/icons/13675kh.gif)
testEqualsTransitive![](/icons/13675kh.gif)
testEqualsOnNullParameter![](/icons/13675kh.gif)
testEqualsWorksMoreThanOnce![](/icons/13675kh.gif)
testEqualsFailsOnSub![](/icons/13675class.gif)
![](/icons/13675kh.gif)
testEqualsIsStillReflexive![](/icons/13675kh.gif)
进行系统测试的所以困难
![](/icons/13675dou.gif)
是
![](/icons/13675yinwei.gif)
您经常尝试测试某个思路方法
![](/icons/13675dou.gif)
而不是检测某个装置
![](/icons/13675dou2.gif)
假设您要测试
![](/icons/13675yi.gif)
个堆栈
![](/icons/13675de.gif)
实现
![](/icons/13675dou.gif)
那么您
![](/icons/13675de.gif)
测试签名可能如清单 2 所示
![](/icons/13675dou2.gif)
清单 2. 用于堆栈单元测试
![](/icons/13675de.gif)
可能测试签名
testPopHappyPath
;
testPopEmptyStack
;
testPushHappyPath
;
testPushFullStack
;
testPeek
;
其中有些测试很容易
![](/icons/13675dou.gif)
如清单 3 所示
![](/icons/13675dou2.gif)
清单 3. 用于空堆栈
![](/icons/13675de.gif)
单元测试
public void testPopEmptyStack
{
Stack stackUT =
Stack
;
assertEquals(0, stackUT.getSize
);
try {
stackUT.pop
;
fail("Expected StackUnderflowException");
} catch (StackUnderflowException _expected) {}
}
但是
![](/icons/13675dou.gif)
如何测试 push
![](/icons/13675de.gif)
愉快路径呢?
清单 4. 用于 stack.push
![](/icons/13675de.gif)
元单测试
public void testPushHappyPath
{
Stack stackUT =
Stack
;
Object item =
Object
;
stackUT.push(item);
// now what?
}
这是测试单元实现
![](/icons/13675de.gif)
常见
![](/icons/13675cuowu.gif)
![](/icons/13675dou.gif)
而不是单元和其客户机签定
![](/icons/13675de.gif)
契约
![](/icons/13675dou2.gif)
假设 push 思路方法
![](/icons/13675de.gif)
实现方式如下:
public
Stack {
private List elements;
...
public void push(Object _element) {
elements.add(_element);
}
}
您需要进行这
![](/icons/13675yi.gif)
测试来验证 elements List 现在是否含有 push 添加
![](/icons/13675de.gif)
Object
![](/icons/13675dou2.gif)
所以
![](/icons/13675dou.gif)
您要编写如下测试:
public void testPushHappyPath
{
Stack stackUT =
Stack
;
Object expectedElement =
Object
;
stackUT.push(expectedElement);
List elements = stackUT.getElementsList
;
assertEquals(1, elements.size
);
assertEquals(expectedElement, elements.get(0));
}
其中
![](/icons/13675de.gif)
问题是破坏了封装
![](/icons/13675dou.gif)
原因是公开了被测单元
![](/icons/13675de.gif)
内幕
![](/icons/13675dou2.gif)
相反
![](/icons/13675dou.gif)
要测试 push 是否将对象放入了列表
![](/icons/13675dou.gif)
您应测试堆栈和客户机签定
![](/icons/13675de.gif)
契约
![](/icons/13675dou2.gif)
J.B. Rainsberger 将此称为测试装置 (fixture)
![](/icons/13675dou2.gif)
现在
![](/icons/13675dou.gif)
您
![](/icons/13675de.gif)
测试如清单 5 所示
![](/icons/13675dou2.gif)
清单 5. 用于堆栈装置
![](/icons/13675de.gif)
单元测试
public void testPushPop
{
Stack stackUT =
Stack
;
Object expectedElement =
Object
;
assertEquals(expectedElement, stackUT.push(expectedElement).pop
;
assertTrue(stackUT.isEmpty
);
}
public void testFILO
{
Stack stackUT =
Stack
;
Object expectedOne =
Object
;
Object expectedTwo =
Object
;
stackUT.push(expectedOne);
stackUT.push(expectedTwo);
assertEquals(expectedTwo, stackUT.pop
);
assertEquals(expectedOne, stackUT.pop
);
assertTrue(stackUT.isEmpty
);
}
您将不会再破坏封装
![](/icons/13675dou.gif)
原因是您没有声明单元在封装中如何运行
![](/icons/13675dou2.gif)
相反
![](/icons/13675dou.gif)
您充分利用了该装置显示
![](/icons/13675de.gif)
严密内聚性
![](/icons/13675dou2.gif)
拥有可以推动但不能弹出
![](/icons/13675de.gif)
堆栈没有任何意义
![](/icons/13675dou.gif)
因此
![](/icons/13675dou.gif)
您可以将这些思路方法作为堆栈暴露给其客户机
![](/icons/13675de.gif)
契约
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
部分进行测试
![](/icons/13675dou2.gif)
当编写代码时
![](/icons/13675dou.gif)
应考虑到这个契约 —— 您将要编写
![](/icons/13675de.gif)
特定内容都将暴露给它
![](/icons/13675de.gif)
客户机
![](/icons/13675dou.gif)
无论此内容是
![](/icons/13675yi.gif)
个思路方法、
![](/icons/13675yi.gif)
个类
![](/icons/13675dou.gif)
还是
![](/icons/13675yi.gif)
个和类交互
![](/icons/13675de.gif)
组
![](/icons/13675dou2.gif)
该契约是您要测试
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
个内容
![](/icons/13675dou.gif)
而不是实现细节
![](/icons/13675dou2.gif)
以这种形式进行测试将有助于该契约
![](/icons/13675de.gif)
形式化
![](/icons/13675dou.gif)
使该契约更为明确并能够通过测试得到很好
![](/icons/13675de.gif)
定义
![](/icons/13675dou.gif)
而不会处于不确定和非正式状态
![](/icons/13675dou2.gif)
过度复杂
![](/icons/13675de.gif)
测试
当测试明显正确时
![](/icons/13675dou.gif)
该测试通常会成功
![](/icons/13675dou2.gif)
如果测试很复杂
![](/icons/13675dou.gif)
以致于不能立即断定它是否正确
![](/icons/13675dou.gif)
那么您将无法知道该测试是否
![](/icons/13675yinwei.gif)
是
![](/icons/13675cuowu.gif)
![](/icons/13675de.gif)
测试(甚至更糟
![](/icons/13675de.gif)
是不知道它是否正被
![](/icons/13675cuowu.gif)
地传递)而导致失败
![](/icons/13675dou2.gif)
当被测系统需要
![](/icons/13675yi.gif)
个复杂
![](/icons/13675de.gif)
设置或暴露需要拆分
![](/icons/13675de.gif)
复杂数据结构时
![](/icons/13675dou.gif)
通常会出现这种情况
![](/icons/13675dou2.gif)
请考虑这样
![](/icons/13675yi.gif)
个例子
![](/icons/13675dou.gif)
在这个例子中有
![](/icons/13675yi.gif)
个代码
![](/icons/13675dou.gif)
该代码携带
![](/icons/13675yi.gif)
些客户数据并将其写出
![](/icons/13675dou.gif)
保存到
![](/icons/13675yi.gif)
个有固定记录
![](/icons/13675de.gif)
文件中
![](/icons/13675dou.gif)
以便在旧式系统中使用
![](/icons/13675dou2.gif)
您大概不会对记录是否为正确格式
![](/icons/13675de.gif)
测试感兴趣 —— 在这些方面
![](/icons/13675dou.gif)
您已经进行了许多测试
![](/icons/13675dou2.gif)
您要测试
![](/icons/13675de.gif)
是记录中是否存在正确
![](/icons/13675de.gif)
数据
![](/icons/13675dou2.gif)
在这种情况下
![](/icons/13675dou.gif)
很容易看到如清单 6 所示
![](/icons/13675de.gif)
测试
![](/icons/13675dou2.gif)
清单 6. 过度复杂
![](/icons/13675de.gif)
测试
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import junit.framework.TestCase;
public
RecordTest extends TestCase {
public void testRecordContainsCorrectCustomerData
{
//
up
String expectedName = "Estragon";
expectedId = 1001;
String
expectedItemNames = {"A man", "A plan", "A canal", "Suez"};
Customer customer =
Customer(expectedId, expectedName, expectedItemNames);
// execute
BillingCenter.processCustomer(customer);
// assert results
File file =
File("customer.rec");
assertTrue(file.exists
);
FileInputStream fis =
FileInputStream(file);
ByteArrayOutputStream baos =
ByteArrayOutputStream
;
buffer =
[16];
numRead;
while ((numRead = fis.read(buffer)) >= 0) {
baos.write(buffer, 0, numRead);
}
record = baos.toByteArray
;
assertEquals(128, record.length); // exactly _disibledevent= 0; i < 4;
i) {
assertEquals(expectedItemNames[i], record.getItemName(i));
}
}
}
现在
![](/icons/13675dou.gif)
测试代码清楚地表示出了该测试
![](/icons/13675de.gif)
意图
![](/icons/13675dou2.gif)
毫无疑问
![](/icons/13675dou.gif)
此测试是正确
![](/icons/13675de.gif)
![](/icons/13675dou.gif)
![](/icons/13675yinwei.gif)
它已经完成了设置预期值
![](/icons/13675dou.gif)
![](/icons/13675diaoyong.gif)
被测试
![](/icons/13675de.gif)
系统
![](/icons/13675dou.gif)
![](/icons/13675diaoyong.gif)
getter 和作出声明
![](/icons/13675dou2.gif)
该逻辑被应用到了 RecordFileFacade 和 RecordFacade 类中
![](/icons/13675dou2.gif)
RecordFileFacade 负责从文件中读取数据
![](/icons/13675dou.gif)
并成批将它们送入记录中
![](/icons/13675dou2.gif)
RecordFacade 负责解析每条记录
![](/icons/13675dou.gif)
并通过 Java 语言友好测试思路方法公开这些这些数据
![](/icons/13675dou2.gif)
这个测试
![](/icons/13675de.gif)
另
![](/icons/13675yi.gif)
个优点是 RecordFileFacade 和 RecordFacade 现在也能够测试
![](/icons/13675dou2.gif)
当拆分记录
![](/icons/13675de.gif)
逻辑保存在该测试中时
![](/icons/13675dou.gif)
将无法对其进行测试
![](/icons/13675dou2.gif)
最好将该逻辑应用到基础结构中
![](/icons/13675dou2.gif)
![](/icons/13675yi.gif)
个优秀
![](/icons/13675de.gif)
测试
![](/icons/13675chengxu.gif)
应当满足以下条件:
设置
声明预期结果
练习被测试
![](/icons/13675de.gif)
单元
获得实际结果
声明实际结果是否和预期结果相符
![](/icons/13675yi.gif)
个测试良好
![](/icons/13675de.gif)
应用
![](/icons/13675chengxu.gif)
不仅仅包含应用
![](/icons/13675chengxu.gif)
代码和测试
![](/icons/13675dou2.gif)
![](/icons/13675yi.gif)
定数量
![](/icons/13675de.gif)
基础结构代码可以充当测试
![](/icons/13675chengxu.gif)
和被测系统的间
![](/icons/13675de.gif)
适配器
![](/icons/13675dou2.gif)
此用途有两个:其
![](/icons/13675yi.gif)
![](/icons/13675dou.gif)
可以允许测试清楚地表示其意图
![](/icons/13675dou.gif)
其 2
![](/icons/13675dou.gif)
通过将复杂
![](/icons/13675de.gif)
代码抽象到独立层中
![](/icons/13675dou.gif)
还能够为该层编写测试
![](/icons/13675dou2.gif)
结束语
在许多思路方法中
![](/icons/13675dou.gif)
使用 JUnit 进行测试更方便
![](/icons/13675dou2.gif)
测试编写代码越来越趋向于进行坏
![](/icons/13675de.gif)
测试和好
![](/icons/13675de.gif)
测试
![](/icons/13675dou2.gif)
但是
![](/icons/13675dou.gif)
1,000 个坏
![](/icons/13675de.gif)
传递测试比不进行试测更糟糕
![](/icons/13675dou.gif)
![](/icons/13675yinwei.gif)
坏
![](/icons/13675de.gif)
测试会给您
![](/icons/13675yi.gif)
个
![](/icons/13675cuowu.gif)
![](/icons/13675de.gif)
自信意识
![](/icons/13675dou2.gif)
编写测试时
![](/icons/13675dou.gif)
![](/icons/13675yi.gif)
定要注意所编写测试
![](/icons/13675de.gif)
质量:
不要仅测试愉快路径
![](/icons/13675dou.gif)
还要测试边界条件和范围的外
![](/icons/13675de.gif)
值
不要测试实现
![](/icons/13675dou.gif)
而是要测试装置
不要使您
![](/icons/13675de.gif)
测试代码比被测代码更复杂
总的
![](/icons/13675dou.gif)
要通过不懈
![](/icons/13675de.gif)
努力来扩展您
![](/icons/13675de.gif)
测试窍门技巧
![](/icons/13675dou.gif)
使的成为专业开发
![](/icons/13675de.gif)
![](/icons/13675yi.gif)
部分
![](/icons/13675dou2.gif)
在测试工作方面
![](/icons/13675dou.gif)
不要将全部精力都用在编程窍门技巧上