单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,通常来讲,要根据实际状况去断定其具体含义,如 C 语言中单元指一个函数,Java 里单元指一个类,图形化的软件中能够指一个窗口或一个菜单等。总的来讲,单元就是人为规定的最小的被测功能模块。git
单元测试是在软件开发过程当中要进行的最低级别的测试活动,软件的独立单元将在与程序的其余部分相隔离的状况下进行测试。github
1)在一个复杂的项目中添加某功能模块时,能够快捷的进行针对性测试,而不用将整个项目 Run 起来。web
2)能够便捷的对某个具体方法进行测试。编程
iOS 开发(或者 MacOS、tvOS、watchOS 等)中,单元测试有多种方式,主要分为 Xcode 提供的以及第三方测试框架这两类:数组
Xcode自带 XCTest:XCTest 是 Xcode自带的单元测试工具,其前身是 OCUnit,随着 Xcode 的发展,XCTest 已经愈来愈完善,功能也越强大。bash
第三方框架 GHUnit:GHUnit 是 GitHub 上著名的开源测试框架,可视化、开源、扩展等功能,让其相比 XCTest 更增强大(如今的 XCTest 也很完善了,不过 GHUnit 比较老,如今已经中止维护,不建议使用) OCMock:OCMock 也是 Github 上的著名开源测试框架,用于 Mock、Stub,为测试提供数据做假功能。框架
以上三种就是比较主流的测试 Xcode 单元测试途径,还有一些 BDD 行为测试框架:异步
什么是 BDD BDD(Behavior Driven Development),即行为驱动开发,是敏捷开发技术之一,经过天然语言定义系统行为,以功能使用者的角度,编写需求场景,且这些行为描述能够直接造成需求文档,同时也是测试标准。async
其余的,还有一些其余测试工具、测试方式,如:函数
一、建立测试类
注意,全部测试类都继承自 XCTestCase。
二、测试类的结构
默认建立的单元测试类为一个 .m
文件,里面包含了如下四个方法:
- (void)setUp
:在每一个测试用例开始前调用,能够作一些测试准备工做,为可选方法。- (void)tearDown
:在每一个测试用例结束后调用,能够作一些测试收尾工做,为可选方法。- (void)testExample
:默认建立的测试用例。- (void)testPerformanceExample
:性能测试方法。三、测试流程
当咱们默认执行测试时,系统找到全部的测试类,并执行每一个测试方法;咱们也能够选择性地执行某些测试而已,好比,在 scheme 中 disable 某个用例,或者直接在测试导航栏中每一个测试用例后面的运行按钮,单独执行某个测试。
默认流程以下:
上一个测试类 -> 当前类+ (void)setUp
-> [ - (void)setUp
-> 测试方法 -> - (void)tearDown
] (循环直至当前类测试方法所有执行完) -> 当前类 + (void)tearDown
-> 下一个测试类
四、测试方法
测试方法以 test 为前缀,没有参数,返回值为 void,方法中用断言来判断测试的正确性:
- (void)testColorIsRed {
// Set up, call test subject API. (Code could be shared in setUp method.)
// Test logic and values, assertions report pass/fail to testing framework.
// Tear down. (Code could be shared in tearDown method.
}
- (void)testThatItDoesURLEncoding {
// given
NSString *searchQuery = @"$content$amp;?@";
HTTPRequest *request = [HTTPRequest requestWithURL:@"/search?q=%@", searchQuery];
// when
NSString *encodedURL = request.URL;
// then
XCTAssertEqualObjects(encodedURL, @"/search?q=%24%26%3F%40");
}
复制代码
五、测试断言
断言通常由判断条件、字符串 format、字符串参数组成,参数可选,在 XCTest 中,断言有如下分类:
Specta 和 Expecta 都是出自 Github 做者 Orta 之手,他最出名的开源框架莫过于 Cocoapods。
Specta 是一个轻量级 BBD 测试框架,其为 DSL (Domain-Specific Language) 模式,让测试更加接近于天然语言描述,更加易懂。
一、主要有如下特色:
二、语法介绍
SpecBegin 声明了一个测试类,SpecEnd 结束类声明
describe (context) 块声明了一组实例
it (example/specify) 是一个单一的样例
beforeAll 是一个执行于所有同级块以前的块,仅仅执行一次。afterAll 与beforeAll相反,是在所有同级块以后执行的块。仅仅执行一次。
beforeEach/afterEach,在每个同级块执行的时候,都会执行一次,而beforeAll/afterAll仅仅会执行一次
it/waitUntil/done()。异步调用,注意完毕异步操做以后。必须调用done()函数。例如如下:
it(@"should do some stuff asynchronously", ^{
waitUntil(^(DoneCallback done) {
// Async example blocks need to invoke done() callback.
done();
});
});
复制代码
注意:在describe局部使用sharedExamplesFor定义shared examples。可以在它做用域内覆盖全局的shared examples。
三、通常使用:
SpecBegin(Car)
describe(@"Car", ^{
__block Car *car;
// Will be run before each enclosed it
beforeEach(^{
car = [Car new];
});
// Will be run after each enclosed it
afterEach(^{
car = nil;
});
// An actual test
it(@"should be red", ^{
expect(car.color).to.equal([UIColor redColor]);
});
describe(@"when it is started", ^{
beforeEach(^{
[car start];
});
it(@"should have engine running", ^{
expect(car.engine.running).to.beTruthy();
});
});
describe(@"move to", ^{
context(@"when the engine is running", ^{
beforeEach(^{
car.engine.running = YES;
[car moveTo:CGPointMake(42,0)];
});
it(@"should move to given position", ^{
expect(car.position).to.equal(CGPointMake(42, 0));
});
});
context(@"when the engine is not running", ^{
beforeEach(^{
car.engine.running = NO;
[car moveTo:CGPointMake(42,0)];
});
it(@"should not move to given position", ^{
expect(car.engine.running).to.beTruthy();
});
});
});
});
SpecEnd
复制代码
上面例子对 Car 这个类作测试,经过多个上下文嵌套(describe/context),结合不一样的条件(beforeEach),来做出不一样的断言(it);当咱们某个测试失败时,咱们会收到一段很明确的错误信息,好比:汽车启动后应该移动到指定位置这个用例测试失败,那么咱们会收到 Car move to when the engine is running should move to given position 这么一段话。这样很是接近天然语言的描述会让咱们很快知道错误出在哪里。
四、注意:
若是想用 SPEC_BEGIN 和 SPEC_END 替代 SpecBegin and SpecEnd,应该在引入头文件以前写上 #define SPT_CEDAR_SYNTAX
若是要使用 XCTest Resporter,那么在 Test Scheme 中,把 SPTXCTestReporter 字段值改成 SPECTA_REPORTER_CLASS
把环境变量 SPECTA_SHUFFLE 设置为 1 启用测试拖拽(test shuffling)
Expecta 是基于 Objective-C/Cocoa 的断言框架,XCTest 自带的断言 XCAssert 有好几个基础操做,不过基础的断言不太丰富,和 Specta 也没有很适配。 Expecta 不同,将匹配过程从断言中剥离开,能够很好地适配 Specta 的 DSL 断言块。
一、Expecta 有如下几个特色:
没有类型限制,好比数值 1,并不用关心它是整形仍是浮点数
链式编程,可读性高,如:expect(foo).notTo.equal(1)
反向匹配,断言不匹配只需加上 .notTo 或者 .toNot,如:expect(x).notTo.equal(y)
延时匹配,能够在链式表达式中加入 .will、.willNot、.after(interval) 等操做来延时匹配
可扩展,支持增长自定义匹配
二、基础匹配 API:
expect(x).to.equal(y); // x 与 y 相等
expect(x).to.beIdenticalTo(y); // x 与 y 相等且内存地址相同
expect(x).to.beNil(); // x 为 nil
expect(x).to.beTruthy(); // x 为 true(非 0)
expect(x).to.beFalsy(); // x 为 false(0 值)
复制代码
三、异步匹配
describe(@"WebImage", ^{
beforeAll(^{
// 设置默认延时匹配时间
[Expecta setAsynchronousTestTimeout:2];
});
it(@"will not be nil", ^{
// 使用默认延时匹配
expect(webImage).willNot.beNil();
});
it(@"should equal 42 after 3 seconds", ^{
// 不使用默认延时匹配,手动设置为3秒
expect(webImage).after(3).to.equal(42);
});
});
复制代码
四、自定义使用
#import <Specta/Specta.h>
#import <Expecta/Expecta.h>
#import "ImageModel.h"
SpecBegin(ImageModel);
__block ImageModel *model;
beforeEach(^{
model = [[ImageModel alloc] initWithUrl:@"http://pic37.nipic.com/20140113/8800276_184927469000_2.png" title:@"天空独角马" described:@"在黄色的沙漠里,特别突兀"];
});
it(@"should not nil", ^{
expect(model).toNot.beNil();
});
it(@"equal", ^{
expect(model.url).to.equal(@"http://pic37.nipic.com/20140113/8800276_184927469000_2.png");
expect(model.title).to.equal(@"天空独角马");
expect(model.described).to.equal(@"在黄色的沙漠里,特别突兀");
});
SpecEnd;
复制代码
Expecta 和 Specta 须要配合使用,与 XCTest 同样都是基于 XCTestCase 实现。在断言的使用上,XCTest 太过死板,Expecta 和 Specta 则很灵活,能够知足大部分场景需求。