编辑: 我不是阿L | 2019-05-05 |
6 你应该知道的单元测试 如何比较好的来编写一个测试用例,对此,有很多不同的做法,而这也并没有一个标准,也不需要有一个标准.我们需要清 楚一个测试用例存在的意义是什么,它是为了验证某个类的某个行为在某种上下文中能得到预期的结果,如果你的测试用例 达到了这样的目的,那么如何写也都不算错.不过,为了能够统一单元测试的规范(这点在多人协同开发下非常重要),我 们常常会把一个测试用例分为三个阶段:排列资源、执行行为、断言结果,一般我会习惯用 Arrange 、 Act 、 Assert 来表 示,也会有用 Given , When , Then 来表示的,但意思都相同. 排列资源,便是提供一切测试方法所需要的东西,而这些东西便称之为资源.这些资源包括: 1. 方法的输入参数 2. 方法所执行的特定上下文 这个阶段相当于准备阶段,一切都是为了这个用例中执行行为而作准备,如果没有任何需要准备的数据,这个阶段是可以被 忽略的. 这里,我们以测试 NSMutableDictionary 的 setObject:forKey: 为示例,那么在排列资源阶段,我们的代码如下: - (void)test_setObject$forKey { // arrange NSString *key = @ test_key ;
NSString *value = @ test_value ;
NSMutableDictionary *dic = [NSMutableDictionary new];
} 关于测试用例的命名,我比较推崇这样的写法: test_测试方法签名_测试上下文 由于Objective-C的方法签名比较奇怪,为了可读性,我建议使用 $ 进行分割,比如这个示例中的 test_setObject$forKey ,或 者附带上下文的 test_setObject$forKey_when_key_is_nil . 当准备阶段完毕后,便进入要测试行为的执行阶段,在这个阶段,我们会使用准备好的资源,并记录下行为的输出以供下个 阶段使用.这里的行为输出不一定就是方法执行的返回值,很多时候我们要测试的方法并没有任何返回值,但一个方法执行 后,总归会有一个预期的行为会发生,即便是空方法也是(什么都不会被改变),而这个预期行为便是测试行为的输出. 加入执行行为的代码: - (void)test_setObject$forKey { // arrange NSString *key = @ test_key ;
NSString *value = @ test_value ;
NSMutableDictionary *dic = [NSMutableDictionary new];
// act [dic setObject:value forKey:key];
} 最后一步,也是最核心的一步,它决定着一个测试用例的成功与否,我们需要在这一步断言执行行为的输出是否达到预期. 确定一个行为的输出,我们可能需要有多次断言,这里需要遵循一个原则:先执行的断言,不应该以后执行的断言成功为前 排列资源 执行行为 断言结果 iOS程序员
7 你应该知道的单元测试 提.以上原则很重要,这对快速排除Bug会很有帮助.现在,我们来看下针对 NSMutableDictionary 的这个完整测试用例: - (void)test_setObject$forKey { // arrange NSString *key = @ test_key ;
NSString *value = @ test_value ;
NSMutableDictionary *dic = [NSMutableDictionary new];
// act [dic setObject:value forKey:key];
// assert XCTAssertNotNil([dic objectForKey:key]);
XCTAssertEqual([dic objectForKey:key], value);
} 可以看到,最后我们是先断言是否为空,再断言是否相等,后者是在前者成功的前提下才可能不失败.如果颠倒顺序,就很 难尽早的发现错误原因,我们应该下意识的将这种断言的依赖关系排列正确,就像我们在很多语言里使用 try...catch 时,我 们会排列好异常捕获的顺序. 不知道大家有没有认真想过,这种测试为什么要叫Unit Test?顾名思义,是针对Unit来进行测试,也就是针对基本单元进行 测试.所以,要做到真正的单元测试,你需要保证你每个测试用例所针对的仅仅是一个基本单元,而不是一个有很多复杂依 赖的综合行为. 在面向对象的程序设计中,一般最基本的单元就是一个类的方法,所以在单元测试中,我们要面对的就是针对这些方法编写 合适的测试用例.方法就是一个类的对外行为,针对方法的测试也可以看作是针对一个类的行为测试,在编写测试用例时, 我们不应该考虑一个行为的中间产出,我们应该将关注点放在最终的执行结果上. 关于行为测试,目前已有一套相关的理论和相应的测试框架,可以参考objc.io上的这篇文章. 前面也提到了,我们需要的是针对一个基本单元进行测试,这样的要求会促使我们改善设计.我们应该尽可能让类方法的职 责单一,这会方便我们撰写测试用例.理想中,每个类都是独立的,但现实里,一个类很少会没有依赖关系,而在编写测试 用例时,我们不应该将依赖的类行为纳入到该类的测试用例中,被依赖的类应该是经过了单独测试,我们需要假定它是完全 合理正确的. 为了能够不受依赖类的实现影响,我们可以将依赖的行为抽象成接口,依赖类去实现这样一个接........