聊聊iOS开发中的单元测试

看到文章标题的时候,你也许会问,测试不是测试妹子干的事吗?的确,测试妹子能帮助我们测试出软件的很多问题(不符合业务的问题),但是代码的测试还得靠我们自己啊。团队Leader在开会时一直强调要打造一支不依靠测试团队的团队,因此,代码自测也变成了一个项目重要的一环。是的,今天我要聊的就是我们程序员对自己代码的测试,而不是测试妹子的测试。在iOS开发中我们用单元测试来保证我们的代码可靠性,什么是单元测试,请看在维基百科上的解释:

在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块的最小单位来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。 -- 维基百科

有了单元测试以后,我们就没必要为了测试某个小模块去编译我们的程序,然后去等待模拟器启动然后到你需要验证的模块去。这样做也是可以的啦!可是,你的项目很大,编译等老半天,你的电脑没那么快,那就够你等的了。我还记得我工作的第一个公司,给我用的是一个性能堪忧的Mac mini,每次修改代码后Command + R后都得等上一分多,简直不能忍受啊!是的,我没能忍受,没过多久我就离职了。扯淡这么半天,就是为了说明单元测试能节约我们的时间,提高开发效率,对于项目越大的效果越明显。

XCTest

XCode4.x时代Xcode集成的是OCUnit,到了XCode5.x时代就升级为了XCTest,并且到了XCode7时代还有了进行UI测试的能力。除了官方自带的,还有一些比较出名的第三方的测试框架,如:GHUnit,KiWiOCMock,Specta等,当然本文不讨论这些第三方框架。怎么知道我们的项目有没有加上单元测试,用Xcode打开你的项目,看文件导航栏有没有类似下图的两个文件夹(TestDemo是工程名)。


其实在我们新建工程的时候就可以为我们的工程选择是否带上单元测试,如下图:

如果你的项目没有上面说的两个文件,你可以通过新建一个Target的方式添加,如下图:


在test下选择你项目没有的便可:

在这2个文件夹目录下分别都有2个文件,一个.m文件和一个plist文件。并且.m文件有4个方法,如下图:

项目名+Test.m文件里面默认有4个方法,这个文件里面主要做一些逻辑的测试。项目名+UITest.m文件里默认有3个方法。这个文件里面主要做一些UI的测试。说了这么半天,该如何写单元测试呢?在讲解如何写测试方法前,先说说默认的方法是干什么的吧!

//TestDemoTest.m
- (void)setUp {
    [super setUp];
    //每个test方法执行前调用,在这个测试用例里进行一些通用的初始化工作
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
    //每个test方法执行后调用
}

- (void)testExample {
    //测试方法样例
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
}

- (void)testPerformanceExample {
    //这个方法主要是做性能测试的,所谓性能测试,主要就是评估一段代码的运行时间。该方法就是性能测试方法的样例。
    // This is an example of a performance test case.
    [self measureBlock:^{
        // Put the code you want to measure the time of here.
    }];
}

测试用例方法非常简单,从testExample这个方法我们大概知道怎么写了吧!方法名只需要以test开头,是的,就是这么简单。现在我们模拟登录这个功能来写一个登录模块的测试用例吧,Demo代码在GitHub,在User这个模型类里面一个方法叫isChinese的,是用来判断字符串里面是否有中文的。

#import <Foundation/Foundation.h>

@interface User : NSObject

@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *passWord;

/**
 *  判断字符串中是否有中文
 */
- (BOOL)isChinese:(NSString *)string;

@end

现在我们通过Xcode的File->New->File->Source选择Unit Test Case Class来新建一个UserTests,注意要继承XCTestCase类。

接下来我们为User类写一个测试isChinese方法的测试方法,叫做testIsChinese,测试用例具体如下:

UserTests.m

这样,你只要点击测试方法旁边的那个菱形的按钮就可以运行该测试方法啦!通过测试会变成绿色的对勾,失败会变成红色的叉叉。到这里测试用例你就会写了。也许你会在意那些断言,这样的断言有18个,如下:

