iOS 單元測(cè)試(Unit Test 和 UI Test)

一 :前言

很多的開發(fā)者 都聽說過單元測(cè)試,但是不可否認(rèn) 很多開發(fā)者 在實(shí)際開發(fā)中很少使用這個(gè) 單元測(cè)試。 大部分人想我自己把工程跑起來 一步步 按照 流程來測(cè)試 就行了 。在新建任何一個(gè) 工程時(shí) 很多小伙伴 對(duì)如下圖 所示的 的 Include Unit Tests 和 Include UI Tests ?感到疑惑。


why

其實(shí)這兩個(gè) 就是 Xcode 自帶的 UnitTest 。

百度百科 對(duì)于單元測(cè)試 的定義 如下

單元測(cè)試(unit testing),是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。對(duì)于單元測(cè)試中單元的含義,一般來說,要根據(jù)實(shí)際情況去判定其具體含義,如C語言中單元指一個(gè)函數(shù),Java里單元指一個(gè)類,圖形化的軟件中可以指一個(gè)窗口或一個(gè)菜單等??偟膩碚f,單元就是人為規(guī)定的最小的被測(cè)功能模塊。單元測(cè)試是在軟件開發(fā)過程中要進(jìn)行的最低級(jí)別的測(cè)試活動(dòng),軟件的獨(dú)立單元將在與程序的其他部分相隔離的情況下進(jìn)行測(cè)試。

二: 為什么需要使用 單元測(cè)試

1.0 ?設(shè)想你在在一個(gè)龐大的工程里面加個(gè)了 小功能 A ,按照我們前面的說法,是不是每次都要把工程跑起來,然后 去到 A處 然后進(jìn)行測(cè)試。甚至如果我們僅僅是想 測(cè)試一個(gè) 接口 返回的參數(shù) 而這個(gè)接口又需要使用到現(xiàn)有工程中的很多參數(shù) 等?如果使用 單元測(cè)試的話 就不用每次去 把整個(gè)工程跑起來,只需要跑你測(cè)試的部分。

2.0 我們可以使用單元測(cè)試測(cè) 某個(gè)方法的耗時(shí)和性能,單次 和 多次運(yùn)行的整體對(duì)比的。當(dāng)然你可以在 方法執(zhí)行前 獲取時(shí)間 ,方法結(jié)束 后獲取時(shí)間等方式 獲取時(shí)間消耗 姑且這樣寫 麻煩不說 但是如何 計(jì)算 CPU占用這些消耗呢?當(dāng)然我們可以使用instrument 來做更專業(yè)的測(cè)試。 相對(duì)而言 單元測(cè)試更加便捷 和 方便使用給我們省不少事。

三 :如何做單元測(cè)試

本文只介紹Xcode 自帶的 Unit Tests ?和 UI Tests

代碼下載地址:?https://gitee.com/DeLongYang/Performace_Test? 下的?UnitTestDemoTests 文件夾就是了。

水平有限 ,如有 不對(duì) 希望大家 不吝指正。工程需要安裝?Pods?。?

第一 如何添加單元測(cè)試

1.0 ?新建工程時(shí) 也就是 ?前言中的那兩個(gè)選項(xiàng) 勾選就可以了。

2.0 在現(xiàn)有的 工程中 添加單元測(cè)試 File ->New ->Target 在選項(xiàng)卡中 選擇 iOS UI Testing Bundle 和 iOS Unit Testing Bundle 這兩個(gè) 任選一個(gè)添加給 指定的工程就可以了。

下面我們分 ??UI Testing Bundle ?測(cè)試 ?和 ?UITest ?和 性能測(cè)試來 分別闡述。?


第二:?iOS Unit Tests Bundle ?

1.0 ?測(cè)試方法

打開 UnitTestDemoTestsTests.m ?文件 ,如下圖所示 鼠標(biāo)光標(biāo)選中菱形區(qū)域 就會(huì) 顯示這個(gè)播放的狀態(tài),然后

點(diǎn)擊 這個(gè)播放的狀態(tài) 就會(huì)測(cè)試這個(gè)方法。 或者 ?選擇左邊 testIsChinese 右邊也會(huì)出現(xiàn) 黑色的小播放按鈕 點(diǎn)擊也可以測(cè)試。

b

2.0 ?添加測(cè)試方法

下面這些是 系統(tǒng)自帶的方法 蘋果都給了我們注釋。?

