以下内容属于个人见解,如有不恰当欢迎指正!

什么叫单元测试

对于面向对象来说,最基本的单元是类,对类的测试或者是对类中的方法测试,认为是 单元测试 。类中的方法特指 public 方法或者类方法,私有方法不做测试。
tip:若私有方法需要测试,那么私有方法一定含有概念性错误。

XCTest框架

1
2
3
- (void) setUP;     //在测试前设置好要测试的方法
- (void) tearDown; //在测试后将设置好的要测试的方法拆卸掉或销毁
- (void) testXXXX; //测试方法

其中测试方法的编写时有讲究的:

  • 测试方法名必须是以test开头
  • 测试方法没有返回值(void)
  • 测试方法的调用顺序就是方法名字典序 eg:testA,testB。testA调用先于testB

XCTest中的几种断言,加入断言机制是为了使测试更加明确,我们预计程序错误或崩溃的条件,防止程序在运行到该位置时崩溃。

  • XCTFail(format…) 生成一个失败的测试;
  • XCTAssertNil(a1, format…)为空判断,a1为空时通过,反之不通过;
  • XCTAssert(expression, format…)当expression求值为TRUE时通过;

expression 是在测试的时候确定的一个布尔表达式

测试demo

  • 异步操作测试

具体通过XCTestExpection类和waitForExpectationsWithTimeout:handler:方法来实现对异步操作的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(void)testWebPageDownload
{
XCTestExpectation *expectation =
[self expectationWithDescription:@"High Expectations"];
[self.pageLoader requestUrl:@"acumen1005.github.io"
completionHandler:^(NSString *page) {

//请求回调处理
[expectation fulfill];
}];
//设置预计的超时时间
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}

其中框架会预计XCTestExpection在之后的某一时刻被实现。最终的程序完成代码块中的测试代码会调用XCTestExpection类中的fulfill方法来实现期望
若是请求时间超过时限,则会调用其error中的代码块,以至于无限制的等待一个无效的请求

辅助测试的第三方框架 OCMock

从名字的来看 OCMock 是仿制,伪造的意思,他的功能的确是伪造一个类,来解决测试过程中一些类难以实例化的问题,直接可以通过 mock 来伪造生成,降低系统业务和测试之间耦合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

/* dynamically create a subclass and use its meta class as the meta class
for the mocked class */
Class subclass = OCMCreateSubclass(mockedClass, mockedClass);
originalMetaClass = object_getClass(mockedClass);
id newMetaClass = object_getClass(subclass);

/* create a dummy initialize method */
Method myDummyInitializeMethod = class_getInstanceMethod([self mockObjectClass],
@selector(initializeForClassObject));
const char *initializeTypes =
method_getTypeEncoding(myDummyInitializeMethod);
IMP myDummyInitializeIMP =
method_getImplementation(myDummyInitializeMethod);
class_addMethod(newMetaClass, @selector(initialize),
myDummyInitializeIMP, initializeTypes);

现在看Mock的源码比较吃力,大概的意思就是 动态创建一个类用它的元类(meta-class)作为mockedClass的元类,同时并创建改类的初始化方法。

OCMock中几个主要方法

  • 任意类型的 mock
1
id mockA = [OCMock mockForClass [A class]];

这种方式创建的mock对象,如果调用未stub的方法,会抛出一个异常。
这需要保证在mock的生命周期中每一个独立调用的方法都是被存根的。

  • Nice Mock
1
id niceMockA = [OCMock niceMockForClass[A class]];

如果你不想自己对很多的方法进行存根,那么使用nice mock。Nice mock是比较友好的,当一个没有存根的方法被调用时他不会引起一个异常

  • Partial Mock
1
id aMock = [OCMockObject partialMockForObject: [UIApplicationsharedApplication]];

当调用一个没有被存根的方法时,会调用实际对象的该方法。当不能很好的存根一个类的方法时,该技术是非常有用的。另外,对于单例类的mock也需要使用partialMock。

  • Stub与Expect的使用

场景:假如我们的待测代码中有一个Car类,Car类中有一个方法为goFaster:units: ,我们的待测函数A需要传入一个Car对象,而A内部一些条件依赖于goFaster:units:的返回值。
stubbed methods(存根方法)

1
2
3
4
id mockCar = [OCMockmockForClass[Car class]];
[[[mockCar stub]andReturn:@"75kph"] goFaster:[OCMArg any] units:@"kph"];
//这样当我们这样调用时:
[mockCar goFaster:84units:@”kph”] //该方法就会返回我们指定的值了(andReturn:期望返回的值)。

OCHamcrest

OCHamcrest 用于各种匹配,文本匹配,逻辑等等。他的github说的很清楚啦。

总结

测试其实很重要,老外对这种要求应该比较高,记得看过一篇blog中间一个项目的单元测试代码量比项目代码量还多,在很多人看来这是得不偿失的。作为一个小白,只能现在慢慢重视起来,特别在越大的项目中,单元测试或者时说测试就越重要,不做测试的结果就是项目做到后期bug堆积起来容易失控,这是很可怕的。就bb到这吧,欢迎到我的github上一起交流技术.

参考

糟糕的测试
OCMock
Testing_with_Xcode文档