iOS 单元测试

发表于:2019-7-01 11:32

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:马小尾    来源:掘金

  什么是单元测试
  单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如 C 语言中单元指一个函数,Java 里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。
  单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
  为什么要做单元测试
  1)在一个复杂的项目中添加某功能模块时,可以快捷的进行针对性测试,而不用将整个项目 Run 起来。
  2)可以便捷的对某个具体方法进行测试。
  单元测试有哪些
  iOS 开发(或者 MacOS、tvOS、watchOS 等)中,单元测试有多种方式,主要分为 Xcode 提供的以及第三方测试框架这两类:
  Xcode自带
  XCTest:XCTest 是 Xcode自带的单元测试工具,其前身是 OCUnit,随着 Xcode 的发展,XCTest 已经越来越完善,功能也越强大。
  第三方框架
  GHUnit:GHUnit  是 GitHub 上著名的开源测试框架,可视化、开源、扩展等功能,让其相比 XCTest 更加强大(现在的 XCTest 也很完善了,不过 GHUnit 比较老,现在已经停止维护,不建议使用)
  OCMock:OCMock 也是 Github 上的著名开源测试框架,用于 Mock、Stub,为测试提供数据作假功能。
  以上三种就是比较主流的测试 Xcode 单元测试途径,还有一些 BDD 行为测试框架:
  什么是 BDD
  BDD(Behavior Driven Development),即行为驱动开发,是敏捷开发技术之一,通过自然语言定义系统行为,以功能使用者的角度,编写需求场景,且这些行为描述可以直接形成需求文档,同时也是测试标准。
  Specta:GitHub 上轻量级的 BDD 测试框架。
  Expecta:与 Specta 同个作者,是一个功能强大的匹配框架。
  Kiwi:Kiwi 是重量级的,集 OCMock、Specta、Expecta 所拥有的功能与一身的 BDD DSL 测试框架。
  其他的,还有一些其他测试工具、测试方式,如:
  Nocilla:强大的 HTTP 模拟测试工具。
  OHHTTPStubs:也是 HTTP 模拟测试工具。
  TUDelorean:基于 Objective-C 的时间模拟测试工具。
  KIF:集成/界面测试工具,其它的还有 Frank、Calabash 等。
  GitHub + Jenkins + TestFlight:自动化测试
  Monkey Test:随机测试。
  XCTest 测试
  1、创建测试类
  注意,所有测试类都继承自 XCTestCase。
  2、测试类的结构
  默认创建的单元测试类为一个 .m 文件,里面包含了以下四个方法:
  - (void)setUp:在每个测试用例开始前调用,可以做一些测试准备工作,为可选方法。
  - (void)tearDown:在每个测试用例结束后调用,可以做一些测试收尾工作,为可选方法。
  - (void)testExample:默认创建的测试用例。
  - (void)testPerformanceExample:性能测试方法。
  3、测试流程
  当我们默认执行测试时,系统找到所有的测试类,并执行每个测试方法;我们也可以选择性地执行某些测试而已,比如,在 scheme 中 disable 某个用例,或者直接在测试导航栏中每个测试用例后面的运行按钮,单独执行某个测试。
  默认流程如下:
  上一个测试类 -> 当前类+ (void)setUp -> [ - (void)setUp -> 测试方法 -> - (void)tearDown ] (循环直至当前类测试方法全部执行完) -> 当前类 + (void)tearDown -> 下一个测试类
  4、测试方法
  测试方法以 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");
  }
  5、测试断言
  断言一般由判断条件、字符串 format、字符串参数组成,参数可选,在 XCTest 中,断言有以下分类:
  Unconditional Fail:XCTFail,失败时候抛出。
  Equality Tests:用于断言两个表达式相等或不相等,如:XCTAssertEqual、XCTAssertEqualWithAccuracy、XCTAssertNotEqual、 XCTAssertGreaterThan。
  Boolean Tests:断言布尔表达式真假,如:XCTAssertTrue、XCTAssertFalse。
  Nil Tests:空断言,如:XCTAssertNil、XCTAssertNotNil。
  Exception Tests:断言表达式抛出或不抛出异常,如:XCTAssertThrows、XCTAssertThrowsSpecific、XCTAssertNoThrow。
  Specta、Expecta 测试
  Specta 和 Expecta 都是出自 Github 作者 Orta 之手,他最出名的开源框架莫过于 Cocoapods。
  Specta
  Specta 是一个轻量级 BBD 测试框架,其为 DSL (Domain-Specific Language) 模式,让测试更加接近于自然语言描述,更加易懂。
  1、主要有以下特点:
  容易集成到项目中。
  基于XCTest编写,可以很好地与XCTest配合使用。
  2、语法介绍
  SpecBegin 声明了一个测试类,SpecEnd 结束类声明
  describe (context) 块声明了一组实例
  it (example/specify) 是一个单一的样例
  beforeAll 是一个执行于全部同级块之前的块,仅仅执行一次。afterAll 与beforeAll相反,是在全部同级块之后执行的块。仅仅执行一次。
  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
  sharedExamplesFor 设置多个spec之间共享的test case,第一个參数作为标识符。通过itShouldBehaveLike来执行spec中test case。第一个參数传入sharedExamplesFor设置时使用的标识符。
  注意:在describe局部使用sharedExamplesFor定义shared examples。能够在它作用域内覆盖全局的shared examples。
  8.pending,仅仅打印一条log信息。不做测试。这个语句会给出一条警告,能够作为一开始集中书写行为描写叙述时还未实现的测试的提示。
  3、一般使用:
   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 这么一段话。这样非常接近自然语言的描述会让我们很快知道错误出在哪里。
  4、注意:
  如果想用 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
  Expecta 是基于 Objective-C/Cocoa 的断言框架,XCTest 自带的断言 XCAssert 有好几个基础操作,不过基础的断言不太丰富,和 Specta 也没有很适配。 Expecta 不一样,将匹配过程从断言中剥离开,可以很好地适配 Specta 的 DSL 断言块。
  1、Expecta 有以下几个特点:
  没有类型限制,比如数值 1,并不用关心它是整形还是浮点数
  链式编程,可读性高,如:expect(foo).notTo.equal(1)
  反向匹配,断言不匹配只需加上 .notTo 或者 .toNot,如:expect(x).notTo.equal(y)
  延时匹配,可以在链式表达式中加入 .will、.willNot、.after(interval) 等操作来延时匹配
  可扩展,支持增加自定义匹配
  2、基础匹配 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 值)
  3、异步匹配
  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);
  });
  });
  4、自定义使用
   #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 则很灵活,可以满足大部分场景需求。

      不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号