- (void)setUp {

? ? [super setUp];

// 每個(gè)類中 測(cè)試方法調(diào)用前 ?先調(diào)用這個(gè)方法 ?以方便 開發(fā)者 做些 測(cè)試前的準(zhǔn)備

? ? // Put setup code here. This method is called before the invocation of each test method in the class.

}

- (void)tearDown {

// 當(dāng)這個(gè) 類中的 所有的 測(cè)試 方法 ?測(cè)試完后 ?會(huì)調(diào)用這個(gè)方法 ?

? ? // Put teardown code here. This method is called after the invocation of each test method in the class.

? ? [super tearDown];

}

- (void)testExample {

? ? // This is an example of a functional test case.

? ? // Use XCTAssert and related functions to verify your tests produce the correct results.

}

這是 我們做性能測(cè)試的方法 ? 把要測(cè)試的方法 放進(jìn)Block 就可以了 。后面會(huì)重點(diǎn)介紹?

- (void)testPerformanceExample {

? ? // This is an example of a performance test case.

? ? [self measureBlock:^{

? ? ? ? // Put the code you want to measure the time of here.

? ? ? ? for (int i =0; i<1000; i++) {

? ? ? ? ? ? NSLog(@"this is a example");

? ? ? ? }

? ? }];

}

注意??! 所有需要測(cè)試的方法名以 test 開頭 。 剛添加進(jìn)去的方法可能左上角的 菱形按鈕 不會(huì)立即顯示 ,你可以 Comand + U ?Build 這個(gè)測(cè)試文件一下 就可以了 或者 ?選中?- (void)testPerformanceExample ?測(cè)下這個(gè)方法就可以了 或者 從 圖b 中 3 ?處 測(cè)試都可以。

整體的測(cè)試方法 和 Java 中的Junit 有點(diǎn)類似 。 測(cè)試沒通過 左邊就會(huì)有一個(gè) 大紅叉 ?,能通過的方法 就會(huì)有一個(gè) 綠色的鉤。?

- (void)testExample {

? ? // This is an example of a functional test case.

? ? // Use XCTAssert and related functions to verify your tests produce the correct results.


? ? NSLog(@"自定義測(cè)試testExample");

? ? int a =?3;

? ? XCTAssertTrue(a == 0,"a 不能 等于 0");


}

下面是一些 ?常用的 ? 測(cè)試語法。 注意 ?空 和 nil ?還是有區(qū)別的 。 讀者自己可以測(cè)試一下。?

- (void)testAssertSyntax

