iOS中的自动化测试

2015-12-15 | 阅读

研究iOS中的单元测试

当前流行的Uinit Test 框架有两个 XCTestGHunit,前者由苹果将其与Xcode集成,Scheme配置非常方便,但是不能灵活运行测试集,且输出可读性不好,不容易找到失败的测试,而其前身是OCUnit已被弃用.后者自带GUI且测试结果清晰可见,是一个开源项目,但需要开发者安装配置,且无法方便的进行命令行运行,所以持续化集成CI的支持不够好.

所以从 持续集成平台上的Unit测试框架来说,肯定是使用XCTest结合Facebook的xctool来使用.

扩展方面,使用OCHamcrest中的断言,使用OCMock来测试第三方API.

对于其他层次的测试,UI测试,可以使用UIAutomation,要通过Instruments来进行访问.

单元测试,使用XCTest与XCTool

setUp:测试方法调用之前调用,用来初始化一些资源和对象

tearDown: 测试方法全部结束后调用,用来清理现场,删除对象释放资源.

testXXXX: 真正的测试方法,命名是不带参数,以test开始,所有的testXXX方法都是在Scheme里看到,所以也可以在Scheme中设置是否测试这个方法.

XCTest中的常用断言有:

  1. XCTFail(format…) 生成一个失败的测试,该测试必定失败,format与之后的参数组成的格式化字符串为当前失败用例的输出说明.

  2. XCTAssertNil(a1, format...)为空判断,a1为空时通过,反之不通过;

  3. XCTAssertNotNil(a1, format…)不为空判断,a1不为空时通过,反之不通过;

  4. XCTAssert(expression, format...)当expression求值为TRUE时通过;

  5. XCTAssertTrue(expression, format...)当expression求值为TRUE时通过;

  6. XCTAssertFalse(expression, format...)当expression求值为False时通过;

  7. XCTAssertEqualObjects(a1, a2, format...)判断相等,[a1 isEqual:a2]值为TRUE时通过,其中一个不为空时,不通过;

  8. XCTAssertNotEqualObjects(a1, a2, format...)判断不等,[a1 isEqual:a2]值为False时通过;

  9. XCTAssertEqual(a1, a2, format...)判断相等,实际值相等,相当于直接判断两个对象是否 == ,如果是对象或者id,就会直接拿指针进行比较,而不是用指针所指向的对象进行比较.

  10. XCTAssertNotEqual(a1, a2, format...)判断不等;

  11. XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)判断相等,(double或float类型)提供一个误差范围,当在误差范围(+/-accuracy)以内相等时通过测试;

  12. XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判断不等,(double或float类型)提供一个误差范围,当在误差范围以内不等时通过测试;

  13. XCTAssertThrows(expression, format...)异常测试,当expression发生异常时通过;反之不通过,expression为需要测试的表达式;

  14. XCTAssertThrowsSpecific(expression, specificException, format...)异常测试,当expression发生specificException异常时通过;反之发生其他异常或不发生异常均不通过;

  15. XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)异常测试,当expression发生具体异常、具体异常名称的异常时通过测试,反之不通过;

  16. XCTAssertNoThrow(expression, format…)异常测试,当expression没有发生异常时通过测试;

  17. XCTAssertNoThrowSpecific(expression, specificException, format...)异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过;

  18. XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过

异步测试:

// 注册一个XCTestExpectation
XCTestExpectation *expectation =  [self expectationWithDescription:@"test Expectations"];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    [expectation fulfill];// 在合适的地方结束异步测试.
}];
// 启动异步检测并设置好超时时间.
[self waitForExpectationsWithTimeout:10 handler:nil];

XCtool

使用homebrew下载Xctool,然后执行以下命令来执行Test:

xctool -project MUBase.xcodeproj -scheme MUBaseTests test

XCtool的参数:

xctool -project MUBase.xcodeproj -scheme MUBaseTests test -test-sdk iphonesimulator

通过-test-sdk来指定其使用模拟器模式来打包framework.

通过run-tests来运行已经编译好的文件,而表示重新打包.

通过-only标签来指定测试指定test任务:

path/to/xctool.sh \
  -workspace YourWorkspace.xcworkspace \
  -scheme YourScheme \
  run-tests -only SomeTestTarget:SomeTestClassPrefix*,SomeTestClass/testSomeMethodPrefix*

OCMock

单元测试中,很多重要的不容易构造或者不容易获取的对象,所有有Mock这种工具,来创造一个虚拟的对象用于测试,让测试专注于主要测试对象的逻辑,而模拟其他不重要的对象.OCMock是iOS中常用的Mock工具.

使用GHUnit进行可视化单元测试

GHUnit是一个开源的可视化单元测试框架,地址

使用方法:

在项目中添加Target,添加Single View Application,对于新建的项目,添加GHUnit的framework文件,然后删除AppDelegate文件,然后修改main文件,以使其指向GHUnit中的GHUnitIOSAppDelegate:

#import <GHUnitIOS/GHUnitIOSAppDelegate.h>

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([GHUnitIOSAppDelegate class]));
    }
}

GHUnit的使用方法大致与XCtest相同,但是类继承与GHTestCase,在setUp进行初始化,在tearDown释放资源,以testXXX命名一次测试内容,而断言写法也与XCtest相似,只是前缀改为GH而已.

在GHUnit中测试异步操作:

首先,这个test要继承于GHAsyncTestCase,添加一个test,在函数开始处调用[self prepare]来准备异步测试,在末尾调用[self waitForStatus:kGHUnitWaitStatusSuccess timeout:100.0];来设置超时时间并开始异步测试任务.在异步任务执行过程中,通过[self notify:kGHUnitWaitStatusFailure forSelector:@selector(testDownloader)];来告知该测试最终结果.大致调用过程如下:

- (void)testDownloader {
    // 准备调用异步测试
    [self prepare];
    
    NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentPaths = [docPaths objectAtIndex:0];
    NSString  *filePath = [documentPaths stringByAppendingPathComponent:@"/123.apk"];
    MUBaseDownloadTask* task = [[MUBaseDownloadTask alloc] initWithUrl:@"http://10.75.201.120/download/FenQiBao_sit.apk" FilePath:filePath];
    task.delegate = self;
    [task start];
    // 开始异步测试,并设置超时时间.
    [self waitForStatus:kGHUnitWaitStatusSuccess timeout:100.0];
    
}

- (void)onError:(NSError*) error{
    NSLog(@"下载发生异常 : %@ ",error);
    // 返回 失败状态
    [self notify:kGHUnitWaitStatusFailure forSelector:@selector(testDownloader)];
}

- (void)onSuccess{
    NSLog(@"下载任务完成 ");
    // 返回成功状态
    [self notify:kGHUnitWaitStatusSuccess forSelector:@selector(testDownloader)];
}