swift 单元测试1
1、为什么要举行单元测试?
答:单元测试是为了制止你的app变成布满bug的软件,让我们在开发过程中能更好的发现缺陷,进步代码质量,也能包管在代码重构时实时发现改动带来的标题。
2、单元测试应该测什么?
1)核心功能:模子类和方法,以及它们和控制器的交互2)最常用的UI操纵3)边际条件4)bug修复3、单元测试必要依照的原则是什么?
FIRST原则--测试的最佳实践,依照FIRST原则会让你的测试更加清晰有用1)Fast:测试的运行速率要快,如许人们就不介意你运行它们2)Independent/Isolated:一个测试不应当依靠于另一个测试3)Repeatable:同一个测试,每次都应该得到类似的效果。外部数据提供者和并发标题会导致间歇性的堕落4)Self-validating:测试应当是完全自动化的,输出效果要么是pass要么是fail,而不是依靠步伐员地日志文件的表明5)Timely:抱负环境下,测试的编写,应当在编写要测试的产物代码之前4、单元测试的优点有哪些?
答:
1)使开发职员更自大2)代码不会退化。不会由于改了bug而导致别的的bug3)在有良好的单元测试环境下,可以放心的举行代码重构4)良好的单元测试,本身就是利用阐明,偶然比文档更有用现在许多的开源库、开源项目都加入了单元测试,比方swift版本的AFN--Alamofire,就编写了大量的测试代码,现在单元测试告急分为TDD和BDD两种头脑模式。
5、什么是TDD?
TDD是 Test Drive Development,指的是测试驱动开发,相对于寻常头脑模式来说,是一种比力非常的做法我们一样寻常都是先编写产物代码,在写测试代码,而TDD恰好相反,其头脑是先写测试代码,然后再编写相应的产物代码。
TDD一样寻常依照 red->green->refactor 的步调,即(报错->通过->重构),由于是先写了测试代码,而还未添加产物代码,以是编译器会给出赤色报警,当把相应的产物代码添加美满后,并让单元测试用例通过测试,通过的状态为绿色,云云反复直到各种边界和测试都举行完毕,此时我们就可以得到一个稳固的产物,以是可以大胆的对产物代码举行重构,只要包管项目末了是绿色状态,就说嘛重构的代码没标题。
TDD的过程,类似于脚本语言的交互式编程,写几行代码,就可以查抄运行效果,如果效果有误,则要把近来的代码重写,知道单元测试效果正确为止。
6、什么是BDD?
BDD是Behavior Drive Development ,指的是活动驱动开发,常用于灵敏开发中利用的测试方法,其告急是为相识决XCTest苹果官方测试框架测试时难以mock和stub的标题。
BDD提倡利用Given...When...Then 这种类似天然语言的形貌来编写测试代码,在objc中,现在比力流行的BDD框架有specta、Kiwi、ceder,github上start较多的是Kiwi,在swift中,专用的 BDD 测试框架是Quick和Sleipnir。
比方Alamofire中下载的测试:
7、什么是Stub?
Stub是指人为地让一个对象对某个方法返回我们事先规定好的值。
Stub运用的告急场景是你必要和别的开发职员协同开发时,别人的模块尚未完成,而你的模块必要用到别人的模块,这时就必要Stub。比方,后端的接口未完成,你的代码已经完成了,Stub可以伪造一个调用的返回。
ojbc下可以利用OHHTTPStubs来伪造网络的数据返回。swift下,仍要手动写stub。
8、什么是Mock?
Mock是一个非常轻易和stub混淆的概念,简朴来说,我们可以将Mock看做是一种更全面和更智能的Stub。
明确来说,Mock着实是一个对象,它是对现有类活动的一种模拟(或是岁现有接口实现的模拟)。
Mock和Stub最大的区别在于Stub只是简朴的方法更换,不涉及新的对象,被stub的对象可以是业务代码中真正的对象,而Mock活动本身产生新的(不大概在业务代码中出现)的对象,并依照类的界说相应某些方法。
Mock让你可以查抄某种环境下,一个方法是否被调用,大概一个属性是否被正确设值。objc下可以利用OCMock来mock对象。但是,由于swift的runtime比力弱,以是,swift上一样寻常要手动写mock。
swift 单元测试2
1、XCTest框架概述
XCTest是苹果官方的测试框架,是基于OCUnit的传统测试框架,测试编写起来非常简朴。
XCTest 的优缺点:
1)优点:与 Xcode 深度集成,有专门的Test 导航栏,
2)缺点:由于受限于官方测试API,因此功能不是很丰富。在誊写性和可读性上都不太好。在测试用例太多的时间,由于各个测试方法是割裂的,想在某个很长的测试文件中找到特定的某个测试并搞明确这个测试是在做什么并不是很轻易的事变。全部的测试都是由断言完成的,而许多时间断言的意义并不是特别的明确,对于项目交付大概新的开发职员加入时,通常要花上很大成原来举行明确大概转换。别的,每一个测试的形貌都被写在断言之后,混淆在代码之中,难以探求。利用XCTest测试别的一个标题是难以举行mock大概stub。
2、XCTestCase概述
XCTestCase是苹果官方提供的一个单元测试工具,它的初始化不是用户控制的,开发者无需手动针对XCTestCase的subclass举行alloc和init大概调用静态方法初始化的操纵。
针对一个功能模块的单元测试(针对某个class),只必要单独给这个类创建一个继续于XCTestCase,在文件中实现下面根本函数后(一样寻常体系会默认创建这三个函数),必要测试的逻辑只必要开发者自行界说以test开头的函数,然后在那边实现本身针对某个函数、返回数值效果、操纵等的测试脚本即可,按comman+u实验,函数头上出现出现蓝色的标记体现通过测试,否则直接报赤色错误。
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. } } }从表明我们可以知道这4个函数的意思
函数用途setUp继续与XCTestCase 函数测试文件开始实验的时间运行tearDown继续与XCTestCase 测试函数运行完之后实验testExample测试的例子函数testPerformanceExample性能测试3、利用XCTest框架举行单元测试
1)创建一个单元测试Target
单元测试target的创建方式有2种
方式一:在创建新项目时,勾选 Include Unit Tests,项目创建完成绩会生成一个单元测试target,target名称默以为 项目名称+Tests
方式二:在已存在的项目中创建,按comman+5 打开xcode的测试导航器,点击左下角的 + 按钮,然后从菜单中选择 New Unit Test Target…
运行这个测试类的方法有三种:
(1)Product\Test 大概 Command-U。这实际上会运行全部测试类。
(2)点击测试导航器中的箭头按钮。
(3)点击中缝上的钻石图标
(1)根本逻辑测试处置惩罚测试
(2)异步加载数据测试
(3)数据mock测试
- 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...)
函数阐明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. } } }
创建一个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 }}用XCTAssert测试BullsEyeGame模子类中一个核心功能:一个BullsEyeGame对象可以或许正确盘算出一局游戏的得分吗?
告急步调:
- 1.在BullsEyeTests.swift中,import语句下面添加 :@testable import test
- 2.在 BullsEyeTests类头部加入一个属性:var gameUnderTest : BullsEyeGame!
- 3.在setup()方法中创建一个新的BullsEyeTests对象,位于 super.setup() 方法后
- 4.在 tearDown() 方法中开释你的 SUT 对象,在调用 super.tearDown() 方法之前
- 5.开始编写测试方法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 { } } }末了:点击中缝上大概测试导航器上的钻石图标。App 会编译并运行,钻石图标会变成绿色的对勾
用 XCTestExpectation 测试异步操纵
异步测试必要用到的场景:
(1)打开文档
(2)在其他线程工作
(3)和服务器大概扩展举行交换
(4)网络活动
(5)动画
(6)UI测试的一些条件
举例:网络哀求异步测试
步调:
(1)pod导入alamofire,Target是你要测试的tests Target.(2)新建渴望,用alamofire 发起哀求。(3)哀求回调里断言是否为空,fullfill渴望看是否满足渴望(4)XCWaiter设置渴望完成的时间编写测试代码
import XCTestimport 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 { } } }模拟对象和交互
大部门 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. } } }模拟写入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. } } }Code Converage工具利用
Code Converage告急是用来检测测试的覆盖率
在product->scheme->Edit Scheme
选中test ->option->勾选Code Converage
按command+u 实验测试代码,打开Xcode左边窗口的Report Navigator,找到 Project Log
选择Test可以看到一下界面
再选中coverage。可以检察代码的覆盖率,打开详情,可以点击文件进入检察测试在该文件的覆盖环境,橘黄色的代表还未实验的,绿色代表实验的,右边的次数代表实验的次数
|