Unit swift test unit (b) XCTest test framework UnitTest

 

1, XCTest Framework Overview

        XCTest Apple official testing framework is based on OCUnit traditional testing framework , the test is very simple to write up.

XCTest advantages and disadvantages: 

        1) advantage: with  Xcode integration of depth, a dedicated Test navigation bar,  

        2) Disadvantages: Because the test is limited to official API , thus function is not very rich. On the writing and readability is not very good. In too many test cases, because of the various test methods are fragmented, want to find a particular test in a long test file and thoroughly understand what this test is not a very easy thing to do. All tests were done by the assertion, and very often assert meaning not particularly clear, for the delivery of the project or when new developers to join, often takes a lot to understand or conversion costs. In addition, description of each test have been written after the assertion, inclusion in the code, it is difficult to find. Use XCTest test Another problem is difficult to mock or Stub .

 

2, XCTestCase Overview

        XCTestCase is a unit testing tool provided by Apple's official, it's not initialize user control, developers do not need a manual for XCTestCase a subclass be alloc and init or call the static initialization method of operation.

        For unit test a function module (for a class ), this class only need to create a separate inheritance in XCTestCase , after the realization of the following basic functions in a file (usually the system will default to create these three functions) requires a logical test only need developers to customize in order to test the beginning of the function, and then implement your own for a function, return numerical results, operation of test scripts can press over there comman + u perform the function of the head of a blue mark appears represented by the test, or directly reported to the red error.

import XCTest
@testable import test

class testTests: XCTestCase {

    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }

    func testExample() {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    func testPerformanceExample() {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }

}

From the comments we can know the meaning of these four functions

function use

setUp 

When inheritance and XCTestCase function test file started running 
tearDown  Inheritance and XCTestCase test function execution After running
test Example  Examples of function test
testPerformanceExample Performance Testing

 

3, the frame unit testing using XCTest

1) Create a unit test Target

Way to create unit tests There are two kinds of target

Method 1: When creating a new project, check Include Unit Tests, created the project will generate a unit test target, target name defaults to the project name + Tests

Second way: Create a project that already exists, press comman + 5 xcode open test navigator, click the + button in the lower left corner, then select New Unit Test Target from the menu ...

创建完成,如下图所示,

运行这个测试类的方法有三种:

(1)Product\Test 或者 Command-U。这实际上会运行所有测试类。

(2)点击测试导航器中的箭头按钮。

(3)点击中缝上的钻石图标

2)XCTest的测试范围:

(1)基本逻辑测试处理测试

(2)异步加载数据测试

(3)数据mock测试

3)XCTest常用的一些判断工具都是以”XCT“开头的

//断言,最基本的测试,如果expression为true则通过,否则打印后面格式化字符串
  XCTAssert(expression, format...)
 
  //Bool测试:  
  XCTAssertTrue(expression, format...)
  XCTAssertFalse(expression, format...)
 
  //相等测试
  XCTAssertEqual(expression1, expression2, format...)
  XCTAssertNotEqual(expression1, expression2, format...)
 
  //double float 对比数据测试使用
  XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, format...)
  XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, format...)
 
  //Nil测试,XCTAssert[Not]Nil断言判断给定的表达式值是否为nil
  XCTAssertNil(expression, format...)
  XCTAssertNotNil(expression, format...)
 
  //失败断言     
  XCTFail(format...)

4)XCTest的简单使用

例子说明:

函数 说明

testExample

全局变量f1 + f2 相加是否等于固定的数,断言是否相等

testIsPrimenumber

判断是否是素数 断言是否返回真

 

import XCTest
@testable import test

class SampleTests: XCTestCase {
    
    var f1 : Float?
    var f2 : Float?

    override func setUp() {
        super.setUp()
        
        //在测试方法执行前设置变量
        f1 = 10.0
        f2 = 20.0
    }

    override func tearDown() {
        //在测试方法执行完成后,清除变量
       super.tearDown()
    }

    func testExample() {
        XCTAssertTrue(f1! + f2! == 30.0)
    }
    
