iOS單元測(cè)試怎么寫 ?

什么是單元測(cè)試 ?

針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來(lái)進(jìn)行正確性檢驗(yàn)的測(cè)試工作娃善。程序單元是應(yīng)用的最小可測(cè)試部件多柑。對(duì)于面向?qū)ο缶幊棠淌牵钚卧褪?strong>方法
iOS 集成了自己的測(cè)試框架 OCUnitUITests

為什么單元測(cè)試 ?

執(zhí)行單元測(cè)試,就是為了證明這段代碼的行為和我們期望的一致竣灌,比如測(cè)試一些功能是否正常聂沙,接口是否能正常,特別在一些大的項(xiàng)目初嘹,以防止程序被誤改或引起新的問(wèn)題 及汉。
單元測(cè)試還具有一下幾個(gè)好處:

  • 協(xié)助程序員盡快找到BUG的具體位置
  • 讓程序員對(duì)自己的程序更有自信
  • 能夠讓程序員在提交項(xiàng)目之前就將代碼變的更加健壯
  • 能夠協(xié)助程序員更好的進(jìn)行開(kāi)發(fā)
  • 能夠向其他程序員展現(xiàn)你的程序該如何調(diào)用
  • 能夠讓項(xiàng)目主管更了解系統(tǒng)的當(dāng)前狀況
    如對(duì)其意義仍有疑惑請(qǐng)看這篇文章

單元測(cè)試 怎么寫 ?

我在剛寫單元測(cè)試,也看了網(wǎng)上有很多文章屯烦,但是依然無(wú)從下手坷随。本文總結(jié)了幾點(diǎn),并摘錄了一些例子驻龟。

  • ** 什么該測(cè)試 **

    1. 項(xiàng)目中的公共類中的公開(kāi)方法温眉,將公共組件加入單元測(cè)試可以大大加強(qiáng)底層操作的正確性和健壯性。
    2. 網(wǎng)絡(luò)數(shù)據(jù)層方法的測(cè)試翁狐,數(shù)據(jù)接口一般不會(huì)太多类溢,這里的測(cè)試可以保證接口的正常。
    3. 業(yè)務(wù)邏輯層測(cè)試露懒,這樣可以讓業(yè)務(wù)邏輯保持正確闯冷,產(chǎn)品發(fā)布后可以直接通過(guò)業(yè)務(wù)邏輯的單元測(cè)試來(lái)找到BUG。
    4. 修復(fù)bug時(shí)測(cè)試懈词, 如用戶反映有個(gè)bug 窃躲,可以先寫個(gè)測(cè)試復(fù)現(xiàn)bug,接著找到問(wèn)題修復(fù)bug 钦睡,測(cè)試驗(yàn)證蒂窒,最后要保留測(cè)試躁倒,
  • 更易于測(cè)試的代碼
    很多時(shí)候,項(xiàng)目中難免發(fā)生多個(gè)類之間的交互處理, 耦合度高洒琢,而這種操作非常的不好調(diào)試秧秉。
    單元測(cè)試的原則之一就在于我們用來(lái)測(cè)試的代碼要求功能很單一,這其實(shí)與良好的代碼設(shè)計(jì)的思想是非常相符的衰抑。
    一方面來(lái)說(shuō)象迎,良好的代碼結(jié)構(gòu)設(shè)計(jì)可以讓我們的測(cè)試用例的構(gòu)建更加快速簡(jiǎn)單;反過(guò)來(lái)單元測(cè)試逼著我們?nèi)ハ朕k法減少類之間的耦合以此來(lái)減少甚至排除測(cè)試的干擾呛踊。無(wú)論如何砾淌,如果你想成為更好的開(kāi)發(fā)者,單元測(cè)試是我們快速提升代碼認(rèn)知的重要手段之一谭网。

  • 關(guān)注覆蓋率
    單元測(cè)試寫的是否合理或者是否達(dá)到了要求的一個(gè)唯一的標(biāo)準(zhǔn)就是整個(gè)測(cè)試的代碼覆蓋率汪厨。代碼覆蓋率其實(shí)就是測(cè)試代碼所運(yùn)行到的實(shí)際程序路徑的覆蓋率。在實(shí)際程序中可能會(huì)有很多的循環(huán)愉择、判斷等分支路徑劫乱。一個(gè)好的單元測(cè)試應(yīng)該能夠?qū)⑺锌赡艿穆窂蕉紝⒆叩剑@樣就可以保證大多數(shù)情況都測(cè)試過(guò)了锥涕。

    1. 邊界條件數(shù)據(jù)衷戈,比如值類型數(shù)據(jù)的最大值、最小值层坠、DBNull殖妇,或者是方法中所使用的條件邊界,例如a>100那么100就變成了這個(gè)數(shù)據(jù)的邊界破花。而且在測(cè)試的時(shí)候還必須把超出邊界的數(shù)據(jù)作為測(cè)試條件進(jìn)行測(cè)試谦趣。

    2. 空數(shù)據(jù),一般空數(shù)據(jù)對(duì)應(yīng)于引用類型的數(shù)據(jù)旧乞,也就是Null值蔚润。

    3. ** 格式不正確數(shù)據(jù)**,對(duì)于引用類型的數(shù)據(jù)或者結(jié)構(gòu)對(duì)象尺栖,類型雖然正確但是其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)不正確的數(shù)據(jù)嫡纠。例如一個(gè)數(shù)據(jù)庫(kù)實(shí)體對(duì)象,數(shù)據(jù)庫(kù)中要求其某個(gè)屬性必須為非空延赌,但是這時(shí)我們可以屬于一個(gè)空除盏。這樣這個(gè)對(duì)象就屬于一個(gè)不正確數(shù)據(jù)庫(kù)。

