swift单元测试(四)三方测试框架Quick+Nimble的使用

1、苹果官方测试框架XCTest的优缺点

优点:与 Xcode 深度集成,有专门的Test 导航栏。

缺点:

1)因为受限于官方测试API,因此功能不是很丰富。

2)在书写性和可读性上都不太好。在测试用例太多的时候,由于各个测试方法是割裂的,想在某个很长的测试文件中找到特定的某个测试并搞明白这个测试是在做什么并不是很容易的事情。

3)所有的测试都是由断言完成的,而很多时候断言的意义并不是特别的明确,对于项目交付或者新的开发人员加入时,往往要花上很大成本来进行理解或者转换。另外,每一个测试的描述都被写在断言之后,夹杂在代码之中,难以寻找。

4)使用XCTest测试另外一个问题是难以进行mock或者stub

 

为什么使用Quick+Nimble?

主要是由于苹果官方框架的测试方法及断言不明确,可读性不好,难以分辨,交接项目需要花费的时间很多,所以建议采用三方测试框架

目前主流的三方测试框架主要有:

oc中:kiwi 、spectacedar

swift:quick+nimbleSleipnir

由于项目是使用的swift语言,所以主要采用quick+nimble,用于单元测试和断言。

如果你的项目是OC的,推荐使用kiwi,目前是start最多的三方框架。

2、Quick+Nimble介绍

       Quick 是一个建立在XCTest 上,为Swift Objective-C 设计的测试框架对测试使用Swift编写的App非常友好,Swift使用者来说,Quick是最佳选择。

  它通过DSL 去编写非常类似于RSpec 的测试用例。

  Nimble 就像是Quick 的搭档,它提供了匹配器作为断言,用于编写匹配模式。

 

3、Quick+Nimble使用

1)配置Quick+Nimble

(1)利用cocopod安装Quick+Nimble,pod的描述文件如下

platform :ios, '9.0'
use_frameworks!

def testing_pods
    pod 'Quick', '~>1.0.0'
    pod 'Nimble', '~>5.0.0'
end

target 'testTests' do
    testing_pods
end

post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['SWIFT_VERSION'] = '3.2'
        end
    end
end

(2)在终端执行pod install 

(3)在使用前,xcode还需要在project 中build settting 设置defines module 为yes

(4)在build phases-->link binary with libraries 中导入框架

2)使用Quick+Nimble

(1)在测试文件中导入框架

import Quick
import Nimble

(2)在测试文件的 import 下方导入需要测试的项目的target

//这一行基本上就是标示出我们正在测试的项目目标,然后允许我们从那里 import classes
@testable import test

(3)将测试文件的继承类 改成 QuickSpec必须确保我们的class是QuickSpec的子类,它也是原本XCTestCase的子类。

里面只有一个spec方法,所有的测试都是放在这个方法

import Quick
import Nimble
@testable import test

class BananaTests: QuickSpec {

    override func spec(){
        //所有测试放在这里
    }

}

(4)测试书写的格式

创建一个Dolphin类,用来测试


public struct Click{
    
    public var isLoud = true
    public var hasHighFrequency = true
    
    public func count()->Int{
        return 1
    }
}

class Dolphin {

    public var isFriendly = true
    public var isSmart = true
    public var isHappy = false
    
    public init(){
        
    }
    public init(_ happy : Bool){
        isHappy = happy
    }
    
    public func click()->Click{
        return Click()
    }
    
    public func eat(_ food : AnyObject){
        isHappy = true
    }
}

测试用例的格式是 三板斧 格式即 given--when--then / arrange--act--assert

首先使用苹果官方XCTest

func testA_Dolphin_its_click_whenTheDolphinIsNearSomethingInteresting(){
        //given / arrange
        let dolphin : Dolphin = Dolphin()
        
        
        //when / act
        let click = dolphin.click()
        
        //then / assert
        XCTAssertEqual(click.count(), 3)
    }
    
    func testA_Dolphin_its_click_whenTheDolphinIsNotNearAnythingInteresting(){
        //given / arrange
        let dolphin : Dolphin = Dolphin()
        
        
        //when / act
        let click = dolphin.click()
        
        //then / assert
        XCTAssertEqual(click.count(), 1)
        
    }

再将断言替换成 nimble框架的

func testA_Dolphin_its_click_whenTheDolphinIsNearSomethingInteresting(){
        //given / arrange
        let dolphin : Dolphin = Dolphin()
        
        
        //when / act
        let click = dolphin.click()
        
        //then / assert
//        XCTAssertEqual(click.count(), 3)
        expect(click.count()).to(equal(3))
    }
    
    func testA_Dolphin_its_click_whenTheDolphinIsNotNearAnythingInteresting(){
        //given / arrange
        let dolphin : Dolphin = Dolphin()
        
        
        //when / act
        let click = dolphin.click()
        
        //then / assert
//        XCTAssertEqual(click.count(), 1)
        expect(click.count()).to(equal(1))
    }

最后使用quick+nimble 于官方的对比

import Quick
import Nimble
@testable import test

class DolphinQuickTests: QuickSpec {
    
