• iOS自动化测试框架Kiwi快速上手


    介绍

    Kiwi是一个适用于iOS开发的行为驱动测试框架,旨在提供一个足够简单易用的BDD(Behavior Driven Development)库。

    安装

    使用Cocoapods安装,在测试Target中增加以下配置:

    pod 'Kiwi', '3.0.0'
    
    • 1

    基本使用

    先看一个完整的代码示例:

    
     #import 
      SPEC_BEGIN(KiwiTest)
          describe(@"descibeString", ^{
              context(@"contextString", ^{
                  beforeAll(^{
                      NSLog(@"beforeAll");
                  });
                  afterAll(^{
                      NSLog(@"afterAll");
                  });
                  beforeEach(^{
                      NSLog(@"beforeEach");
                  });
                  afterEach(^{
                      NSLog(@"afterEach");
                  });
                  it(@"test1", ^{
                      [[theValue(1) should] equal:@(1)];
                      NSLog(@"test1");
                  });
                  it(@"test2", ^{
                      [[theValue(2) should] equal:@(2)];
                      NSLog(@"test2");
                  });
              });
          });
      SPEC_END
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    使用Kiwi编写的测试用例不会在用例开发结束后就显示在xcode测试用例列表中,必须要运行一遍测试用例后才会显示出来(不管成功还是失败),这一点是不如XCTest直观的。

    测试用例运行成功后,会生成2条测试用例,见下图:

    图片

    日志打印如下图:

    图片

    通过日志输出,基本可以看出API间的等价关系:

    在这里插入图片描述

    KiwiXCTestbeforeAll()setUp()afterAll()tearDown()it()testXXX()[[theValue(xxx) should] equal:yyy]XCTAssertEqual(xx, yyy, @“”)

    同时Kiwi还提供了2个新的方法,beforeEach和afterEach,分别在每个测试用例开始执行前和执行结束后触发,方便在每个测试用例执行前后做一些初始化和清理方面的事情。

    Kiwi 常用 API 介绍

    Kiwi提供了丰富的逻辑判断API,用法如下:

    1. bool判断

    [[theValue(boolVar) should] beYes]

    [[theValue(boolVar) shouldNot] beYes]

    [[theValue(boolVar) should] beNo]

    [[theValue(boolVar) shouldNot] beNo]

    等价于 XCTAssertTrue(boolVar) 以及 XCTAssertFalse(boolVar)。

    2. 值判断

    [[theValue(1) should] equal:@(1)]

    [[@“string1” shouldNot] equal:@“string”]

    等价于 XCTAssertEqual 和 XCTAssertNotEqual。

    3. 空判断

    [[oc_object should] beNil];

    [[oc_object shouldNot] beNil];

    等价于 XCTAssertNil 和 XCTAssertNotNil。

    上述API,和XCTest用法一样,不同在写法上,XCTest更像是C语言的API形式,Kiwi更像是OC的API形式,语义上更直白。

    4. 延时判断

    expectFutureValue这个API很有用,通常用在异步场景中,比如网络请求、定时器等,同时写在延时判断后面的测试用例又成为同步判断。

    // 30秒后,result值在应该是YES

    [[expectFutureValue(@(result)) shouldAfterWaitOf(30.0)] equal:@(YES)];

    例如:

    __block BOOL result = NO;

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

    // 做一些很耗时的操作

    result = YES;

    });

    // 10秒后判断result值是否是YES

    [[expectFutureValue(@(result)) shouldAfterWaitOf(10.0)] equal:@(YES)];

    // 在上一个判断结束后,再判断下面的用例

    [[@“string1” shouldNot] equal:@“string”]

    Kiwi进阶用法

    1. Stub

    用于替换指定方法的执行过程或者返回结果,Kiwi提供了如下丰富的stub API

    API 介绍

    - (void)stub:(SEL)aSelector;
    - (void)stub:(SEL)aSelector withBlock:(id (^)(NSArray *params))block;
    - (void)stub:(SEL)aSelector withArguments:(id)firstArgument, ...;
      - (void)stub:(SEL)aSelector andReturn:(id)aValue;
      - (void)stub:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, ...;
      - (void)stub:(SEL)aSelector andReturn:(id)aValue times:(NSNumber *)times afterThatReturn:(id)aSecondValue;
      + (void)stub:(SEL)aSelector;
      + (void)stub:(SEL)aSelector withBlock:(id (^)(NSArray *params))block;
      + (void)stub:(SEL)aSelector withArguments:(id)firstArgument, ...;
      + (void)stub:(SEL)aSelector andReturn:(id)aValue;
      + (void)stub:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, ...;
      + (void)stub:(SEL)aSelector andReturn:(id)aValue times:(NSNumber *)times afterThatReturn:(id)aSecondValue;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    仔细观看,不难发现,上述API分2类,一类是实例方法,一类是类方法。这里挑几个常用的说明下:

    - (void)stub:(SEL)aSelector withBlock:(id (^)(NSArray *params))block;

    当调用aSelector时,不走原来的逻辑,而是执行你传递的block。

    - (void)stub:(SEL)aSelector andReturn:(id)aValue

    当调用aSelector时,不返回原来的值,而是用你传递的值来返回

    - (void)stub:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, …

    当调用aSelector并且参数是你指定的参数时,不返回原来的值,而是用你传递的值来返回。

    使用场景

    比如你要测试一个复杂的方法A,这个方法中调用了其他模块的一些方法(B、C、D),而方法A中会根据B、C、D方法的不同返回值做不同的逻辑处理,那么你要怎么写测试用例,来验证方法A在不同的逻辑分支处理结果都满足预期呢?

    这种场景在XCTest中是不好处理的,因为你没法处理A方法的内部细节,对写测试用例的人来说,相当于一个黑盒。

    而在Kiwi中,只要在调用A方法前,对B、C、D分别调用相应的stub方法,之后再调用A方法,A方法内的B、C、D方法就能按照之前stub的约定执行指定的block和返回指定的值。

    示例如下:

     @implementation Runner
      - (BOOL)start {
          // 返回值由当前设备环境以及登录账号来决定
          BOOL enabled = [ConfigABTest enabled];
          if (enable) {
              return YES;
          }
          return NO;
      }
      @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们需要测试[Runner.instance start]这个方法在不同场景下是否返回正确的值,

    而在此方法内部需要调用[ConfigABTest enabled]方法。比如在我当前测试设备或者测试账号中,

    [ConfigABTest enabled]只能返回YES,XCTest中的测试用例在不更换测试设备或者账号的前提下,

    就没法验证[ConfigABTest enabled]在返回NO的情况下,[Runner.instance start]是否也返回正确的值。

    而在Kiwi中,这很容易做到,只需要编写如下测试代码就可以了。

    
    // 测试YES的场景
      [ConfigABTest stub:@selector(enabled) andReturn:theValue(YES)];
      BOOL result = [Runner.instance start];
      [[theValue(result) should] beYes];
      // 测试NO的场景
      [ConfigABTest stub:@selector(enabled) andReturn:theValue(NO)];
      BOOL result = [Runner.instance start];
      [[theValue(result) should] beNo];
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. Mock

    这个比较好理解,在网络请求场景中,经常需要mock数据来验证服务端返回各种数据时,客户端是否都能正确处理。那么在Kiwi中,mock一个对象就是生成一个源对象几乎行为一致的对象。

    API介绍

    // NSOject + KiwiMockAdditions.h
      /// mock调用的类
      + (id)mock;
      // KWMock.h
      /// mock指定class并生成一个对象
      + (id)mockForClass:(Class)aClass;
      /// mock一个遵循指定协议的对象
      + (id)mockForProtocol:(Protocol *)aProtocol; 
      /// 根据指定的object生成一个mock了该object类型的对象
      + (id)partialMockForObject:(id)object;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用场景

    比如现在需要针对下面的[MyDownloader downloadWithURL:handler:]方法写测试用例。

    
    @protocol MyDownloadDelegate <NSObject> 
      @required 
      /// 通知下载结果 
      - (void)downloadComplete:(NSString * _Nullable)dstPath error:(NSError * _Nullable)error;
      @end
      @implementation MyDownloader
      - (void)downloadWithURL:(NSString *)url handler:(id<MyDownloadDelegate>)delegate {
          // 通过调用底层网络库处理下载
          [XXXNetwork downloadWithUrl:url completeHandler:^(NSString *path, NSError *error) {        // 对path做一些转换        // 对error做一些提示上的转换        [delegate downloadComplete:path error:error];
          }];
      }
      @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    因为[MyDownloader downloadWithURL:handler:]需要一个protocol参数,我不可能为了这个参数去现场定义一个类实现指定的protocol,这种情况下就轮到mock上场了。

     it(@"测试下载", ^{
          AnyDeclaredClass *mocked = [KWMock mockForProtocol:@protocol(MyDownloadDelegate)];
          [mocked stub:@selector(downloadComplete:error:) withBlock:^id(NSArray *params) {
              resultImage = [params[0] isEqual:[NSNull null]] ? nil : params[0];
              resultError = [params[1] isEqual:[NSNull null]] ? nil : params[1];
              return nil;
          }];
          MyDownloader *downloader = MyDownloader alloc] init];
          [downloader downloadWithURL:@"" handler:mockDelegate];
      });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当执行到[MyDownloader downloadWithURL:handler:]中的[delegate downloadComplete:path error:error];这一行代码时,就会执行到上面[mocked stub:withBlcok:]中指定的block体中去,这样就避免了创建新类的操作。

    个人体会

    个人觉得编写测试用例最大的好处,就是能更早的发现问题。要知道问题发现的越晚,为改正它而花费的代价就越大。但是我们为什么就坚持不下去呢?最主要的原因就是随着App的规模不断增长,XCTest测试框架能应用的场景越来越少,起到的作用很有限。比如业务场景的数据都是通过网络请求去获取的,XCTest没法切入进去定制化响应数据以验证我们代码的健壮性;还有我们的模块代码内调用其他模块的代码,我们没法验证其他模块代码返回不同值时,我们的模块代码是否都能够正确处理,等等。

    现在Kiwi提供了强大的Stub和Mock能力,上面所说的困难也都不存在了。自从使用Kiwi开发测试用例后,每次迭代只要测试用例都能跑通,上线基本上是没有问题的,就算上线发现了问题,只要及时补充用例,也能杜绝相同的问题再次出现的情况。

    现在我邀请你进入我们的软件测试学习交流群:746506216】,备注“入群”, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。

    喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!
    在这里插入图片描述

  • 相关阅读:
    vue3 联合搜索
    LeetCode50天刷题计划(Day 19—— 在排序数组中查找元素的第一个和最后一个位置(9.10-10.40)
    一场直播帮你揭开保险代打卡背后的“猫腻”
    数据分析:小红书品牌“共情力”缔造指南
    数据结构---第七章查找---顺序查找和折半查找---应用题
    jpg格式图片无法打开可以修复吗?有哪些方法?
    代码随想录训练营day58 || ● 739. 每日温度 ● 496.下一个更大元素 I
    AFL模糊测试
    六大排序实现
    TablePlus for Mac/Win:开启高效数据开发新纪元
  • 原文地址:https://blog.csdn.net/wx17343624830/article/details/126023031