在編寫單元測(cè)試代碼的時(shí)候先了解到被測(cè)試方法可能會(huì)使用的外部數(shù)據(jù)挫以,然后將這些外部數(shù)據(jù)一次設(shè)置為上面規(guī)定的這幾種情況者蠕,然后再執(zhí)行方法。這樣就基本可以達(dá)到外部數(shù)據(jù)所有情況都能夠正確測(cè)試到了掐松。


摘錄的一些例子

** 例1**
在邏輯測(cè)試的某個(gè)操作步驟前后踱侣,應(yīng)該有對(duì)應(yīng)的數(shù)據(jù)發(fā)生了改變粪小,這樣才能夠方便我們進(jìn)行測(cè)試:

@interface LXDTestsModel : NSObject
 
@property (nonatomic, readonly, copy) NSString * name;
@property (nonatomic, readonly, strong) NSNumber * age;
@property (nonatomic, readonly, assign) NSUInteger flags;
 
+ (instancetype)modelWithName: (NSString *)name age: (NSNumber *)age flags: (NSUInteger)flags;
 
- (instancetype)initWithDictionary: (NSDictionary *)dict;
- (NSDictionary *)modelToDictionary;
 
@end

在測(cè)試用例中,我定義了一個(gè)testModelConvert
方法用來(lái)測(cè)試模型跟json之間的轉(zhuǎn)換是否正確:

- (void)testModelConvert
{
    NSString * json = @"{\"name\":\"SindriLin\",\"age\":22,\"flags\":987654321}";
    NSMutableDictionary * dict = [[NSJSONSerialization JSONObjectWithData: [json dataUsingEncoding: NSUTF8StringEncoding] options: kNilOptions error: nil] mutableCopy];
 
    LXDTestsModel * model = [[LXDTestsModel alloc] initWithDictionary: dict];
    XCTAssertNotNil(model);
    XCTAssertTrue([model.name isEqualToString: @"SindriLin"]);
    XCTAssertTrue([model.age isEqual: @(22)]);
    XCTAssertEqual(model.flags, 987654321);
    XCTAssertTrue([model isKindOfClass: [LXDTestsModel class]]);
 
    model = [LXDTestsModel modelWithName: @"Tessie" age: dict[@"age"] flags: 562525];
    XCTAssertNotNil(model);
    XCTAssertTrue([model.name isEqualToString: @"Tessie"]);
    XCTAssertTrue([model.age isEqual: dict[@"age"]]);
    XCTAssertEqual(model.flags, 562525);
 
    NSDictionary * modelJSON = [model modelToDictionary];
    XCTAssertTrue([modelJSON isEqual: dict] == NO);
 
    dict[@"name"] = @"Tessie";
    dict[@"flags"] = @(562525);
    XCTAssertTrue([modelJSON isEqual: dict]);
}

