前文提到,在UI自动化之外,我们着力探索了如何实施单元测试(unit test)
相对于UI自动化,单元测试方面的实践还是不够充分的,因此,这里也只是小结一下我们的经验
概述
首先明确一下,此处单元测试概念与经典意义有所不同,泛指所有:
由开发工程师编写的,可以在开发本地一键运行的,运行时间在分钟级别的测试用例,用例执行会依赖不多的,但往往也是稳定可靠的外部环境
测试框架一般使用TestNg而不是JUnit,主要原因在于TestNg的 DataProvider 功能很给力,非常适合用例须要覆盖多分支的场景
用例组织原则:
- 一个测试类对应一个功能类: funcOneTest.java 对应于 funcOne.java
- 若干个测试方法对应一个功能方法:test_funcOne_smoke() & test_funcOne_normal() & test_funcOne_error() 对应于 funcOne()
用例分类约定
还是以 超市购物 为背景,写几个Demo用例
Δ冒烟型用例 – 甲
|
@Test
(
description
=
"getNewestItems_冒烟_获取最新商品并检查若干关键属性"
)
public
void
test_getNewestItems_smoke
(
)
{
List
<ItemVo>
itemList
=
itemBean
.
getNewestItems
(
1
)
;
Assert
.
assertTrue
(
itemList
.
size
(
)
==
16
,
"size应该是16"
)
;
for
(
ItemVo
vo
:
itemList
)
{
Assert
.
assertTrue
(
vo
.
getName
(
)
!=
null
,
"name不能为空"
)
;
Assert
.
assertTrue
(
vo
.
getPrice
(
)
!=
null
,
"price不能为空"
)
;
}
}
|
说明:
顾名思义,就是在用例中简单调用一下被测方法(method),主要是为了跑通流程,拒绝Block级别问题
实践中,推荐 伴随着功能开发,随时编写冒烟用例,功能代码与冒烟用例一起提交代码库
对于一些复杂的功能,功能开发的过程也是一个持续重构的过程,编码前期完成的冒烟用例,可以有效起到安全网作用
Δ冒烟型用例 – 乙
|
@Test
(
description
=
"enter_and_leave_market_冒烟_进入与离开超市"
)
public
void
test_enter_and_leave_market_smoke
(
)
{
Custom
tom
=
new
Custom
(
"Tom"
)
;
tom
.
enterMarket
(
)
;
Assert
.
assertTrue
(
Custom
.
isAtMarket
(
tom
)
,
"tom应该在超市内"
)
;
tom
.
leaveMarket
(
)
;
Assert
.
assertFalse
(
Custom
.
isAtMarket
(
tom
)
,
"tom应该不在超市"
)
;
}
|
说明:
这个用例把进入超市及离开超市这两个(强相关的)接口串起来了,目的在于走通流程
实践中,建议 将新模块(函数)与其强相关的模块(函数)尽早进行简单的集成测试以提前发现一些问题
Δ正常流程用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Test
(
description
=
"addToCart_正常流程_往购物车内添加各种类型数目的商品"
,
dataProvider
=
"test_addToCart_normal_data"
)
@Rollback
public
void
test_addToCart_normal
(
String
caseNote
,
long
itemId
,
int
count
)
{
Custom
tom
=
new
Custom
(
"Tom"
)
;
this
.
setCustom
(
tom
)
;
cartBean
.
addToCart
(
itemId
,
count
)
;
Item
item
=
tom
.
getCart
.
getItems
.
get
(
0
)
;
// 获取购物车中的第一项商品
Assert
.
assertEquals
(
item
.
getId
,
itemId
,
"itemId is wrong"
)
;
Assert
.
assertEquals
(
item
.
getCount
,
count
,
"count is wrong"
)
;
}
@DataProvider
public
Object
[
]
[
]
test_addToCart_normal_data
(
)
{
return
new
Object
[
]
[
]
{
// caseNote, itemId, count
{
"Milk - just a dozen"
,
39001L
,
12
,
}
,
{
"Bread - huge number"
,
116001L
,
999
}
,
{
"Bean - less then 10"
,
1018100L
,
2
}
,
}
;
}
|
说明:
- 这个示例代码演示了如何测试addToCart()的功能,假定这个方法内部有十分复杂的业务逻辑,我们须要覆盖各种场景
- 可以看到,正常流程用例与冒烟用例其实差不多,不同的是,正常流程用例会覆盖更多分支,冒烟用例则一般是走通流程就行
- 这个用例使用了@Rollback标签,用例执行后会回滚数据,而不会真正往数据库内插入数据;这个功能十分有用,可以大大减少数据准备与清理的工作;至于@Rollback背后的实现原理,此处暂时按下不表
- 测试参数使用TestNg的@DataProvider组织起来,每一行都是一组测试数据,覆盖一种测试分支
- 测试参数的第一列建议设置为caseNote,简单阐述用例的意图,可以有效提升用例可读性
Δ异常流程用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Test
(
description
=
"addToCart_异常流程_往购物车内添加参数非法的商品"
,
dataProvider
=
"test_addToCart_error_data"
)
@Rollback
public
void
test_addToCart_error
(
String
caseNote
,
long
itemId
,
int
count
,
int
expectedErrorCode
)
{
Custom
tom
=
new
Custom
(
"Tom"
)
;
this
.
setCustom
(
tom
)
;
try
{
cartBean
.
addToCart
(
itemId
,
count
)
;
Assert
.
fail
(
)
;
}
catch
(
Exception
e
)
{
Assert
.
assertEquals
(
e
.
getErrorCode
,
expectedErrorCode
)
;
}
}
@DataProvider
public
Object
[
]
[
]
test_addToCart_error_data
(
)
{
return
new
Object
[
]
[
]
{
// caseNote, itemId, count, expectedErrorCode
{
"iPad - 0 count"
,
39001L
,
0
,
Cart
.
ZERO_COUNT
}
,
{
"MacBookPro - more then stock"
,
116001L
,
1024
,
Cart
.
MORE_THAN_STOCK
}
,
{
"no such item"
,
0L
,
1L
,
Cart
.
NO_SUCH_ITEM
}
}
;
}
|
说明:
如果不同的异常输入会有相应的 errorCode 的话, 可以把errorCode当成测试数据的一项参数传进去
Δ特殊场景 & 复杂流程
个别特殊场景,不方便使用@DataProvider合并的复杂流程,可以单独创建一个用例进行测试,函数命名的时候注明一下,例如:
- test_addToCart_error_withoutEnoughMoney() – 钞票不够
- test_addToCart_normal_mergeMultiCarts() – 合并多个购物车
Δ前事不忘,后事之师
出过Bug的地方(及其周边)补充单元测试覆盖,由单元测试帮你记住前事 – test_funcOne_issue12345_bugfix()
用例编写流程
用例编写顺序:
- 开发新功能时,同步编写冒烟测试,用于自测及调试,功能代码与冒烟用例一起提交 – test_funcOne_smoke()
- 稍晚,补充更多分支覆盖的正常流程用例,相当于进行又一轮自测 – test_funcOne_normal()
- 最后,补充异常流程和特殊(复杂)场景用例 – test_funcOne_error() & test_funcOne_specialScenario()
之所以这样安排,一个很重要的原因是希望 保持主干及正常流程的畅通,确保开发及测试不会Block
此外:
- 尽量把单个开发任务切分成多个小功能点,频繁提交,稳扎稳打,配合Jenkins & Sonar,多跑单元测试和静态代码检查,问题早发现早处理
- 前事不忘,后事之师,出过Bug的地方补充单元测试
补遗
单元测试与静态代码检查(static analysis, SA)是一对好基友,两者可以统一显示在Sonar上面,在实践中往往一起考察
关于Sonar,实乃居家必备,代码度量之利器,以后会另外讲述,这里先贴个图:
![saa](http://chenkan.me/blog/wp-content/uploads/2013/11/saa.png)