聊聊iOS开发中的单元测试

作者: FITZ9311 | 来源:发表于2016-03-21 21:30 被阅读13995次

    看到文章标题的时候,你也许会问,测试不是测试妹子干的事吗?的确,测试妹子能帮助我们测试出软件的很多问题(不符合业务的问题),但是代码的测试还得靠我们自己啊。团队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,地址点击这里

    相关文章

      网友评论

      • F麦子:这个UI测试有什么用?怎么感觉用处不大
        本帅不良:记录你的操作啊!当需要反复进行复杂操作的时候就很有用啊!比如你需要填写一些身份信息,然后提交,每次都手动输入是不是很麻烦!
      • 小石头JS:大牛你好,小白刚接触这个有好多疑问,想问一下,被测试的类目与单元测试如何关联的
        本帅不良:在swift下有个@testable import 引用,主要作用是让你能够在测试文件中找到被测试文件的索引。oc中原理相同,能够找到索引之后,直接调用需要测试的方法就可以了。
      • 5c0783a08038:UI测试 Xcode 上的那个录制视频的红点 是怎么出来的
        我是卖报滴小行家:先选中项目 运行一下 再到UITest 就ok了
      • Link913:大哥,请教一个问题,我录了一个脚本
        [app.otherElements[@"\\U6211\\U7684"] tap];
        这中间其实只有一个"\",我因为编译不通过特别转义了一下,但是运行的时候告诉我用户界面测试失败,没有找到匹配,这个问题该怎么去解决啊
        东岳哥哥:@SkyHarute 那你看一下我写的文章吧,http://www.jianshu.com/p/560e397efdc7,希望对你有帮助
        Link913:@雨轩_99 一个一个改会累死人的,一个触发改一个汉字改到明年去了
        东岳哥哥:把它改成对应的中文就好了
      • Reiko喵:请问在username的textfield切换到password的textfield的时候xcode会报这个错误“Timestamped Event Matching Error: Failed to find matching element”是什么意思
        我的大名叫小爱:@小奇boc 不是中文引起的 ...这个录制功能好像确实不够强大...
        小奇boc:因为输入框的placehoder是汉字
        妖妖零幺幺:@Reiko喵 我也遇到了
      • RealTrump:请问Xcode的背景色怎么调节?
        特拉法尔咖:- - 楼主你是自己手动调的么?没有快捷方式么?
        RealTrump:@张飞_ tks
        FITZ9311:@夜空中最亮的心 Xcode-->Perfenerces-->Fonts & Colors
      • civicyang0:请问性能测试的评估时间 是代表什么意思? 比如我循环打印1000条信息,明显用了10多秒才打印完,控制台也输出也显示耗时10多秒, 但右边的评估时间是1.几秒.是怎么回事呢?
        FITZ9311:@civicyang0 注意看控制台的打印信息,评估的那个是平均值,average那个

      本文标题:聊聊iOS开发中的单元测试

      本文链接:https://www.haomeiwen.com/subject/yzjflttx.html