** 例二 **
由于單元測(cè)試是在主線程中進(jìn)行的抡句,因此異步操作的測(cè)試在執(zhí)行完畢之前探膊,往往已經(jīng)結(jié)束了。有兩種方法解決

  • 采用while()的方式無(wú)限循環(huán)等待
//waitForExpectationsWithTimeout是等待時(shí)間待榔,超過(guò)了就不再等待往下執(zhí)行逞壁。
#define WAIT do {
 [self expectationForNotification:@"RSBaseTest" object:nil handler:nil];
 [self waitForExpectationsWithTimeout:30 handler:nil];
} while (0)
#define NOTIFY [[NSNotificationCenter defaultCenter]postNotificationName:@"RSBaseTest" object:nil]
-(void)testRequest{
    // 1.獲得請(qǐng)求管理者
    AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
    mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",nil];

    // 2.發(fā)送GET請(qǐng)求
    [mgr GET:@"http://www.weather.com.cn/adat/sk/101110101.html" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"responseObject:%@",responseObject);
        XCTAssertNotNil(responseObject, @"返回出錯(cuò)");
        NOTIFY //繼續(xù)執(zhí)行
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"error:%@",error);
        XCTAssertNil(error, @"請(qǐng)求出錯(cuò)");
        NOTIFY //繼續(xù)執(zhí)行
    }];
    WAIT  //暫停
}
  • 另一種異步測(cè)試實(shí)現(xiàn) 使用GCD信號(hào)量
- (void)downloadImageURLWithString:(NSString *)URLString
{ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSURL *url = [NSURL URLWithString:URLString];
    __unused Photo *photo = [[Photo alloc] initwithURL:url
                             withCompletionBlock:^(UIImage *image, NSError *error) {
                                 if (error) {
                                     XCTFail(@"%@ failed. %@", URLString, error);
                                 }

                                 dispatch_semaphore_signal(semaphore);
                             }];

    dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds);
    if (dispatch_semaphore_wait(semaphore, timeoutTime)) {
        XCTFail(@"%@ timed out", URLString);
    }
}

** 借鑒 開(kāi)源項(xiàng)目**
常用的第三方框架例如YYModel、AFNetworking锐锣、Alamofire等等優(yōu)秀框架中也有對(duì)框架自身編寫的單元測(cè)試腌闯,學(xué)習(xí)仿寫這些單元測(cè)試也是快速提升自己的一種手段。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雕憔,一起剝皮案震驚了整個(gè)濱河市姿骏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橘茉,老刑警劉巖工腋,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姨丈,死亡現(xiàn)場(chǎng)離奇詭異畅卓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蟋恬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門翁潘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人歼争,你說(shuō)我怎么就攤上這事拜马。” “怎么了沐绒?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵俩莽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我乔遮,道長(zhǎng)扮超,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任蹋肮,我火速辦了婚禮出刷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坯辩。我一直安慰自己馁龟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布漆魔。 她就那樣靜靜地躺著坷檩,像睡著了一般却音。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矢炼,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天僧家,我揣著相機(jī)與錄音,去河邊找鬼裸删。 笑死八拱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的涯塔。 我是一名探鬼主播肌稻,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匕荸!你這毒婦竟也來(lái)了爹谭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤榛搔,失蹤者是張志新(化名)和其女友劉穎诺凡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體践惑,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腹泌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尔觉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凉袱。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖侦铜,靈堂內(nèi)的尸體忽然破棺而出专甩,到底是詐尸還是另有隱情,我是刑警寧澤钉稍,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布涤躲,位于F島的核電站,受9級(jí)特大地震影響贡未,放射性物質(zhì)發(fā)生泄漏种樱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一羞秤、第九天 我趴在偏房一處隱蔽的房頂上張望缸托。 院中可真熱鬧,春花似錦瘾蛋、人聲如沸俐镐。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)佩抹。三九已至叼风,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棍苹,已是汗流浹背无宿。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枢里,地道東北人孽鸡。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像栏豺,于是被迫代替她去往敵國(guó)和親彬碱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容