十.測試及發佈

十.測試及發佈

10.1 測試類型

  • 單元測試:在模擬環境中測試一個獨立方法來保證其有效性。
  • 功能測試:在真是環境中測試一個方法來確保其準確性。
  • 性能測試:測試一個方法,模塊或完整應用的性能。

10.2 定義

  • 測試用例:須要進行測試的一個場景。它包含一個方法,功能或程序執行所需的條件;測試場景須要的一系列輸入變量;以及一個指望行爲,其中包括系統的輸出或改變。
  • 測試夾具:表示進行一個或多個測試用例前所需的準備和清理階段。其中包括對象建立,依賴項設置,數據庫設置,等等。
  • 測試套件:包含一系列測試夾具的測試用例,能夠嵌套其餘的測試套件,它用於聚合應當一塊兒執行的測試用例。
  • 測試運行器:執行測試並提供結果的系統。Xcode是一個圖形化的測試運行器。命令行工具一樣能夠啓動一個測試自動化經常使用的客戶端。
  • 測試報告:測試成功或失敗的內容摘要,若有必要,還會附上錯誤信息。
  • 測試覆蓋率:衡量測試套件進行的測試數量,並能夠發現應用未被測試的部分。
  • 測試驅動開發:測試驅動開發是一種反覆迭代且開發週期很短的軟件開發流程。其過程包含編寫自動化測試用例,編寫經過測試的最小代碼集,重構代碼以符合准入標準。

10.3 單元測試

10.3.1 設置

添加測試目標: 輸入圖片說明html

product scheme測試設置: 輸入圖片說明node

10.3.2 編寫單元測試

繼承XCTestCase,默認的方法有4個:ios

  1. setUp(每一個測試方法調用前執行, 在執行完父類方法後添加自定義配置);
  2. tearDown(每一個測試方法調用後執行,在執行父類方法前添加自定義配置);
  3. textExample(一個示例);
  4. testPerformanceExample(在measureBlock中放入須要測試性能的代碼)方法;

輸入圖片說明

測試用例命名必須以test開頭,不可有參數且返回爲void,否則沒法識別爲測試方法。測試用例類型有3種:普通測試,性能測試與異步測試。git

運行單元測試: CMD + U測試整個文件的測試用例. 也可經過每一個單元測試用例左邊的按鈕執行單元測試,執行後綠色勾選按鈕表明測試成功,紅色叉號按鈕表明測試失敗。github

