您的位置:首页 > 理论基础 > 计算机网络

iOS单元测试:Specta + Expecta + OCMock + OHHTTPStubs + KIF

2015-07-22 18:14 1516 查看

框架选择

参考这篇选型文章,http://zixun.github.io/blog/2015/04/11/iosdan-yuan-ce-shi-xi-lie-dan-yuan-ce-shi-kuang-jia-xuan-xing/,虽然结论不一定完全适用,但是关于框架对比的地方还是值得阅读的,基于这篇文章,排除Kiwi框架之后,决定参考一些项目的源代码,了解他们使用的测试方面的框架。

首先,参考https://github.com/artsy/eigen开源项目,其内部整体结构非常完整,开发流程也非常专业,至少比我知道的大多数国内团队都要专业:
eigen: Specta + OCMock + Expecta + OHHTTPStubs + FBSnapshotTestCase + "Expecta+Snapshots" + "XCTest+OHHTTPStubSuiteCleanUp”。

其次,参考公司内部别的项目使用情况,发现使用以下框架来做测试方面的事情: Specta + Expecta + OCMock + OHTTPStubs + KIF(UI Test)

so,我决定选择 Specta (BDD框架) + Expecta(断言框架) + OCMock(mock框架) + OHHTTPStubs(http stub框架) + KIF(UI Test) 做测试框架来学习。

XCTest简介

由于我决定不直接使用XCTest作为测试框架,但是又由于Specta是基于XCTest进行封装的,所以对XCTest做一个基础的了解还是有必要的。
参考:
1. https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/testing_with_xcode
2. http://www.objc.io/issues/15-testing/xctest/,翻译:http://objccn.io/issue-15-2/
3. http://zixun.github.io/blog/2015/04/16/iosdan-yuan-ce-shi-xi-lie-dan-yuan-ce-shi-bian-ma-gui-fan/

BDD框架 — Specta

1. 简介

目前主流的BDD框架,这些BDD框架在语法层面几乎是相同的,主要的区别在于他们的可配置能力和绑定的组件。下面三个OC BDD框架相对于官方框架XCTest都具有更好的可读性,另外现在已经有了比较流行的swift BDD框架: https://github.com/railsware/Sleipnir 和 https://github.com/Quick/Quick
https://github.com/specta/specta
https://github.com/kiwi-bdd/Kiwi
https://github.com/pivotal/cedar

关于specta与kiwi框架的对比,参考:http://appleprogramming.com/blog/2014/01/18/tdd-with-specta-and-expecta/,这篇文章的结论是specta相对于kiwi有更加优雅的语法,对于我这种刚开始使用的新手,果断采用specta这种各种完胜的框架。Specta框架具有一下特点:
An OC RSpec-like BDD DSL
Quick and easy set up
Build on top of XCTest
Excellent Xcode integration

2. Specta BDD DSL语法简介

可以参考 https://github.com/specta/specta 官网和https://github.com/artsy/eigen项目中的test
case代码来学习语法
1) SpecBegin 声明了一个测试类,SpecEnd 结束了类声明
2) describe (context) 块声明了一组实例
3) it (example/specify) 是一个单一的例子
4) beforeAll 是一个运行于所有同级块之前的块,只运行一次。afterAll 与beforeAll相反,是在所有同级块之后运行的块,只运行一次。
5) beforeEach/afterEach,在每个同级块运行的时候,都会运行一次,而beforeAll/afterAll只会运行一次
6) it/waitUntil/done(),异步调用,注意完成异步操作之后,必须调用done()函数,如下:

it(@"should do some stuff asynchronously", ^{

    waitUntil(^(DoneCallback done) {

      // Async example blocks need to invoke done() callback.

      done();

    });

  });

7) sharedExamplesFor 和 itShouldBehaveLike结合在一起,可以实现在不同的spec之间共享同一套test case,参考:http://artandlogic.com/2014/02/specta-shared-behavior/;sharedExamplesFor
设置多个spec之间共享的test case,第一个参数作为标识符。通过itShouldBehaveLike来运行spec中test case,第一个参数传入sharedExamplesFor设置时使用的标识符。注意,在describe局部使用sharedExamplesFor定义shared examples,可以在它作用域内覆盖全局的shared examples。

8) pending,只打印一条log信息,不做测试。这个语句会给出一条警告,可以作为一开始集中书写行为描述时还未实现的测试的提示。

断言框架 — Expecta

用法可以参考开源项目: https://github.com/artsy/eigen,从中找到对应的代码学习是最好的方式。如果需要找到更多更全面的用法,可以去项目官方网站:https://github.com/specta/expecta。截取一段eigen上面代码,基本上就可以了解Expecta框架的基本用法了,如下图中
expect(mapView.nextZoomScale).to.equal(mapView.annotationZoomScaleThreshold)



mock框架 —
OCMock

了解OCMock 2.x中的详细features,可以参考: http://ocmock.org/features/;了解OCMock 3.x的详细API,可以参考:http://ocmock.org/reference/

什么是mock,refer: http://hackazach.net/code/2014/03/03/effective-testing-with-ocmock/
In a modern Object Oriented system, the component under test will likely have several object dependencies. Instead of instantiating dependencies as concrete classes, we use mocks. Mocks are ‘fake’ objects with pre-defined behavior to stand-in for concrete objects during testing. The component under test does not know the difference! With mocks, a component can be tested with confidence that it behaves as designed within a larger system. 