    //simpleTest
    func testIsPrimenumber(){
        let oddNumber = 5
        XCTAssertTrue(isPrimenumber(Double(oddNumber)))
    }
    func isPrimenumber(_ number : Double)->Bool{
        for no in 1...Int(sqrt(number)) {
            if Int(number)/no != 0{
                return true
            }
        }
        return false
    }

    func testPerformanceExample() {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }

}

5)XCTAssert测试模型

(1)创建一个BullsEyeGame模型类

import Foundation

class BullsEyeGame {
    var round = 0
    let startValue = 50
    var targetValue = 50
    var scoreRound = 0
    var scoreTotal = 0
    
    init() {
        startNewGame()
    }
    
    func startNewGame() {
        round = 0
        scoreTotal = 0
        startNewRound()
    }
    
    func startNewRound() {
        round = round + 1
        scoreRound = 0
        targetValue = 1 + (Int(arc4random()) % 100)
    }
    
    func check(guess: Int) -> Int {
        let difference = abs(targetValue - guess)
        //    let difference = guess - targetValue
        scoreRound = 100 - difference
        scoreTotal = scoreTotal + scoreRound
        
        return difference
    }
}

(2)用XCTAssert测试BullsEyeGame模型类中一个核心功能:一个BullsEyeGame对象能够正确计算出一局游戏的得分吗?

主要步骤:

在BullsEyeTests.swift中,import语句下面添加 :@testable import test

在 BullsEyeTests类头部加入一个属性:var gameUnderTest : BullsEyeGame!

在setup()方法中创建一个新的BullsEyeTests对象,位于 super.setup() 方法后

在 tearDown() 方法中释放你的 SUT 对象,在调用 super.tearDown() 方法之前

开始编写测试方法testScoreIsComputed

:测试方法的名字总是以 test 开头,后面加上一个对测试内容的描述。           

     Given-When-Then 结构源自 BDD(行为驱动开发),是一个对客户端友好的、更少专业术语的叫法。另外也可以叫做 Arrange-Act-Assert 和 Assemble-Activate-Assert。  

    将测试方法分成 given、when 和 then 三个部分是一种好的做法:

        在 given 节,应该给出要计算的值:在这里,我们给出了一个猜测数,你可以指定它和 targetValue 相差多少。

        在 when 节,执行要测试的代码,调用 gameUnderTest.check(_:)方法。

         在 then 节,将结果和你期望的值进行断言(这里,gameUnderTest.scoreRound 应该是 100-5),如果测试失败,打印指定的消息。
import XCTest
@testable import test

class BullsEyeTests: XCTestCase {

    var gameUnderTest : BullsEyeGame!
    
    override func setUp() {
        super.setUp()
        
        gameUnderTest = BullsEyeGame()
        gameUnderTest.startNewGame()
    }

    override func tearDown() {
        gameUnderTest = nil
        
        super.tearDown()
    }

    // XCTAssert to test model
    func testScoreIsComputed() {
        // 1. given
        let guess = gameUnderTest.targetValue + 5
        
        // 2. when
        _ = gameUnderTest.check(guess: guess)
        
        // 3. then
        XCTAssertEqual(gameUnderTest.scoreRound, 95, "Score computed from guess is wrong")
    }
    
    func testPerformanceExample() {
        
        self.measure {
            
        }
    }

}

(3)点击中缝上或者测试导航器上的钻石图标。App 会编译并运行,钻石图标会变成绿色的对勾

 

6)用 XCTestExpectation 测试异步操作

异步测试需要用到的场景:

(1)打开文档

(2)在其他线程工作

(3)和服务器或者扩展进行交流

(4)网络活动

(5)动画

(6)UI测试的一些条件

举例:网络请求异步测试

步骤:

(1)pod导入alamofire,Target是你要测试的tests Target.
(2)新建期望,用alamofire 发起请求。
(3)请求回调里断言是否为空,fullfill期望看是否满足期望
(4)XCWaiter设置期望完成的时间
import XCTest
import Alamofire

@testable import test

class NetworkAsyncTests: XCTestCase {

    override func setUp() {
        super.setUp()
    }

    override func tearDown() {
        super.tearDown()
    }