{

//? ? XCTFail(@"this is a fail test");? // 生成一個(gè)失敗的測(cè)試

//? ? XCTAssertNil(@"not a nil string",@"string must be nil"); // XCTAssertNil(a1, format...) 為空判斷, a1 為空時(shí)通過,反之不通過;

//? ? XCTAssertNil(@"",@"string must be nil");? // 注意@"" 一樣無法通過

? ? XCTAssertNil(nil,@"object must be nil");

? ? // XCTAssertNotNil(a1, format…) 不為nil 判斷,a1不為? nil 時(shí)通過,反之不通過;

? ? // 注意空? 和 nil 還是有區(qū)別的

? ? XCTAssertNotNil(@"not nil string", @"string can not be nil");

//? ? XCTAssert(expression, format...) 當(dāng)expression求值為TRUE時(shí)通過; expression 為一個(gè)表達(dá)式

//? ? XCTAssert((2 > 2), @"expression must be true");

? ? XCTAssert((3>2),@"expression is true");

? ? // XCTAssertTrue(expression, format...) 當(dāng)expression求值為TRUE時(shí)通過;>0 的都視為 true

? ? XCTAssertTrue(1, @"Can not be zero");

? ? // XCTAssertFalse(expression, format...) 當(dāng)expression求值為False時(shí)通過;

? ? XCTAssertFalse((2 < 2), @"expression must be false");

? ? // XCTAssertEqualObjects(a1, a2, format...) 判斷相等, [a1 isEqual:a2] 值為TRUE時(shí)通過,其中一個(gè)不為空時(shí),不通過;

? ? XCTAssertEqualObjects(@"1", @"1", @"[a1 isEqual:a2] should return YES");

? ? //? ? XCTAssertEqualObjects(@"1", @"2", @"[a1 isEqual:a2] should return YES");

? ? //? ? XCTAssertNotEqualObjects(a1, a2, format...) 判斷不等, [a1 isEqual:a2] 值為False時(shí)通過,

//? ? ? ? XCTAssertNotEqualObjects(@"1", @"1", @"[a1 isEqual:a2] should return NO");

? ? XCTAssertNotEqualObjects(@"1", @"2", @"[a1 isEqual:a2] should return NO");

? ? // XCTAssertEqual(a1, a2, format...) 判斷相等(當(dāng)a1和a2是 C語言標(biāo)量、結(jié)構(gòu)體或聯(lián)合體時(shí)使用,實(shí)際測(cè)試發(fā)現(xiàn)NSString也可以);

? ? // 1.比較基本數(shù)據(jù)類型變量

? ? //? ? XCTAssertEqual(1, 2, @"a1 = a2 shoud be true"); // 無法通過測(cè)試

? ? XCTAssertEqual(1, 1, @"a1 = a2 shoud be true"); // 通過測(cè)試

? ? // 2.比較NSString對(duì)象

? ? NSString *str1 = @"1";

? ? NSString *str2 = @"1";

//? ? NSString *str3 = str1;

? ? XCTAssertEqual(str1, str2, @"a1 and a2 should point to the same object"); // 通過測(cè)試

? ? //? ? XCTAssertEqual(str1, str3, @"a1 and a2 should point to the same object"); // 通過測(cè)試

? ? // 3.比較NSArray對(duì)象

? ? ? ? NSArray *array1 = @[@1];

? ? ? ? NSArray *array2 = @[@1];

? ? ? ? NSArray *array3 = array1;

//? ? XCTAssertEqual(array1, array2, @"a1 and a2 should point to the same object"); // 無法通過測(cè)試

? ? XCTAssertEqual(array1, array3, @"a1 and a2 should point to the same object"); // 通過測(cè)試

? ? // XCTAssertNotEqual(a1, a2, format...) 判斷不等(當(dāng)a1和a2是 C語言標(biāo)量、結(jié)構(gòu)體或聯(lián)合體時(shí)使用);

? ? // XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...) 判斷相等,(double或float類型)提供一個(gè)誤差范圍,當(dāng)在誤差范圍(+/- accuracy )以內(nèi)相等時(shí)通過測(cè)試;

//? ? XCTAssertEqualWithAccuracy(1.0f, 1.5f, 0.25f, @"a1 = a2 in accuracy should return NO");? // 測(cè)試沒法通過

? ? // XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判斷不等,(double或float類型)提供一個(gè)誤差范圍,當(dāng)在誤差范圍以內(nèi)不等時(shí)通過測(cè)試;

? ? XCTAssertNotEqualWithAccuracy(1.0f, 1.5f, 0.25f, @"a1 = a2 in accuracy should return NO");? // 測(cè)試通過?

? ? // XCTAssertThrows(expression, format...) 異常測(cè)試,當(dāng)expression發(fā)生異常時(shí)通過;反之不通過;(很變態(tài))

? ? // XCTAssertThrowsSpecific(expression, specificException, format...) 異常測(cè)試,當(dāng)expression發(fā)生 specificException 異常時(shí)通過;反之發(fā)生其他異?;虿话l(fā)生異常均不通過;

? ? // XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...) 異常測(cè)試,當(dāng)expression發(fā)生具體異常、具體異常名稱的異常時(shí)通過測(cè)試,反之不通過;

? ? // XCTAssertNoThrow(expression, format…) 異常測(cè)試,當(dāng)expression沒有發(fā)生異常時(shí)通過測(cè)試;

? ? // XCTAssertNoThrowSpecific(expression, specificException, format...)異常測(cè)試,當(dāng)expression沒有發(fā)生具體異常、具體異常名稱的異常時(shí)通過測(cè)試,反之不通過;

? ? // XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...) 異常測(cè)試,當(dāng)expression沒有發(fā)生具體異常、具體異常名稱的異常時(shí)通過測(cè)試,反之不通過

}

像 我們前文 中說到的 ?測(cè)試某個(gè)接口,源碼中的 ?testRequest 方法。 這里就不貼了 。?

/**

測(cè)試 網(wǎng)絡(luò)請(qǐng)求的方法

*/

- (void)testRequest

{

? ? ? ?。。。。。。

}

這里 如果是新建的工程的話 運(yùn)行報(bào)錯(cuò) 。提示如下:

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app’s Info.plist file.

?請(qǐng)參考http://blog.csdn.net/maxdong24/article/details/53610127中的方案解決。

這是 iOS9 新增加的 傳輸安全性的規(guī)定。

3.0 ?新建 自定義的 測(cè)試文件