OCMock框架的用法也比较简单,由于我个人时间比较紧张,只能抽出一两天的时间学习测试部分的知识,就不多说了,下面几篇文章都说的比较清楚,可以参考:http://zixun.github.io/blog/2015/04/16/iosdan-yuan-ce-shi-xi-lie-yi-ocmockchang-jian-shi-yong-fang-shi/ ,学习2.x和3.x的API的基本使用。另外可以参考开源项目 https://github.com/artsy/eigen,学习其中的OCMock
API的使用,框架使用比较简单,看看就懂了,不需要多说。

eigen的一个test case,注意在执行完毕的时候,需要调用stopMocking。OCMockObject是基于runtime方法转发实现的,mock一个对象,就是对这个对象的方法进行转发的过程,执行完毕需要调用stopMocking,否则会影响其他test case的执行。下面可以看出一个OCMock基本过程:获得OCMockObject -> stub方法 -> 设置expect ->
verify校验执行结果 -> 调用stopMocking



下面有一个mock一个alert view show的过程



参考:
http://ocmock.org/reference/
http://ocmock.org/features/
http://ocmock.org/introduction/
http://www.archive.alexvollmer.com/posts/2010/06/28/making-fun-of-things-with-ocmock/
http://hackazach.net/code/2014/03/03/effective-testing-with-ocmock/,翻译:http://zixun.github.io/blog/2015/04/16/iosdan-yuan-ce-shi-xi-lie-yi-ocmockchang-jian-shi-yong-fang-shi/
http://engineering.aweber.com/improving-ios-unit-tests-with-ocmock/

OHHTTPStubs

官方:https://github.com/AliSoftware/OHHTTPStubs。这个框架是基于NSURLProtocol实现的,之前正好看过这部分的只是,整理来说,这个框架的源代码并不复杂,但实现还是比较巧妙的。详细的介绍和使用,在github上面介绍的非常清楚,框架本身使用也比较简单:

[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {

  return [request.URL.host isEqualToString:@"mywebservice.com"];

} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {

  // Stub it with our "wsresponse.json" stub file (which is in same bundle as self)

  NSString* fixture = OHPathForFile(@"wsresponse.json", self.class);

  return [OHHTTPStubsResponse responseWithFileAtPath:fixture

            statusCode:200 headers:@{@"Content-Type":@"application/json"}];
}];

这个框架的主要使用方法就是上面这个示例,用法很明显易用。结合unit test使用的时候,需要使用网络请求的时候,可以在it或者beforeAll或者beforeEach的时候进行stub request,即上面这段代码的行为。但是不要忘记的是,需要在tear down的时候,即specta的afterAll的时候,记得调用 [OHHTTPStubs removeAllStubs] 。
注意,这里只是使用NSURLProtocol来stub request,不会影响被测试的请求接口的测试,请求是异步的话,可以使用Specta的it/waitUntil/done()流程对请求进行测试,如果使用XCTest的话,OHTTPStubs给出了一个wiki解决,使用XCTestExpectation来搞定,我觉得挺有意思:

- (void)testFoo

{

  NSURLRequest* request = ...

  XCTestExpectation* responseArrived = [self expectationWithDescription:@"response of async request has arrived"];

  __block NSData* receivedData = nil;

  [NSURLConnection sendAsynchronousRequest:request

                                     queue:[NSOperationQueue mainQueue]

                         completionHandler:^(NSURLResponse* response, NSData* data, NSError* error)

   {

     receivedData = data;

     [responseArrived fulfill];

   }

  ];

  [self waitForExpectationsWithTimeout:timeout handler:^{

    // By the time we reach this code, the while loop has exited

    // so the response has arrived or the test has timed out

    XCTAssertNotNil(receivedData, @"Received data should not be nil");

  }];

}

由于NSURLProtocol的局限性,OHHTTPStubs没法用来测试background sessions和模拟数据上传。

F.I.R.S.T 原则

优秀测试实践原则,https://pragprog.com/magazines/2012-01/unit-tests-are-first
Fast — 测试应该能够被经常执行
Isolated — 测试本身不能依赖于外部因素或其他测试的结果
Repeatable — 每次运行测试都应该产生相同的结果
Self-verifying — 测试应该依赖于断言,不需要人为干预
Timely — 测试应该和生产代码一同书写
如何将测试结果收益最大化:不要将测试和实现细节耦合在一起。
不要测试私有方法
不要Stub私有方法
不要Stub外部库
正确地Stub依赖
不要测试构造函数

参考资料

http://www.objc.io/issues/15-testing/,(翻译:http://objccn.io/issue-15/ )
https://github.com/artsy/eigen,非常专业的APP的开源代码,http://objccn.io/issue-22-2/
<Functional Reactive Programming on iOS>: RAC + 单元测试
http://www.jianshu.com/p/73f9d719cee4
http://nshipster.com/unit-testing/
http://onevcat.com/2014/02/ios-test-with-kiwi/
http://onevcat.com/2014/05/kiwi-mock-stub-test/
https://github.com/dblock/fui,find unused objective-c imports
<Testing with Xcode>
<Pro iOS Continuous Integration>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: