标签:
1,单元测试简介
单元测试又称为模块测试, UnitTesting)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
在github上的一些著名的开源库用到的测试框架如图所示:
目前在iOS领域, 最流行的2个:OCUnit和GHunit
OCUnit –
优点:1)苹果自带的测试框架,与Xcode深度集成,Scheme配置都非常方便;
2)可以享受Apple后续对XCTest升级的福利。
缺点:1)测试结果输出的可读性不好,不容易找到失败的测试
Ghunit –
优点:1)自带GUI,测试结果清晰可见;
2)可以灵活的运行指定的测试;
3)开源项目;
缺点:1)需开发者安装,配置略复杂.
GHunit比之OCUnit最大的优势就是自带GUI,但随着Facebook的xctool的发布,OCUnit就逆袭了,因为xctool帮助OCUnit把运行测试的灵活性和测试结果的可读性这两块短板给补齐了,再加上其和Xcode的集成优势以及通过命令行运行的便捷性,让其成为持续集成平台的Unit测试框架的首选。
2, 单元测试实践
实践1 每个功能类都应提供单元测试,且每一个测试类,只依赖于其要测试的受测类。使用伪造对象可以避免对其他类的依赖。
保证一个测试类只关注一个被测类,当测试不通过时,就能迅速的定位到是谁发生了错误,而不会受到其他类的干扰。
简单的数据类等可以不提供,但是要保证该测试的都要覆盖到。并不存在一种合适的度量指标可以量化地判断某种单元测试方案是否成功。常用的标准(代码 覆盖率和成功执行的测试用例数)都可以在受测软件的质量不变的情况下人为的修改(作假)。当然,在无法确保程序员素质的情况下,作为没有办法的办法,使用 这种标准也是可以的(或者无奈的说,必须的)。单元测试需要程序员自己把关,关注哪些功能确实需要测试覆盖。这也就是前面所说的一些程序员不相信单元测试 可以提高生产率的理由--它更多的依赖于程序员的素质,这是没有保证的。但同样的,由于敏捷是一种以人为本的思想实践,因而这种行为似乎又是一种必然。
实践1.1 使用伪造类避免对其他类的依赖。
避免依赖的一种手段。
例如,某个被测的方法声明是这样的:
-(void)xxxx:(Person*)person;
如果测试时传入Person的话,就造成了测试类依赖于两个类。当由于person中的错误引发测试不通过时,就不能迅速的定位到受测类中是否有问题。遇到这种情况,就可以使用伪造类。假如方法中只使用了person的一个属性name,那么可以将方法名重构为
-(void)xxxx:(id)person;(此处id有待商榷,只是这样做最简单)
然后在单元测试的target中添加只包含name属性的fakePerson来作为伪造类。这样,一旦发生错误就可以迅速的推测出错误的来源。
实践1.2 使用伪造环境避免其他环境的干扰。
适合于异步的方法测试。
很经常遇到的一种情况是测试有网络环境的代码。由于异步的存在,这会造成测试代码不好写。一种简单的解决方法是,我们假定网络一定是通畅的,则我们 测试的代码将分为两部分,即拼接发送功能和接收解析功能。假如发送和接收功能各自都能通过测试,那么我们大约可以确定这个异步方法的正确性。而现在XCTest提供了XCTestExpectation来进行异步测试。另一种方法是 使用GHUnit,它支持异步代码的测试。
实践2 测试用例(方法)名应该是自解释的且是独立的。
如果被测试类的名称是XXX,那么测试类可以命名为XXXTests。而对于其中要测试的功能,命名应该是自解释的。这可以在发现错误时尽快的定位问题所在。例如,如果某个属性obj应该是非空的,那么我们可以将其命名为:
-(void)testObjNotNil{}
每个方法目标应该是单一的,大多数情况下每个方法内都只有一个断言语句;方法不应该依赖于其他方法的结果作为输入,保证原子性。
实践3 断言语句需要解释测试者的意图。
实践4 判断某个意图有没有达到的很好的方法是检测方法影响的数据有没有合理的变化。
由于单元测试是使用断言语句来做判断的,因而最容易做的就是判断数据的变化。这也就限定了单元测试能测试的方法范围,即引起数据变化的方法。对于一些纯展示的方法,例如播放一段特效,这种方法是无法靠单元测试来进行约束的。测试数据的特性包括取值范围(int、float等),排列顺序(NSArray等),类型等等。
实践5 运用重构的手段使方法变得易于被测试。
单元重构的手段使方法变得易于被测试。
什么样的代码是容易进行单元测试的?最简单的一点就是,每个被测方法都应该是功能单一的。当然,这也是代码规范中应该做到的。方法的功能单一,则测试方法的断言也会比较好确定。如果你发现某个方法很难进行测试,则就应该对这个方法进行拆分重构。
实践5.1 面向抽象设计类之间的关系。
类之间通讯如果依赖于抽象(接口),则可以较容易的使用伪造类。参照实践1.1。
实践6 运用自上而下的方式构建类
自上而下的方式可以使类的功能明确,类的构成将会清晰紧凑,不会出现一些废方法。
先确定类需要负担的责任,以此来确定类具有的公有方法以及属性。通过重构将公有方法中的代码转化为私有方法,以使方法尽量短小紧凑。实践6.1 应对所有暴露的属性和方法提供测试,私有方法则不必
若运用自上而下的方式构建类,则理论上私有方法应该都是公有方法重构而得到的。实际上测试公有方法时这些私有方法都应该被测试到了。而且,由于私有方法相对公有方法来说发生变动的可能性很大,会造成不必要的修改测试代码的成本。
回调方法不属于私有方法,也需要进行测试。
由于回调方法一般是异步和不可触发的(按正常流程),例如网络事件的返回和按下按钮的触发事件。因而,测试的时候要直接调用来对其流程进行检测。例如某个按钮的touch upinside事件:
-(void)buttonPressed:(id)sender;
可以根据方法中用到的方法、属性伪造一个FakeButton按钮作为参数传递进行测试。
实践6.3 测试私有的方式,KVC、子类化和类别。
遇到需要通过验证私有数据才能编写的测试时,可以考虑使用KVC和子类化。子类继承于被测类,只包含于单元测试target,其作用就是在不该变受测类的情况下,使受测类具有某些易于被测的能力。
实践7:变化需要新测试的支持。
保证测试的覆盖度。
就像敏捷中提到的“改变需要抽象”一样,在测试中改变需要新的测试。当然,度依然由程序员自己掌控。
3,OCUnit简介
OCUnit利用XCTest进行测试,是苹果自带的测试框架,与Xcode深度集成,可以享受Apple后续对XCTest升级的福利。
为了规范,建议每个测试方法都写成“ -(void)testXXX ”形式,XXX表示要测试的方法名,并且必须是无返回类型。
XCTest仅在OC中使用,同时也适用于swift 。
4,创建单元测试
1)新建工程时,如图,勾选即可
2)或者是项目是之前的,创建时没有按上述方法创建,此时可以自行添加,方法如下图所示:
点击左下角的“+”按钮,如图:
3)创建完之后,测试文件在工程中的位置,如图:
注:在编写tests时,可以根据功能将tests进行划分,如官网图例,将tests分为3类:
5,运行
5.1 在测试时,运行可以command+U运行整个tests,或者可以如图所示:
1,点击框内按钮,运行整个unittesting,
2,点击框内按钮,运行某一分类下的点击框内按钮,运行unittesting
3,点击框内按钮,运行某一分类下的点击框内按钮,运行指定的unittesting
5.2 运行,如图,绿色表示通过
6,测试方法
6.1 默认生成的测试方法有四个:、
1)setUp — 初始化的代码,在测试方法调用之前调用,可以在此编写通用的代码,如初始化一个多次用到的控制器
2)tearDown — 释放测试用例的资源代码,这个方法会每个测试用例执行后调用,同上,可以在此编写通用的代码,如置空一个多次用到的控制器
* setUp是初始化的地方,tearDown是结束清理的地方,而且它们在每个用例方法执行时都会重新执行--这保证了测试用例的原子性。
3)testPerformanceExample — 测试性能例子
measureBlock — 需要测试性能的代码
Baseline即可设置该性能是否合格的参考基准。
注意,一个性能测试case中只能执行一次measureBlock,即只能测试一个指定block步骤的性能(若混到一起就不能区分出性能测试的结果)。因此在while、for等循环中是不能使用measureBlock的,否则会出错。
4)testExample — 测试用例的例子,注意测试用例一定要test开头
若测试时生成的值与预期的值不同,则会通过断言来显示,如:
XCTAssert(expression, format...)当expression求值为TRUE时通过;
若为0,则会在控制台打印如下信息:
6.3 简单示范
? 测试方法返回值是否符合预期猜测
? 例:测试的getNum方法返回值为100,测试代码如图:
? 1)测试成功,如图
? 2)测试失败,如图
? 控制台输出,如图
7,常用的断言测试函数
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语言标量、结构体或联合体时使用, 判断的是变量的地址,如果地址相同则返回TRUE,否则返回NO);
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没有发生具体异常、具体异常名称的异常时通过测试,反之不通过
8,参考链接
2,http://www.uml.org.cn/Test/201408131.asp
3,http://www.jianshu.com/p/009844a0b9ed
4,http://blog.csdn.net/sqc3375177/article/details/40857313
5,http://www.cocoachina.com/industry/20140805/9314.html
标签:
原文地址:http://blog.csdn.net/sophia_xiaoma/article/details/51276117