Command + N ? 選中Unit Test Case Class ?不是 Cocoa ?Touch Class ?!!!!? 筆者習(xí)慣性思維 好幾次選錯(cuò)。新建

UserTest.m 類, 同時(shí)在工程中 新建 User 類 ,具體的看源代碼中的。因?yàn)闆]有做UI測(cè)試 所以 ?setUp

中的一些方法 我們注釋掉不然會(huì) 報(bào)錯(cuò)。?

第二:?iOS UI Testing?

打開?UnitTestDemoTestsUITests.m 發(fā)現(xiàn) setUp 方法 和 UnitTest 中有些不同。多了一個(gè)?

self.continueAfterFailure = NO; 和?? [[[XCUIApplication alloc] init] launch]; ?蘋果都有解釋 。我們照做就可以了。

- (void)setUp {

? ? [super setUp];

? ? // Put setup code here. This method is called before the invocation of each test method in the class.

// 如果發(fā)生 測(cè)試不通過的情況 ,最好 停止程序的運(yùn)行?

// In UI tests it is usually best to stop immediately when a failure occurs.

? ? self.continueAfterFailure = NO;

// ?UI 測(cè)試必須 等應(yīng)用 先開啟 ,這個(gè)方法 可以確保應(yīng)用的開啟 在每個(gè)測(cè)試方法 測(cè)試的時(shí)候 。

? ? // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.

? ? [[[XCUIApplication alloc] init] launch];


? ? // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.

}

先 添加 測(cè)試方法?- (void)testLogin ?大擴(kuò)號(hào)中是 空白的

- (void)testLogin

{

}

?把 光標(biāo) 放進(jìn)到這個(gè) 大括號(hào)里面,看到 如下圖 c ?的小紅點(diǎn)。點(diǎn)擊這個(gè)錄制屏幕的按鈕。應(yīng)用啟動(dòng)。 我們點(diǎn)擊

首頁的 ViewController 中的兩個(gè) ?userName 和 ?passWord UITextField 輸入相關(guān)的內(nèi)容。 再點(diǎn)登陸 發(fā)現(xiàn)?- (void)testLogin的括號(hào)中自動(dòng)生成了代碼 。?

c

接著 ?直接測(cè)試?- (void)testLogin ?會(huì)發(fā)現(xiàn)重復(fù) 了我們錄屏?xí)r候的 輸入和 輸出 包括跳轉(zhuǎn)等等。

注意 在 OC 寫的工程中 如果 輸入中文 自動(dòng)生成的測(cè)試代碼有問題如圖 d 所示

- (void)testLoginTwo 就是這種現(xiàn)象 的代碼

d

但是在 swift 工程 中就沒有這種問題。 如何解決?

其實(shí)我們可以 自己 寫 測(cè)試 代碼 ?如 ?- (void)testLoginThree ?我們可以打斷點(diǎn) 看 打印 app 的結(jié)構(gòu) 。?

- (void)testLoginThree