#import <XCTest/XCTest.h>
#import "HPUser.h"
[@interface](https://my.oschina.net/u/996807) ReactiveDemoOneTests : XCTestCase  //1.測試類名

[@end](https://my.oschina.net/u/567204)

@implementation ReactiveDemoOneTests

- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testExample {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
}

- (void)testPerformanceExample {
    // This is an example of a performance test case.
    [self measureBlock:^{
        // Put the code you want to measure the time of here.
    }];
}
-(void)testInitializer{//2.測試實例方法前綴必須是test
	HPUser *user = [[HPUser alloc]init];
	XCTAssert(user,@"user alloc-init fail");//3.XCTAssert方法用於斷言對象不是nil
}

-(void)testPropertyGetter{//4.測試屬性的獲取
	HPUser *user = [[HPUser alloc]init];
	user.name = @"ZJ";
	user.LoginTime = [NSDate date];
	
	ZJEntry *zj = [[ZJEntry alloc]init];
	zj.vipLever = @"10";
	zj.sexy = @"man";
	user.zoujie = zj;//5.測試狀態前的對象設置和須要提早執行的代碼
	
	XCTAssertEqualObjects(@"man", zj.sexy);
	XCTAssertEqualObjects(@"10", zj.vipLever);
	XCTAssertEqualObjects(@"ZJ", user.name);
	XCTAssertEqualObjects(user.zoujie, zj);//6.測試對象等價性的斷言
}
-(void) testOne{
	NSLog(@"1111111111111111");
	NSInteger wordA = 1;
	XCTAssertTrue(wordA == 1,@"斷言wordA等於0,不等於則測試沒經過");
}
-(void) testTwo{
	NSLog(@"22222222222222222");
}

-(void) fourMethod{
	NSLog(@"wrong method444444");
}

@end

參考文章:http://blog.csdn.net/Jolie_Yang/article/details/54891250數據庫

10.3.3 代碼覆蓋率

在自動化單元測試或功能測試中,使用代碼覆蓋率來表示通過測試的代碼的百分比。 1.集成覆蓋率報告 開啓測試覆蓋率數據: 輸入圖片說明bash

測試覆蓋率報告:閉包

輸入圖片說明

輸入圖片說明

2.外置測試率報告 還能夠生成XML或HTML格式的報告。 生成包含覆蓋率數據的報告,須要啓用一下標記:框架

  • Generate Debug Symbols :在編譯生成的庫中引入調試符號
  • Generate Test Coverage Files :生成包含覆蓋率數據的二進制文件
  • Instrument Program Flow :在測試用例運行時檢測應用

輸入圖片說明

輸入圖片說明

這些設置會在工程的衍生數據文件夾中生成.gcno和.gcda文件。.gcno包含重建基本代碼塊和塊對應源碼的詳細信息,.gcda包含代碼分支轉換的計數。異步

使用這些文件生成能夠導爲XML或HTML格式的報告:

  • lcov :從多個文件收集覆蓋率數據到一個統一的INFOFILE文件
  • genhtml :用lcov工具生成INFOFILE文件來生成HTML報告

使用Macposts或HomeBrew來安裝lcov軟件包 在Xcode的Build Phases中添加一個New Run Script Phase,從而生成報告。

lcov --directory "${OBJECT_FILE_DIR_normal}/${CURRENT_ARCH}"
    --capture
    --output-file "${PROJECT_DIR}\${PROJECT_NAME}.info"
    
genhtml --output-directory "${PROJECT_DIR}/${PROJECT_NAME}-covergae"
    "${PROJECT_DIR}/${PROJECT_NAME}.name"

10.3.4 異步操做

測試異步方法的步驟: (1)使用expectationWithDescription:方法來獲取XCTestExpectation實例。 (2)waitForExpectationsWithTimeout: handler:方法來等待操做完成。若是測試用例沒有完成,那麼將會調用回調處理的閉包塊。 (3)使用XCTestExpectation對象的fulfill方法來表示操做已經完成,等待結束。

-(void)testsignalForUserUpdates{
	HPUserService *userSever = [[HPUserService alloc]init];
	
	XCTestExpectation *expectation = [self expectationWithDescription:@"Test Fetch Type"];//1.建立XCTestExpectation 對象。能夠等待多個expectation。
	
	[userSever catchType:@"user" WithId:@"1" block:^(NSDictionary *responseDict) {//2.執行要被測試的方法
	NSString *key1Value =	[responseDict objectForKey:@"key1"];
		XCTAssertEqualObjects(@"uesrName", key1Value);
		//驗證數據,使用斷言 知足指望 這將致使後面的 -waitForExpectation 調用其completion handler而後返回。
		[expectation fulfill];
	}];
	
	// 測試會等在這裏,運行着run loop,直到超時或者expectation被知足
	[self waitForExpectationsWithTimeout:1 handler:^(NSError * _Nullable error) {
		NSLog(@"若是指望對象沒有知足,則進行清理操做");
	}];
}

10.3.5 Xcode福利:性能單元測試

XCTestCase提供了方法measureBlock來測量一個代碼塊的性能。 輸入圖片說明 一旦設置好基線,輸出結果將不只顯示平均值和標準差,還將顯示低於基線的最差結果。

10.3.6 模擬依賴

添加一個系統來模擬依賴關係,帶有模擬依賴的測試用例的工做方式以下: (1)配置依賴項以便依照提早定義好的方式運行,返回特定的值,或根據特定的輸入改變至特定的狀態。 (2)執行測試用例。 (3)重置依賴項使一切正常工做。

依賴項在-[set up]方法中配置,在測試夾具的-[tear down]中重置。

詞彙

  • dummy/double(n.傀儡,adj.虛擬的) 用於描述模擬測試對象的通用詞彙,double共有4種類型

    1. stub 在測試期間提供封裝好的數據以便被調用
    2. spy 捕獲並使參數和狀態信息可用
    3. mock 在受控制的狀況下模擬一個真實對象的行爲
    4. fake 除了底層實現不一樣,它與原始對象的工做方式如出一轍
  • BDD 行爲驅動開發,是測試驅動開發的一種擴展。

  • mocking框架 容許建立dummy框架。

OCMock很是好的mocking框架:

  • 建立mock對象 使用OCMClassMock宏來建立一個lei的mock實例
  • 建立spy對象 OCMPartialMock宏來建立一個spy或一個對象的部分mock
  • stub對象 OCMStub宏對函數進行stub操做,實現什麼也不作就返回或返回一個值
  • 驗證操做 OCMVerify宏來驗證一個底層子系統是否以特定方式進行交互
-(void)testUserWithId_Completion{
	id syncService = OCMClassMock([HPUserService class]);//模擬HPUserService類的一個對象
	[OCMStub([syncService new]) andReturn:syncService];//stub操做,返回以前獲得的mock對象,在mock對象上調用某個方法的時候,這個方法必定返回一個anObject.(也就是說強制替換了某個方法的返回值爲anObject)。
	//測試用例輸入
	NSString *userId = @"user-id",
	 		*fname = @"fn-user-id",
			*lname = @"ln-user-id",
			*gender =@"gender-x";
	NSDate *dob = [NSDate date];
	
	NSDictionary *data = @{@"id":userId,
						   @"fname":fname,
						   @"lanme":lname,
						   @"gender":gender,
						   @"dateOfBirth":dob
						   };

	[OCMStub([syncService fetchType:OCMOCK_ANY WithId:OCMOCK_ANY completion:OCMOCK_ANY]) andDo:^(NSInvocation *invocation) {//andDo 在mock對象調用someMethod的時候,andDo後面的block會調用.block能夠從NSInvocation中獲得一些參數,而後使用這個NSInvocation對象來構造返回值等等.
		
		void (^callBack)(NSDictionary *);
		[invocation getArgument:&callBack atIndex:4];
		callBack(data);
	}];
	
	HPUserService *svc = [HPUserService new];
	[svc fetchType:userId WithId:userId completion:^(NSDictionary *dic) {//配置完成開始測試
		//進行驗證
		XCTAssert(dic);
		XCTAssertEqualObjects(userId, dic[@"id"]);
		XCTAssertEqualObjects(fname, dic[@"fname"]);
		//......
	}];
	OCMVerify(syncService);//驗證方法以特定參數值被調用
}

參考文章:http://blog.csdn.net/jymn_chen/article/details/21562869, http://www.cnblogs.com/xilifeng/p/4690273.html

10.3.7 其餘框架

框架類型 名稱 保持器 GitHub URL
mock對象 OCMock https://github.com/erikdoe/ocmock
OCMockito https://github.com/jonreid/OCMockito
匹配器 Expecta https://github.com/specta/expecta
OCHamcrest https://github.com/hamcrest/OCHamcrest
TDD/BDD框架 Specta https://github.com/specta/specta
Kiwi https://github.com/kiwi-bdd/Kiw
Cedar https://github.com/pivotal/cedar
Calabash https://calaba.sh

10.4 功能測試

功能測試確保應用的功能與預期一致。 UITesting 參考文章:https://www.jianshu.com/p/31367c97c67d

10.5 持續集成與自動化

持續集成能夠保持代碼清晰並確保構建即時更新。 輸入圖片說明

10.6 最佳實踐

  • 測試全部的代碼,包括全部的初始化方法。
  • 測試參數值的全部組合。
  • 不要測試私有方法。將被測方法視爲黑盒。
  • 建議消除任何的外部依賴,保證能夠輕鬆駕馭各類場景。
  • 在每一個測試運行前設置狀態,並在執行後清理。確保每次測試用例結果不受其餘測試影響。
  • 每一個測試用例應當是可重複的,相同的輸入產生相同的結果。
  • 每一個測試用例必須使用斷言來驗證測試的代碼經過與否。
  • 完整的運行應當用代碼覆蓋率報告。

性能測試: 爲測試的內容編寫特定的代碼,例如計算運行速度,使用一個簡化的計時器。 跟蹤運行速度的計算器代碼:

#import <Foundation/Foundation.h>
@interface HPTimer : NSObject//公共API
+(HPTimer *)startWithName:(NSString *)name;
@property (nonatomic , readonly , assign) uint64_t timeNanos;
@property (nonatomic , copy) NSString *name;
-(uint64_t)stop;
-(void)printTree;
@end


#import "HPTimer.h"
#include <mach/mach_time.h>
#import "CocoaLumberjack.h"
#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelDebug;
#else
static const DDLogLevel ddLogLevel = DDLogLevelError;
#endif
@interface HPTimer()
@property (nonatomic , strong) HPTimer *parent;
@property (nonatomic , strong) NSMutableArray *children;
@property (nonatomic , assign) uint64_t startTime;
@property (nonatomic , assign) uint64_t stopTime;
@property (nonatomic , assign) BOOL stopped;
@property (nonatomic , copy) NSString *threadName;
@end
@implementation HPTimer
+(HPTimer *)startWithName:(NSString *)name{//建立新的定時器,用於計時
	NSThread *thread = [NSThread new];
	NSMutableDictionary *tls = thread.threadDictionary;
	
	HPTimer *top = [tls objectForKey:@"hp-timer-top"];//計時器上下文是線程的局部對象。在示例的實現中,一旦建立計時器上下文,計時器能夠在任意線程中止。這種實現能夠變爲讓計時器對限制線程調用stop方法,只在建立計時器的線程中調用
	
	HPTimer *rv = [[HPTimer alloc]initWithParent:top name:name];
	[tls setObject:rv forKey:@"hp-timer-top"];
	
	rv.startTime = mach_absolute_time();//https://www.jianshu.com/p/82475b5a7e19
	return rv;
}

-(instancetype)initWithParent:(HPTimer *)parent name:(NSString *)name{
	if(self = [super init]){
		self.parent = parent;
		self.name = name;
		self.stopped = NO;
		self.children = [NSMutableArray array];
		self.threadName = [NSThread currentThread].name;
		if (parent){
			[parent.children addObject:self];
		}
	}
	return self;
}

-(uint64_t)stop{
	self.stopTime = mach_absolute_time();
	self.stopped = YES;
	//self.timeNanos =   獲取以納米爲單位的時間間隔self.startTime && self.stopTime
	NSThread *thread = [NSThread new];
	NSMutableDictionary *tls = thread.threadDictionary;
	[tls setObject:self.parent forKey:@"hp-timer-top"];//從線程局部存儲中後去當前線程的計時器
	return self.timeNanos;
}

-(void)printTree{
	[self printTreeWithNode:self indent:@" "];
}

-(void)printTreeWithNode:(HPTimer *)node indent:(NSString *)indent{//美化計時器樹輸出
	if (node){
		DDLogDebug(@"%@[%@][%@] -> %lld",indent,self.threadName,self.name,self.timeNanos);
		NSArray *childern = node.children;
		if (childern.count > 0){
			indent = [indent stringByAppendingString:@" "];
			for (NSUInteger i = 0;i<childern.count;i++){
				[self printTreeWithNode:[childern objectAtIndex:i] indent:@" "];
			}
		}
	}	
}
@end


#pragma mark BEGIN  調用
-(void)methodA{
	HPTimer *timer = [HPTimer startWithName:@"method A"];//爲計時器賦予一個有意義的名字
	[self methodB];
	[timer stop];//運行後調用stop
	[timer printTree];//輸出運行耗時,包括任意嵌套的計時器
}
-(void)methodB{
	HPTimer *timer = [HPTimer startWithName:@"method B"];//在嵌套的方法調用中,建立其餘計時器
	//do some thing
	[timer stop];
	[timer printTree];//能夠輸出嵌套的調用樹
}
#pragma EDN
相關文章
相關標籤/搜索