Kiwi -- BDD 模式下的单元测试自动化
首先看到标题里面的BDD,这是什么鬼?这里,我们先来看看目前的不一样的测试思想。
TDD和 BDD
-
行为驱动开发(英语:Behavior-driven-development,缩写 BDD) 是一种敏捷开发的技术,BDD 提倡的是通过将测试语句转换为类似自然语言的描述,开发人员可以使用更符合大众语言的习惯来书写测试,当别人接手/交付,或者自己修改的时候,都简单易明白,顺利很多。一个典型的 BDD 测试用例包括完整的三段式上下文,测试大多可以翻译为
Given...When...Then
的格式,读起来轻松惬意。 -
测试驱动开发(英语:Test-driven-development,缩写 TDD) 是一种软件开发过程中的应用方法,由极限编程中倡导。
TDD
倡导先根据需求或者接口情况编写测试,然后再根据测试来编写业务代码。首先这其实是违反传统软件开发中的先验认知的,特别是国内环境。但是 TDD 模式下,已经提前深入思考和实践如和使用代码,能提高设计和可扩展性,另外,因为有测试的保护,我们可以放心的对原有代码进行重构,而不必担心破坏逻辑和丢失逻辑。
看完上面两种测试思想的差异之后,我想,在我们当前的 iOS 和国内环境下,BBD 测试方法将是我们的首选。而 BDD,不得不介绍一下它-- Kiwi。
Kiwi
Kiwi 的 GitHub地址:https://github.com/kiwi-bdd/Kiwi
安装方式
- 使用 CocoaPods 安装
target :YourProjectTests do
pod 'Kiwi';
end
在这里必须得注意YourProject
是你自己的项目名。
-
手动导入
- 下载
KiwiProject
然后选中模拟器和Generic iOS Device
编译,会生成对应的Kiwi.framework
。 - 把生成好的
Kiwi.framework
拖进你的工程,注意,是拖进去,不是引用 - 在 test target的
setting - Build Phases
里面的Link Binary With Libraies
里添加它 - 设置测试 target 的
User Headers Search
指向它的位置 - 设置测试 target 的
Runpath Search Paths
,添加$(FRAMEWORK_SEARCH_PATHS)
非常重要,不然会报错 - 在
Other Linker Flags
里面添加-ObjC
。
- 下载
Kiwi 的基本语法和结构
在我们开始自己编写 Kiwi
语法之前,我们先来看看Kiwi
的GitHub提供的示例代码。
describe(@"Team", ^{
context(@"when newly created", ^{
it(@"should have a name", ^{
id team = [Team team];
[[team.name should] equal:@"Black Hawks"];
});
it(@"should have 11 players", ^{
id team = [Team team];
[[[team should] have:11] players];
});
});
});
我们很容易就可以上下文将其提取程Given...When...Then
的三段式自然语言
Given a team,when newly created,it should have a name,and should have 11 players
describe
描述需要测试的对象内容,也即是我们三段式中的 Given
,context
描述测试的上下文,也就是这个测试在when
进行,最后it
中的是测试的本体,描述了这个测试应该满足什么期许(条件),三者共同构成了Kiwi 测试中的行为描述。它们是可以nest
的,也就是一个Spec
文件中可以包含多个describe
(虽然我们很少这么做,一个测试文件应该专注于测试一个类);一个describe
可以包含多个context
,来描述类在不同情景下的行为;一个context
可以包含多个it
的测试例。
通过这个示例,我们再来了解一下Kiwi
的其它一些行为描述关键字,其中比较重要的包括:
-
beforeAll(aBlock)
-当前的scope
内部的所有的其它block
运行之前调用一次 -
afterAll(aBlock)
- 当前scope
内部所有的其它block
运行之后调用一次 -
beforeEach(aBlock)
-在scope
内的每个 it 钱调用一次,对于context
的配置写在这里 -
alterEach(aBlock)
-在scope
内的每个 it 之后调用一次,用于清理测试后的代码和清空状态 -
specify(aBlock)
-可以在里面直接书写不需要描述的测试 -
pending(aString, aBlock)
-只打印一条log
信息,不做测试,这个语句会给出一条警告,可以作为开始集中书写行为描述还未实现的测试的提示 -
xit(aString,aBlock)
-和pending
一样,另一种写法。因为在真正实现时测试时只需要将x删掉就是it
,但是pending
语意更明确,因此还是推荐pending
可以看到,由于有context的
存在,以及其可以嵌套的特性,测试的流程控制相比传统测试可以更加精确。我们更容易把before
和after
的作用区域限制在合适的地方。
实际的测试写在it
里,是由一个一个的期望(Expectations)
来进行描述的,期望相当于传统测试中的断言,要是运行的结果不能匹配期望,则测试失败。在Kiwi
中期望都由should
或者shouldNot
开头,并紧接一个或多个判断的的链式调用,大部分常见的是be
或者haveSomeCondition
的形式。
Kiwi 使用实例
如何利用 Kiwi 来进行单元测试呢!
我们工程中添加了一个Person
的类
@interface Person : NSObject
- (CGFloat)everyDayTheBodyPutOnWeight:(CGFloat)weight;
- (CGFloat)everyDayRunForKeepFitLoseWeight:(CGFloat)weight;
@interface Person()
@property (nonatomic, assign) CGFloat totalWeight;
@end
@implementation Person
- (instancetype)init
{
if (self = [super init]) {
_totalWeight = 50.0;
}
return self;
}
- (CGFloat)everyDayTheBodyPutOnWeight:(CGFloat)weight
{
return _totalWeight + weight;
}
- (CGFloat)everyDayRunForKeepFitLoseWeight:(CGFloat)weight
{
return _totalWeight - weight;
}
如何通过 kiwi 编写这个类的单元测试呢?
#import <Kiwi/Kiwi.h>
#import "Person.h"
SPEC_BEGIN(PersonSpec)
describe(@"every one for a day", ^{
context(@"for lose or put on weight", ^{
__block Person *p = nil;
beforeEach(^{
p = [Person new];
});
afterEach(^{
p = nil;
});
it(@"put on weight", ^{
CGFloat onWeight = [p everyDayTheBodyPutOnWeight:7];
[[theValue(onWeight) should] equal:57 withDelta:2];
});
it(@"lose weight", ^{
CGFloat loseWeight = [p everyDayRunForKeepFitLoseWeight:10];
[[theValue(loseWeight) should] beLessThanOrEqualTo:theValue(50)];
});
});
});
SPEC_END
当我们用 command + U 运行这段测试用例的时候,输出如下
- 'every one for a day when for lose or put on weight, put on weight' [PASSED]
- 'every one for a day when for lose or put on weight, lose weight' [PASSED]'
来解释下上面的语法中用到的theValue
.
Kiwi 为我们提供了一个标量转对象的语法糖,叫做theValue
,在做精确比较的时候我们可以直接使用例子中直接与7
或者10
做比较这样的写法来进行对比。
通过这样一个简单的例子,我们基本能掌握Kiwi
的简单语法,以及Kiwi的使用。单元测试尽管入门门槛不高,但是如何用心的,动脑子的去写单元测试,并且可以实现自动化,则是对我们程序员莫大的考验哦。
想要更进阶的使用 kiwi 的话,请看Kiwi 使用进阶 Mock, Stub, 参数捕获和异步测试
总结:
首先,和CocoaPods结合紧密,官方创建Pods后直接支持生成Kiwi的测试项目;
其次,由于其BDD的特性,语法可读性很强;
最后,由于是基于XCTest来开发的,对XCode的支持很好,直接通过XCode进行测试回归或调试即可。
参考文献: