ios 简单的单元测试

原文  简书

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

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

有了单元测试以后,我们就没必要为了测试某个小模块去编译我们的程序,然后去等待模拟器启动然后到你需要验证的模块去。这样做也是可以的啦!可是,你的项目很大,编译等老半天,你的电脑没那么快,那就够你等的了。扯淡这么半天,就是为了说明单元测试能节约我们的时间,提高开发效率,对于项目越大的效果越明显。

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个,只需要记住常用的即可, 其他的可以根据这个改变条件推导出来, 全部内容如下:

  • XCTAssert(expression, format...) //当expression求值为TRUE时通过; 这个最最常用
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,地址点击这里

不要被demo带歪了,xctest 中的assert 其实没什么用 ,  单元测试可以做很多事情, 比如 你在开发一个功能,一次写了一点,想验证下写的行不行,不要直接运行,把那部分复制到单元测试里,然后进行验证,可以省很多时间,当然你需要假设一些需要的数据. 比如 调调接口通不通, 写的逻辑判断有没有问题 .

 在进一步 ,不要写在XCTest,给NSObject写个类别,加一个 类方法 +(void)test ; 在XCTest 中简单的调用下 [MyClass test]; 然后在MyClass中重写+(void)test方法  ,这样 .这个类里的 私有变量,私有方法 都可以随意调用了 .

2者结合下,以后写东西都在+(void)test 中写, 验证通过了在复制到你需要的地方. 减少很多运行时的等待时间.

当然也不要被我带偏了, 发挥你的想象力, 会有更多更好的方法等待被挖掘. 

总的来说, XCTest 中 test 会调用 applicationDidFinishLaunching , 所以 整个app的环境是有的, 比你新建一个工程做一些验证性的东西快的多 . 

最后 , 记录下工程中常用的2个宏.  NSAssert , NSParameterAssert .

NSAssert()是这样定义的:
#define NSAssert(condition, desc)

condition是条件表达式,值为YES或NO;desc为异常描述,通常为NSString。当conditon为YES时程序继续运行,为NO时,则抛出带有desc描述的异常信息。NSAssert()可以出现在程序的任何一个位置。

NSAssert 可以很舒服的实现这样的效果 , 

// 旧写法 
   if ([viewController isKindOfClass: [UIViewController class]] == NO) {
        NSLog(@"传入的vcName不合法");
        return ;
    }

// 用NSAssert
    NSAssert([viewController isKindOfClass: [UIViewController class]], @"传入的vcName不合法");

NSParameterAssert的定义 , 就是NSAssert简写 , 

#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)

    // 旧的写法,判空需要4行
    if (url == nil) {
        NSLog(@"url 不能为空");
        return ;
    }
    
    // 简单写法,
    NSParameterAssert(url);

再说一点,  Xcode 已经默认将release环境下的断言取消了, 免除了忘记关闭断言造成的程序crash. 所以不用担心 在开发时候大胆使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值