iOS测试
第一章 iOS测试
在编写业务代码的同时,也要编写和维护相应的测试代码。因为单元测试不仅能保证代码运行的正确性,也有助于代码结构的安排和思考,有助于自身的不断提高。
对于持续集成平台来说,测试还是非常重要的。项目中能采用自动化测试越多,平台的价值就会越大。持续集成最大的好处在于能够尽早发现问题,降低解决问题的成本。而发现问题的手段主要就在于测试。
比如输出必须在点击一系列按钮之后才能在屏幕上显示出来的东西,我们可以在代码中构建出一个类似的场景,然后在代码中调用我们之前想检查的代码,并将运行的结果与我们的设想结果在程序中进行比较,如果一致,则说明了我们的代码没有问题,是按照预期工作的。
我们做出某些条件和假设,并以其为条件使用到被测试代码中,并比较预期的结果和实际运行的结果是否相等,这就是软件开发中测试的基本方式。
测试的最低层是单元测试,即使用Xcode6.0以后的XCTest或者Kiwi等,然后是业务逻辑测试,再然后是系统测试。
Xcode的Instruments是构建Xcode持续集成不可缺少的部分。instruments中包含了很多的模版,例如leaks,alloction,automation等等,可以动态分析和跟踪内存、CPU和文件系统。
第二章 单元测试
1.概述
单元测试作为敏捷开发实践的组成之一,其目的是提高软件开发的效率,维持代码的健康性。其目标是证明软件能够正常运行,而不是发现bug(发现bug这一目的与开发成本是正相关的,虽然发现bug是保证软件质量的一种手段,但是很显然这与降低软件开发成本这一目的背道而驰)。它是对软件质量的一种保证,例如重构之后我们需要保证软件产品的正常运行。编写单元测试没有用是认为单元测试并不能保证一定能减少bug发生的几率,而由于编写单元测试一定会花费一定的时间与精力,因而必然的会增加成本。
2.测试实践
1.每个功能类都应提供单元测试,且每一个测试类,只依赖于其要测试的受测类。使用伪造对象进行测试,避免对其他类造成影响。
eg:
保证一个测试类只关注一个被测类,当测试不通过时,就能迅速的定位到是谁发生了错误,而不会受到其他类的干扰。
2.测试用例(方法)名应该是自解释的且是独立的。
3.断言语句需要解释测试者的意图。
4. 运用重构的手段使方法变得易于被测试。
5.变化需要新测试的支持。
3.测试案例
1.异步测试
-(void)testHttpRequest{
// 创建期望(相当于一个NSRunLoop,因为我们不知道异步什么时候执行结束)
XCTestExpectation *expectation = [self expectationWithDescription:@"Handler called"];
myClass = [[MyClass alloc]init];
[myClass postWithUrl:@"http://baidu.com" callBackResult:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// expection调用已完成
[expectation fulfill];
XCTAssertEqualObjects(response, @"bar");
//判断登录的时候在这里解析数据,使用断言判断登录是否成功
}];
//运行循环在处理事件,直到所有的期望都满足或超时
[self waitForExpectationsWithTimeout:5 handler:nil];
}
2.XCTest性能测试
XCTest中有 measureBlock:方法进行对算法或者某一部分的性能进行测试,如下:
-(void)testPerformance{
// 测试代码性能的一个block(如测试算法,读取内存等等)
[self measureBlock:^{
// set the initial state
[self.testVC pressView:[self.testVC.view viewWithTag:1000]];
// iterate for 100000 cycles of adding 2
for (int i=0; i<100000; i++) {
[self.testVC pressView:[self.testVC.view viewWithTag:1000]];
}
}];
}
3. 测试application创建了testVC的实例
-(void)testAppliation{
self.testVC = (ViewController*)[[UIApplication sharedApplication] delegate].window.rootViewController;
UIView *testView = self.testVC.view;
// 判断不为空的断言
XCTAssertNotNil(testView, @"Cannot find CalcView instance");
}
第三章 行为驱动测试(TDD)
1.概述
测试驱动开发的好处,它以需求为引领,通过测试的形式,来指导开发者进行软件的设计与架构,并编写出最为精炼的代码,使得测试用例运行通过。经过适当的重构之后,测试用例与产品代码可达到较为健康的状态。
它旨在解决具体问题,帮助开发人员确定应该测试些什么。此外,它鼓励开发者弄清楚他们的需求,并且它引入了一个通用语言帮助你轻易理解测试的目的。
驱动测试主要是对行为测试。即如设计的 app 中的一个对象。它有一个接口定义了其方法和依赖关系。这些方法和依赖,声明了对象的约定。它们定义了如何与应用的其他部分交互,以及它的功能是什么。
2.使用
1.配置Kiwi框架
使用cocoapods导入Kiwi框架(Kiwi导入到testtarget下) eg:
target :"TestXcodeTestTests",:exclusive => true do
platform:ios,‘7.0‘
pod ‘Kiwi‘, ‘~> 2.3.1‘
end
2.创建一个空的类,继承XctestCase。删除所有代码后导入Kiwi框架
3.具体测试使用代码如下:
// describe 描述测试对象的内容(一般一个测试文件只描述一个类)
describe(@"SimpleString", ^{
// context描述测试的上下文
context(@"when assigned to ‘Hello world‘", ^{
NSString *greeting = @"Hello world";
// it描述测试的本体
it(@"should exist", ^{
// 这里封装了类似Xctest中断言的方法
[[greeting shouldNot] beNil];
});
it(@"should equal to ‘Hello world‘", ^{
[[greeting should] equal:@"Hello world"];
});
});
});
使用Kiwi测试tableView的datasource的方法:
SPEC_BEGIN(ArrayDataSourceSpec)
describe(@"ArrayDataSource", ^{
context(@"Initializing", ^{
it(@"should not be allowed using init", ^{
[[[[ArrayDataSource alloc] init] should] beNil];
});
});
context(@"Configuration", ^{
__block DetialTableViewCell *configuredCell = nil;
__block id configuredObject = nil;
TableViewCellConfigureBlock block = ^(DetialTableViewCell *a, id b){
configuredCell = a;
configuredObject = b;
};
ArrayDataSource *dataSource = [[ArrayDataSource alloc] initWithItems:@[@"a", @"b"] cellIdentifier:@"foo" configureCellBlock:block];
id mockTableView = [UITableView mock];
DetialTableViewCell *cell = [[DetialTableViewCell alloc] init];
__block id result = nil;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
it(@"should receive cell request", ^{
[[mockTableView should] receive:@selector(dequeueReusableCellWithIdentifier:forIndexPath:) andReturn:cell withArguments:@"foo",indexPath];
result = [dataSource tableView:mockTableView cellForRowAtIndexPath:indexPath];
});
it(@"should return the dummy cell", ^{
[[result should] equal:cell];
});
it(@"should have benn passed to the block", ^{
[[configuredCell should] equal:cell];
});
it(@"should have the same configured object", ^{
[[configuredObject should] equal:@"a"];
});
});
context(@"number of rows", ^{
id mockTableView = [UITableView mock];
ArrayDataSource *dataSource = [[ArrayDataSource alloc] initWithItems:@[@"a", @"b"] cellIdentifier:@"foo" configureCellBlock:nil];
it(@"should be 2 items", ^{
NSInteger count = [dataSource tableView:mockTableView numberOfRowsInSection:0];
[[theValue(count) should] equal:theValue(2)];
});
});
});
SPEC_END
第四章 Xcode中Instruments
instruments是一款Xcode自带的自动化测试工具。里面包含了很多的模块,可以对iOS APP进行不同方面的测试,但是常用的就有下面几种:
1.Leaks
Leaks可以检测内存泄漏点,Allocations跟踪模板可以查看内存的使用情况。在Instruments中,虽然选择了Leaks模板,但默认情况下也会添加Allocations模板。基本上凡是分析内存都会使用Allocations模板,它可以监控内存分布情况。双击错误位置会直接找到错误代码位置。
使用方法参考:http://cn.cocos2d-x.org/tutorial/show?id=1837
2.Automation
测试UI的工具,UI测试即使用代码的形式来模拟事件触发,并让它们就好像真的是由用户的行为所触发的那样。如果测试中遇到错误,双击错误,到代码错误位置进行查看。
简单的JS登录脚本:
var target = UIATarget.localTarget();
var app = target.frontMostApp();
var mainWindow = app.mainWindow();
target.logElementTree();
var userTextField = mainWindow.textFields()[0];
userTextField.setValue("admin");
mainWindow.secureTextFields()[0].setValue("admin");
var button = mainWindow.buttons()["登 录"];
button.tap(); //点击button
Automation具有录制功能,点击录制后打开模拟器进行操作,Automation会自动生成JS代码,我们只需要在我们需要测试的模块加上For循环或者其他你想要的实现测试的方式即可。
使用方法参考:http://www.th7.cn/Program/IOS/201404/192097.shtml