    func testAsynNetworkTest() {
        let networkExpection = expectation(description: "networkDownSuccess")
        Alamofire.request("http://www.httpbin.org/get?key=Xctest", method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON { (respons) in
            XCTAssertNotNil(respons)
            networkExpection.fulfill()
            }
        
        //设置XCWaiter等待期望时间,只是细节不同。
//        waitForExpectations(timeout: 0.00000001)
//        wait(for: [networkExpection], timeout: 0.00000001)

            
        //XCTWaiter.Result  枚举类型如下
        /*
        public enum Result : Int {
            
            
            case completed
            
            case timedOut
            
            case incorrectOrder
            
            case invertedFulfillment
            
            case interrupted
        }
        */
        let result = XCTWaiter(delegate: self).wait(for: [networkExpection], timeout:  1)
        if result == .timedOut {
            print("超时")
        }
        
    }

    func testPerformanceExample() {
        
        self.measure {
           
        }
    }

}

7)模拟对象和交互

 大部分 App 会和系统或库对象打交道——你无法控制这些对象——和这些对象交互时测试会变慢和不可重现,这违背了 FIRST 原则的其中两条。但是,你可以通过从存根获取数据或者写入模拟对象写入来模拟这种交互。

步骤:

(1)在 import语句后导入@testable import HalfTunes
(2)定义SUT是vc以及需要准备好预下载的数据
(3)在setup 函数中设置配置sut
(4)编写测试方法

模拟网络请求: DHURLSessionMock.swift 已经定义好了
模拟数据:https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3 下载得到一个一个 1.txt 之类的文件中。打开它,检查它的 JSON 格式,然后重命名为 abbaData.json,然后将它拖到 testSimulationObjectsTests 的文件组中

import XCTest
@testable import test

class testSimulationObjectsTests: XCTestCase {
    //声明SUT被测对象
    var controllerUnderTest: SearchViewController!
    
    override func setUp() {
        super.setUp()
        //构建SUT对象
        controllerUnderTest = UIStoryboard(name: "Main",
                                           bundle: nil).instantiateViewController(withIdentifier: "SearchVC") as! SearchViewController
        
        //获取下载好并拖入项目的json文件
        let testBundle = Bundle(for: type(of: self))
        let path = testBundle.path(forResource: "abbaData", ofType: "json")
        let data = try? Data(contentsOf: URL(fileURLWithPath: path!), options: .alwaysMapped)
        
        //项目中有一个 DHURLSessionMock.swift 文件。它定义了一个简单的协议 DHURLSession,包含了用 URL 或者 URLRequest 来创建 data taks 的方法。还有实现了这个协议的 URLSessionMock 类,它的初始化方法允许你用指定的数据、response 和 error 来创建一个伪造的 URLSession
        //构造模拟数据和response
        let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
        let urlResponse = HTTPURLResponse(url: url!, statusCode: 200, httpVersion: nil, headerFields: nil)
        //创建一个伪造的session对象
        let sessionMock = URLSessionMock(data: data, response: urlResponse, error: nil)
        //将伪造的对象注入app的属性中
        controllerUnderTest.defaultSession = sessionMock
    }
    
    override func tearDown() {
        //释放SUT对象
        controllerUnderTest = nil
        super.tearDown()
    }
    
    // 用 DHURLSession 协议和模拟数据伪造 URLSession
    func test_UpdateSearchResults_ParsesData() {
        // given
        let promise = expectation(description: "Status code: 200")
        
        // when
        XCTAssertEqual(controllerUnderTest?.searchResults.count, 0, "searchResults should be empty before the data task runs")
        let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
        let dataTask = controllerUnderTest?.defaultSession.dataTask(with: url!) {
            data, response, error in
            // 如果 HTTP 请求成功,调用 updateSearchResults(_:) 方法,它会将数据解析成 Tracks 对象
            if let error = error {
                print(error.localizedDescription)
            } else if let httpResponse = response as? HTTPURLResponse {
                if httpResponse.statusCode == 200 {
                    promise.fulfill()
                    self.controllerUnderTest?.updateSearchResults(data)
                }
            }
        }
        dataTask?.resume()
        waitForExpectations(timeout: 5, handler: nil)
        
        // then
        XCTAssertEqual(controllerUnderTest?.searchResults.count, 3, "Didn't parse 3 items from fake response")
    }
    