    override func spec(){
        //所有测试放在这里
       
        // describe用于描述类和方法
        describe("a dolphin", closure: {
            var dolphin : Dolphin!
            
             // beforeEach/afterEach相当于setUp/tearDown,beforeSuite/afterSuite相当于全局setUp/tearDown
            beforeEach {
                dolphin = Dolphin()
            }
            
            describe("its click", closure: {
                var click : Click!
                beforeEach {
                    click = dolphin.click()
                }
                
                // context用于指定条件或状态
                context("when the dolphin is not near anything interesting", closure: {
                    
                    // it用于描述测试的方法名
                    it("it only emited once", closure: {
                        expect(click.count()).to(equal(1))
                    })
                })
                
                context("when the dolphin is near something interesting", closure: {
                    it("it emited three times", closure: {
                        expect(click.count()).to(equal(3))
                    })
                })
            })
        })
    }
    
}

对比的结论:

1)在Quick中不用再像官方框架一样,方法名起的特别长

2Nimble有更简洁,更接近自然语言的语法,

3Quick让我们能够写出更具有描述性的测试,并且,简化我们的代码,尤其是arrange阶段的代码.

4、Quick关键字说明

关键字 用途
describe  描述类和类的方法
context 用于指定条件或状态
it 用于描述测试的方法名
beforeEach/afterEach 相当于setUp/tearDown
beforeSuite/afterSuite 相当于全局setUp/teardown
在describe 、context、it前加“x” 表示可以屏蔽此方法的测试
在describe 、context、it前加“f” 表示可以只测试这些带f的测试

5、Nimble关键字说明

Nimble一般使用 expect(...).to 和 expect(...).notTo的写法

1)支持异步测试

dispatch_async(dispatch_get_main_queue()) {
  ocean.add("dolphins")
  ocean.add("whales")
}
expect(ocean).toEventually(contain("dolphins"), timeout: 3)

 

2)使用waitUntil来进行等待

waitUntil { done in
  // do some stuff that takes a while...
  NSThread.sleepForTimeInterval(0.5)
  done()
}

3)列举Nimble中的匹配函数

用途 函数
等值判断

使用equal函数

expect(actual).to(equal(expected))

expect(actual) == expected

expect(actual) != expected

是否是同一个对象

使用beIdenticalTo函数

expect(actual).to(beIdenticalTo(expected))

expect(actual) === expected

expect(actual) !== expected

比较

expect(actual).to(beLessThan(expected))

expect(actual) < expected

expect(actual).to(beLessThanOrEqualTo(expected))

expect(actual) <= expected

expect(actual).to(beGreaterThan(expected))

expect(actual) > expected

expect(actual).to(beGreaterThanOrEqualTo(expected)) expect(actual) >= expected

比较浮点数

expect(10.01).to(beCloseTo(10, within: 0.1))

类型检查

expect(instance).to(beAnInstanceOf(aClass)) expect(instance).to(beAKindOf(aClass))

是否为真

// Passes if actual is not nil, true, or an object with a boolean value of true:

expect(actual).to(beTruthy())

// Passes if actual is only true (not nil or an object conforming to BooleanType true):

expect(actual).to(beTrue())

// Passes if actual is nil, false, or an object with a boolean value of false:

expect(actual).to(beFalsy())

// Passes if actual is only false (not nil or an object conforming to BooleanType false):

expect(actual).to(beFalse())

// Passes if actual is nil:

expect(actual).to(beNil())

是否有异常

// Passes if actual, when evaluated, raises an exception: expect(actual).to(raiseException())

// Passes if actual raises an exception with the given name:

expect(actual).to(raiseException(named: name))

// Passes if actual raises an exception with the given name and reason:

expect(actual).to(raiseException(named: name, reason: reason))

// Passes if actual raises an exception and it passes expectations in the block

// (in this case, if name begins with 'a r')

expect { exception.raise() }.to(raiseException { (exception: NSException) in

     expect(exception.name).to(beginWith("a r"))

})

集合关系

// Passes if all of the expected values are members of actual:

expect(actual).to(contain(expected...))

expect(["whale", "dolphin", "starfish"]).to(contain("dolphin", "starfish"))

// Passes if actual is an empty collection (it contains no elements):

expect(actual).to(beEmpty())

字符串

// Passes if actual contains substring expected: expect(actual).to(contain(expected))

// Passes if actual begins with substring: expect(actual).to(beginWith(expected))

// Passes if actual ends with substring: expect(actual).to(endWith(expected))

// Passes if actual is an empty string, "": expect(actual).to(beEmpty())

// Passes if actual matches the regular expression defined in expected:

expect(actual).to(match(expected))

检查集合中的所有元素是否符合条件

// with a custom function:

expect([1,2,3,4]).to(allPass({$0 < 5}))

// with another matcher: expect([1,2,3,4]).to(allPass(beLessThan(5)))

检查集合个数

expect(actual).to(haveCount(expected))

匹配任意一种检查

// passes if actual is either less than 10 or greater than 20 expect(actual).to(satisfyAnyOf(beLessThan(10), beGreaterThan(20)))

// can include any number of matchers -- the following will pass

expect(6).to(satisfyAnyOf(equal(2), equal(3), equal(4), equal(5), equal(6), equal(7)))

// in Swift you also have the option to use the || operator to achieve a similar function expect(82).to(beLessThan(50) || beGreaterThan(80))

参考链接:

使用swift给objc项目做单元测试

【CodeTest】TDD,BDD及初步使用Quick

iOS测试与集成

Swift 进阶开发指南:如何使用 Quick、Nimble 执行测试驱动开发(TDD)

猜你喜欢

转载自blog.csdn.net/lin1109221208/article/details/93048812