美文网首页
Kiwi -- BDD 模式下的单元测试自动化

Kiwi -- BDD 模式下的单元测试自动化

作者: 红发_KVO | 来源:发表于2019-07-28 11:05 被阅读0次

    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 描述需要测试的对象内容,也即是我们三段式中的 Givencontext描述测试的上下文,也就是这个测试在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的存在,以及其可以嵌套的特性,测试的流程控制相比传统测试可以更加精确。我们更容易把beforeafter的作用区域限制在合适的地方。
    实际的测试写在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进行测试回归或调试即可。

    参考文献:

    TDD的iOS开发初步以及Kiwi使用入门

    iOS开发——TDD、BDD方法以及Kiwi单元测试框架

    相关文章

      网友评论

          本文标题:Kiwi -- BDD 模式下的单元测试自动化

          本文链接:https://www.haomeiwen.com/subject/txznuqtx.html