    // Performance
    func test_StartDownload_Performance() {
        let track = Track(name: "Waterloo", artist: "ABBA", previewUrl: "http://a821.phobos.apple.com/us/r30/Music/d7/ba/ce/mzm.vsyjlsff.aac.p.m4a")
        measure {
            self.controllerUnderTest?.startDownload(track)
        }
    }
    
    func testPerformanceExample() {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }
        
}

8)模拟写入mock对象

BullsEye有两种游戏方式:用户可以拖动 slider 来猜数字,也可以通过 slider 的位置来猜数字。右下角的 segmented 控件可以切换游戏方式,并将 gameStyle 保存到 UserDefaults。

这个测试将检查 app 是否正确地保存了 gameStyle 到 UserDefaults 里。

步骤:

(1)在 BullsEyeMockTests 中声明 SULT 和 MockUserDefaults 对象
(2)在 setup() 方法中,创建 SUT 和伪造对象,然后将伪造对象注入到 SUT 的属性中
(3)在 tearDown() 中释放 SUT 和伪造对象
(4)模拟和 UserDefaults 的交互
import XCTest
@testable import test

//MockUserDefaults 重写了 set(_:forKey:) 方法,用于增加 gameStyleChanged 的值。通常你可能认为应当使用 Bool 变量,但使用 Int 能带来更多的好处——例如,在你的测试中你可以检查这个方法是否真的被调用过一次。
class MockUserDefaults: UserDefaults {
    var gameStyleChanged = 0
    override func set(_ value: Int, forKey defaultName: String) {
        if defaultName == "gameStyle" {
            gameStyleChanged += 1
        }
    }
}

class BullsEyeMockTests: XCTestCase {

    //声明 SULT 和 MockUserDefaults 对象:
    var BullsEyeUnderTest: BullsEyeGameViewController!
    var mockUserDefaults: MockUserDefaults!
    
    override func setUp() {
        super.setUp()
        //创建 SUT 和伪造对象,然后将伪造对象注入到 SUT 的属性中
        BullsEyeUnderTest = UIStoryboard(name: "Main",
                                         bundle: nil).instantiateViewController(withIdentifier: "BullsEyeGameVC") as! BullsEyeGameViewController
        mockUserDefaults = MockUserDefaults(suiteName: "testing")!
        BullsEyeUnderTest.defaults = mockUserDefaults
    }
    
    override func tearDown() {
        //释放 SUT 和伪造对象:
        BullsEyeUnderTest = nil
        mockUserDefaults = nil
        super.tearDown()
    }
    
    // 模拟和 UserDefaults 的交互
    func testGameStyleCanBeChanged() {
        // given
        let segmentedControl = UISegmentedControl()
        
        // when
        XCTAssertEqual(mockUserDefaults.gameStyleChanged, 0, "gameStyleChanged should be 0 before sendActions")
        segmentedControl.addTarget(BullsEyeUnderTest,
                                   action: #selector(BullsEyeGameViewController.chooseGameStyle(_:)), for: .valueChanged)
        segmentedControl.sendActions(for: .valueChanged)
        
        // then
        XCTAssertEqual(mockUserDefaults.gameStyleChanged, 1, "gameStyle user default wasn't changed")
    }
    
    func testPerformanceExample() {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }
    
}

4、Code Converage工具使用

Code Converage主要是用来检测测试的覆盖率

1)在product->scheme->Edit Scheme

2)选中test ->option->勾选Code Converage

(3)按command+u 执行测试代码,打开Xcode左边窗口的Report Navigator,找到 Project Log

(4)选择Test可以看到一下界面

(5)再选中coverage。可以查看代码的覆盖率,打开详情,可以点击文件进入查看测试在该文件的覆盖情况,橘黄色的代表还未执行的,绿色代表执行的,右边的次数代表执行的次数

具体代码:github

Guess you like

Origin blog.csdn.net/lin1109221208/article/details/91955462