{

? ? XCUIApplication *app = [[XCUIApplication alloc] init];

? ? // 下面這是一種通過 遍歷 獲取的形式

? ? for (NSInteger i = 0;i < app.textFields.count; i++) {

? ? ? ? if ([[app.textFields elementBoundByIndex:i] exists]) {//判斷是否存在

? ? ? ? ? ? [[app.textFields elementBoundByIndex:i] tap];//輸入框要獲取焦點(diǎn)后才能給輸入框自動(dòng)賦值

? ? ? ? ? ? if (i == 0 ) {

? ? ? ? ? ? ? ? // 給 第一個(gè)userName 自動(dòng)賦值 你好

? ? ? ? ? ? ? ? [[app.textFields elementBoundByIndex:i] typeText:@"你好"];

? ? ? ? ? ? }

? ? ? ? ? ? // 給 第二個(gè)userPass 自動(dòng)賦值 德龍

? ? ? ? ? ? if (i == 1) {

? ? ? ? ? ? ? ? ? [[app.textFields elementBoundByIndex:i] typeText:@"德龍"];

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

下面看另外一種寫法 ?這種發(fā)放比較 特別?

我們之所以 能使用?textFields[@"username:"]; ?和 ?app.buttons[@"login"] ?這種語法 來找到對(duì)應(yīng)的控件 是因?yàn)?/p>

usernameTextField 的Placeholder 是 username: ?而 ?登錄按鈕的title 是 login ??。?!storyboard中都有設(shè)置 。 不然會(huì)報(bào)錯(cuò)。

- (void)testLoginFour

{

? ? XCUIApplication *app = [[XCUIApplication alloc] init];

//? ? //XCUIElement 這是 UI 元素的代理。元素都有類型和唯一標(biāo)識(shí)??梢越Y(jié)合使用來找到元素在哪里,如當(dāng)前界面上的一個(gè)輸入框

? ? XCUIElementQuery *textFields = app.textFields;

//? ? XCUIElement *usernameTextField = [textFields objectForKeyedSubscript:@"username:"];

? ? XCUIElement *usernameTextField = textFields[@"username:"];

? ? [usernameTextField tap];

? ? [usernameTextField typeText:@"德龍"];

? ? XCUIElement *passwordTextField = app.textFields[@"password:"];

? ? [passwordTextField tap];

? ? [passwordTextField tap];

? ? [passwordTextField typeText:@"楊"];


? ? //

? ? [app.buttons[@"login"] tap];


? ? //登錄成功后的控制器的title為loginSuccess,只需判斷控制器的title時(shí)候一樣便可判斷登錄是否成功

? ? NSLog(@"title is %@",app.navigationBars.element.identifier);

? XCTAssertEqualObjects(app.navigationBars.element.identifier, @"loginSuccess");


? ? // 延時(shí) 3s 再消失

? ? XCUIElement *window = [app.windows elementBoundByIndex:0];

? ? [window pressForDuration:3];

}

核心類是?XCUIElement ?和?XCUIElementQuery ?這兩個(gè)類 , 可以看下這兩個(gè)類 看下是如何設(shè)計(jì)的。 這種設(shè)計(jì)思路 筆者認(rèn)為 和FMDB 中的 FMResult 等的設(shè)計(jì)思路是差不多的。 另外還有要給 ?TableViewController 的測(cè)試

這里就不再贅述了。

第三: 性能測(cè)試

我們回到?UnitTestDemoTestsTests.m 中的?- (void)testPerformanceExample 方法 寫入

- (void)testPerformanceExample {

? ? // This is an example of a performance test case.

? ? [self measureBlock:^{

? ? ? ? // Put the code you want to measure the time of here.

? ? ? ? for (int i =0; i<1000; i++) {

? ? ? ? ? ? NSLog(@"this is a example");

? ? ? ? }

? ? }];

}

第一次運(yùn)行 會(huì)出現(xiàn) 改方法的耗時(shí) ,然后 提示 noBase ... ?點(diǎn)進(jìn)這個(gè)提示 我們可以設(shè)置base 設(shè)置之后再運(yùn)行就點(diǎn)擊左邊的 灰色的 鉤 會(huì)出現(xiàn)下圖e 所示的


e


對(duì)比 。 耗時(shí)和CPU 消耗等,以及多次的 對(duì)比都有 ,比較簡(jiǎn)單 。?

到這就結(jié)束了 , 滿地賣萌打滾求喜歡 ,各位看官打賞打賞?

二: 參考文檔

http://www.itdecent.cn/p/8bbec078cabe

http://www.itdecent.cn/p/07cfc17916e8

http://www.itdecent.cn/p/560e397efdc7

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 單元測(cè)試不是一個(gè)小工程,需要多用些時(shí)間才能做好,不要希望通過這個(gè)文章就能掌握單元測(cè)試,這只是一個(gè)入門,需要自己動(dòng)手...
    勇不言棄92閱讀 8,097評(píng)論 9 60
  • 前言 單元測(cè)試和UI測(cè)試大致步驟網(wǎng)上很多文章都有,如果會(huì)的可以忽略,關(guān)鍵是錯(cuò)誤總結(jié),網(wǎng)上很少有文章提及到,感興趣的...
    _YGL_閱讀 5,397評(píng)論 20 23
  • 轉(zhuǎn)自:http://www.itdecent.cn/p/009844a0b9ed 什么是單元測(cè)試?一聽到單元測(cè)試這...
    YYT1992閱讀 926評(píng)論 0 2
  • 領(lǐng)導(dǎo)給的需求是: 關(guān)于單元測(cè)試的任務(wù)反饋: http://www.itdecent.cn/p/d15a7dea0c...
    SOI閱讀 4,094評(píng)論 1 34
  • 每個(gè)人都是一條會(huì)溺水的魚,無論是在時(shí)間的長(zhǎng)河,或是命運(yùn)的苦海。 前言 前天似乎是北方的小年,一個(gè)人在家,坐在桌前發(fā)...
    鵬入渤海_lion閱讀 710評(píng)論 0 2

友情鏈接更多精彩內(nèi)容