单元测试(Unit Testing)又称为模块测试,是针对程序模块软件设计来进行正确性检验的测试工做。程序单元是应用的最小可测试部件。对于面向对象编程,最小单元就是方法,包括基类、抽象类、或者派生类中的方法。 每一个理想的测试案例独立于其它case,测试时需隔离模块。单元测试一般由软件开发人员编写,用于确保所写的代码匹配软件需求和遵循开发目标。它的实施方式能够是手动的,或是构建自动化的一部分。 单元测试容许程序员在将来重构代码,且确保模块依然工做正确。这个过程是为全部方法编写单元测试,一旦变动致使错误发生,借助于单元测试能够快速定位并修复错误。 可读性强的单元测试可使程序员方便地检查代码片段是否依然正常工做。良好设计的单元测试案例覆盖程序单元分支和循环条件的全部路径。在连续的单元测试环境,经过其固有的持续维护工做,单元测试能够延续用于准确反映当任何变动发生时可执行程序和代码的表现。借助于上述开发实践和单元测试的覆盖,能够老是维持准确性。php
代码能够经过编译器检查语法的正确性,却不能保证代码逻辑是正确的,尤为包含了许多单元分支的状况下,单元测试能够保证代码的行为和结果与咱们的预期和需求一致。在测试某段代码的行为是否和你的指望一致时,你须要确认,在任何状况下,这段代码是否都和你的指望一致,譬如参数可能为空,可能的异步操做等。html
保证原有单元测试正确的状况下,不管如何修改单元内部代码,测试的结果应该是正确的,且修改后不会影响到其余的模块。ios
为了保证可行的可持续的单元测试,程序单元应该是低耦合的,不然,单元测试将难以进行。git
单元测试在开发前期检验了代码逻辑的正确性,开发后期,不管是修改代码内部抑或重构,测试的结果为这一切提供了可量化的保障。程序员
为了可进行单元测试,尤为是先写单元测试(TDD),咱们将从调用者思考,从接口上思考,咱们必须把程序单元设计成接口功能划分清晰的,易于测试的,且与外部模块耦合性尽量小。github
在原代码基础上开发及修改功能时,单元测试是一种快捷,可靠的回归。express
从效果上而言,单元测试就像是能执行的文档,说明了在你用各类条件调用代码时,你所能指望这段代码完成的功能。编程
测试驱动开发(Test-driven development,TDD)是一种软件开发过程当中的应用方法,由极限编程中倡导,以其倡导先写测试程序,而后编码实现其功能得名。测试驱动开发是戴两顶帽子思考的开发方式:先戴上实现功能的帽子,在测试的辅助下,快速实现其功能;再戴上重构的帽子,在测试的保护下,经过去除冗余的代码,提升代码质量。测试驱动着整个开发过程:首先,驱动代码的设计和功能的实现;其后,驱动代码的再设计和重构。xcode
行为驱动开发(Behavior-driven development,BDD)是一种敏捷软件开发的技术,BDD的重点是经过与利益相关者的讨论取得对预期的软件行为的清醒认识。它经过用天然语言书写非程序员可读的测试用例扩展了 测试驱动开发方法(TDD)
。这让开发者得以把精力集中在代码应该怎么写,而不是技术细节上,并且也最大程度的减小了将代码编写者的技术语言与商业客户、用户、利益相关者、项目管理者等的领域语言之间来回翻译的代价。bash
在iOS单元测试框架中,kiwi是BDD的表明。
Xcode集成了对单元测试的支持,XCode4.x集成的是OCUnit,到了XCode5.x时代就升级为了XCTest,XCode7.x时代XCtest还能够进行UI测试。下面咱们简单介绍下XCTest的使用。 在xcode新建项目中,默认会建一个单元测试的target,并创建一个继承于XCTestCase的测试用例类
#import <XCTest/XCTest.h>
#import "ASRevenueBL.h"
@interface ASUnitTestFirstDemoTests : XCTestCase
@property (nonatomic, strong) ASRevenueBL *revenueBL;
@end
@implementation ASUnitTestFirstDemoTests
- (void)setUp {
[super setUp];
self.revenueBL = [[ASRevenueBL alloc] init];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
self.revenueBL = nil;
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testLevel1 { // 异步测试
double revenue = 5000;
double tax = [self.revenueBL calculate:revenue];
XCTAssertEqual(tax, 45.0, @"用例1测试失败");
XCTAssertTrue(tax == 45.0);
}
- (void)testLevel2 {
XCTestExpectation *exp = [self expectationWithDescription:@"超时"];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
double revenue = 1500;
double tax = [self.revenueBL calculate:revenue];
sleep(1);
XCTAssertEqual(tax, 45.0, @"用例2测试失败");
[exp fulfill]; // exp结束
}];
[self waitForExpectationsWithTimeout:3 handler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
for (int a = 0; a<10000; a+=a) {
NSLog(@"%zd", a);
}
}];
}
@end
复制代码
#import "ASRevenueBL.h"
#define baseNum 3500.0
@implementation ASRevenueBL
- (double)calculate:(double)revenue {
double tax = 0.0;
double dbTaxRevenue = revenue - baseNum;
if (dbTaxRevenue <= 1500) {
tax = dbTaxRevenue * 0.03;
} else if (dbTaxRevenue > 1500 && dbTaxRevenue <= 4500) {
tax = dbTaxRevenue * 0.1 - 105;
} else if (dbTaxRevenue > 4500 && dbTaxRevenue <= 9000) {
tax = dbTaxRevenue * 0.2 - 555;
} else if (dbTaxRevenue > 9000 && dbTaxRevenue <= 35000) {
tax = dbTaxRevenue * 0.25 - 1005;
} else if (dbTaxRevenue > 35000 && dbTaxRevenue <= 55000) {
tax = dbTaxRevenue * 0.3 - 2755;
} else if (dbTaxRevenue > 55000 && dbTaxRevenue <= 80000) {
tax = dbTaxRevenue * 0.35 - 5505;
} else if (dbTaxRevenue > 80000) {
tax = dbTaxRevenue * 0.45 - 13505;
}
return tax;
}
@end
复制代码
- (void)setUp; // 测试开始前调用,能够初始化一些对象和变量
- (void)tearDown; // 测试结束后调用
- (void)test##Name; // 含有test前缀无参数无返回的方法都为一个测试方法
- (void)measureBlock:((void (^)(void)))block; // 测量执行时间
- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(nullable XCWaitCompletionHandler)handler; // 多少秒exception不fullfill就报错
- (XCTestExpectation *)expectationForNotification:(NSNotificationName)notificationName object:(nullable id)objectToObserve handler:(nullable XCNotificationExpectationHandler)handler; // 匹配到通知fullfill
- (XCTestExpectation *)expectationForPredicate:(NSPredicate *)predicate evaluatedWithObject:(id)object handler:(nullable XCPredicateExpectationHandler)handler; // predicate 返回true测试fullfill
...
复制代码
product-test 或 command + u即启动test
** 经常使用断言 **
XCTAssertNil(a1, ...)为空判断,expression为空时经过
XCTAssert(expression, ...)当expression值为TRUE时经过;
XCTAssertTrue(expression, format...)当expression值为TRUE时经过;
XCTAssertEqual(e1, e2, ...) e1 == e2经过;
XCTAssertThrows(expression, format...)当expression抛出异常时经过;
XCTAssertThrowsSpecific(expression, specificException, format...) 当expression抛出specificException异常时经过;
复制代码
testLevel1
经过revenueBL
计算出来的tax与预期相同,测试经过;testLevel2
经过revenueBL
计算出来的tax与预期不一样,测试不经过,反映出了程序一些逻辑漏洞;testPerformanceExample
中的平均执行时间比基准值低,测试经过。
在命令行中也能够启动测试,便于持续集成。
Assuner$ cd Desktop/
Desktop Assuner$ cd ASUnitTestFirstDemo/
ASUnitTestFirstDemo Assuner$ xcodebuild test -project ASUnitTestFirstDemo.xcodeproj -scheme ASUnitTestFirstDemo -destination 'platform=iOS Simulator,OS=11.0,name=iPhone 7' // 能够有多个destination
复制代码
结果
Test Suite 'All tests' started at 2017-09-11 11:12:16.348
Test Suite 'ASUnitTestFirstDemoTests.xctest' started at 2017-09-11 11:12:16.349
Test Suite 'ASUnitTestFirstDemoTests' started at 2017-09-11 11:12:16.349
Test Case '-[ASUnitTestFirstDemoTests testLevel1]' started.
Test Case '-[ASUnitTestFirstDemoTests testLevel1]' passed (0.001 seconds).
Test Case '-[ASUnitTestFirstDemoTests testLevel2]' started.
/Users/liyongguang-eleme-iOS-Development/Desktop/ASUnitTestFirstDemo/ASUnitTestFirstDemoTests/ASUnitTestFirstDemoTests.m:46: error: -[ASUnitTestFirstDemoTests testLevel2] : ((tax) equal to (45.0)) failed: ("-60") is not equal to ("45") - 用例2测试失败
Test Case '-[ASUnitTestFirstDemoTests testLevel2]' failed (1.007 seconds).
Test Suite 'ASUnitTestFirstDemoTests' failed at 2017-09-11 11:12:17.358.
Executed 2 tests, with 1 failure (0 unexpected) in 1.008 (1.009) seconds
Test Suite 'ASUnitTestFirstDemoTests.xctest' failed at 2017-09-11 11:12:17.359.
Executed 2 tests, with 1 failure (0 unexpected) in 1.008 (1.010) seconds
Test Suite 'All tests' failed at 2017-09-11 11:12:17.360.
Executed 2 tests, with 1 failure (0 unexpected) in 1.008 (1.012) seconds
Failing tests:
-[ASUnitTestFirstDemoTests testLevel2]
** TEST FAILED **
复制代码
若是是workspace
xcodebuild -workspace ASKiwiTest.xcworkspace -scheme ASKiwiTest-Example -destination 'platform=iOS Simulator,OS=11.0,name=iPhone 7' test
复制代码
每一个test方法都会跑一遍,并给出结果描述。