XCTFail(format…)  //生成一个失败的测试;
XCTAssertNil(a1, format...)  //为空判断,a1为空时通过,反之不通过;
XCTAssertNotNil(a1, format…) //不为空判断,a1不为空时通过,反之不通过;
XCTAssert(expression, format...) //当expression求值为TRUE时通过;
XCTAssertTrue(expression, format...) //当expression求值为TRUE时通过;
XCTAssertFalse(expression, format...)  //当expression求值为False时通过;
XCTAssertEqualObjects(a1, a2, format...)  //判断相等,[a1 isEqual:a2]值为TRUE时通过,其中一个不为空时,不通过;
XCTAssertNotEqualObjects(a1, a2, format...)  //判断不等,[a1 isEqual:a2]值为False时通过;
XCTAssertEqual(a1, a2, format...)  //判断相等(当a1和a2是 C语言标量、结构体或联合体时使用,实际测试发现NSString也可以);
XCTAssertNotEqual(a1, a2, format...)  //判断不等(当a1和a2是 C语言标量、结构体或联合体时使用);
XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)  //判断相等,(double或float类型)提供一个误差范围,当在误差范围(+/-accuracy)以内相等时通过测试;
XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...)   //判断不等,(double或float类型)提供一个误差范围,当在误差范围以内不等时通过测试;
XCTAssertThrows(expression, format...)  //异常测试,当expression发生异常时通过;反之不通过;
XCTAssertThrowsSpecific(expression, specificException, format...)  //异常测试,当expression发生specificException异常时通过;反之发生其他异常或不发生异常均不通过;
XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)  //异常测试,当expression发生具体异常、具体异常名称的异常时通过测试,反之不通过;
XCTAssertNoThrow(expression, format…)  //异常测试,当expression没有发生异常时通过测试;
XCTAssertNoThrowSpecific(expression, specificException, format...)  //异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过;
XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)  //异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过

UI测试

用代码写UI测试比较麻烦,但是苹果在Xcode中为我们提供了录制的功能。录制是怎么一回事呢?当你打开时这个功能时,测试代码会随着你在设备或模拟器上操作自动创建。这么一来就省事多了。现在,我们在TestDemoUITests.m文件中写一个方法testLogin作为测试登录流程操作的UI测试方法。然后把光标放在方法体内,然后点击红色的那个录制按钮,如下:


当你点击了录制后,程序就会自动启动,这时候你在程序的所有操作都会生成想用的代码在你所选择的方法体内。我录制了一个GIF,你可以看一下,非常的好用:


接下来我们看看里面的代码:

//XCUIApplication 这是应用的代理,他能够把你的应用启动起来,并且每次都在一个新进程中。
XCUIApplication *app = [[XCUIApplication alloc] init];
//XCUIElement 这是 UI 元素的代理。元素都有类型和唯一标识。可以结合使用来找到元素在哪里,如当前界面上的一个输入框
XCUIElement *usernameTextField = app.textFields[@"username:"];
[usernameTextField tap];
[usernameTextField typeText:@"xiaofei"];
    
XCUIElement *passwordTextField = app.textFields[@"password:"];
[passwordTextField tap];
[passwordTextField tap];
[passwordTextField typeText:@"12345"];
[[[[[[[app childrenMatchingType:XCUIElementTypeWindow] elementBoundByIndex:0] childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element tap];
[app.buttons[@"login"] tap];

有了这些代码,我们就可以对它进行一些处理了,比如:

//XCUIApplication 这是应用的代理,他能够把你的应用启动起来,并且每次都在一个新进程中。
XCUIApplication *app = [[XCUIApplication alloc] init];
//XCUIElement 这是 UI 元素的代理。元素都有类型和唯一标识。可以结合使用来找到元素在哪里,如当前界面上的一个输入框
XCUIElement *usernameTextField = app.textFields[@"username:"];
[usernameTextField tap];
[usernameTextField typeText:@"xiaofei"];
    
XCUIElement *passwordTextField = app.textFields[@"password:"];
[passwordTextField tap];
[passwordTextField tap];
[passwordTextField typeText:@"12345"];
[[[[[[[app childrenMatchingType:XCUIElementTypeWindow] elementBoundByIndex:0] childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element tap];
[app.buttons[@"login"] tap];
//登录成功后的控制器的title为loginSuccess,只需判断控制器的title时候一样便可判断登录是否成功
XCTAssertEqualObjects(app.navigationBars.element.identifier, @"loginSuccess");

如果你想一次跑完所有的测试方法,快捷键cmd+u即可。跑起来后的面板主要如下图所示:

总结

这只是苹果官方集成在Xcode中的简单框架,优点就是简单,缺点也是简单。当然它的用法也绝非如此,有很多还待开发。苹果官方也有一个Demo,地址点击这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 1.let:声明变量,有块级作用域,不允许在同一作用域内重复声明,在不同作用域内可以重复声明,let不像var那样...
    几岁老人阅读 316评论 0 0
  • 自你走后,我便不再来这里写东西了,算下来已有一月有余,但我确实需要一个地方写些文字去发泄,勉强维持内心的秩序。...
    日记还是流水账阅读 408评论 1 1
  • # jsonp原理讲解 ## 原因 > 我们知道浏览器出于安全考虑,并不容许利用(XMLHTTP/XMLHttp...
    天起晴阅读 183评论 0 1