iOS UnitTest 單元測(cè)試(邏輯,同異步,性能,封裝)

iOS測(cè)試我分三個(gè)篇介紹UI 測(cè)試后,覆蓋率測(cè)試,Unit單元測(cè)試.
本文介紹下面幾個(gè)功能邏輯等UnitTest部分:
1.邏輯功能測(cè)試
2.同,異步功能方法測(cè)試 - [分析AFNetworking解釋]
3.單元測(cè)試之Mock使用簡(jiǎn)介
4.性能耗時(shí)測(cè)試
5.單例測(cè)試
6.編寫測(cè)試用例該注意要點(diǎn)
7.封裝測(cè)試庫(kù)
8.自動(dòng)化測(cè)試,Jenkins的安裝和使用
9.自動(dòng)化單元測(cè)試,可以看LeanCloud 工程師的李智維的自動(dòng)化單元測(cè)試的直播錄影
李智維的演示github李智維的演示github

一 : 邏輯功能測(cè)試

(1)直接測(cè)試文件簡(jiǎn)單測(cè)試一個(gè)字符串是否為nil, 并熟悉XCTAssert

//簡(jiǎn)單例子
- (void)testExample {
    NSString *name = @"明星";
    XCTAssertNotNil(name, @"btn should not be nil");//報(bào)錯(cuò)提示語(yǔ):@"btn should not be nil"
}

上面簡(jiǎn)單在testExample中通過(guò)XCTAssertNotNil測(cè)試一下name是否為nil,點(diǎn)擊方法左側(cè)棱形按鈕測(cè)試.

我們定位XCTAssertNotNil跳轉(zhuǎn)到聲明文件XCTestAssertions.h文件,包含很多判斷,全部是宏定義方式,下面是網(wǎng)友的中文解釋:

 XCTFail(format…) 生成一個(gè)失敗的測(cè)試蚂维;

XCTAssertNil(a1, format...)為空判斷堂飞,a1為空時(shí)通過(guò),反之不通過(guò)莱衩;

XCTAssertNotNil(a1, format…)不為空判斷,a1不為空時(shí)通過(guò)娇澎,反之不通過(guò)笨蚁;

XCTAssert(expression, format...)當(dāng)expression求值為TRUE時(shí)通過(guò);

XCTAssertTrue(expression, format...)當(dāng)expression求值為TRUE時(shí)通過(guò)趟庄;

XCTAssertFalse(expression, format...)當(dāng)expression求值為False時(shí)通過(guò)括细;

XCTAssertEqualObjects(a1, a2, format...)判斷相等,[a1 isEqual:a2]值為TRUE時(shí)通過(guò)戚啥,其中一個(gè)不為空時(shí)奋单,不通過(guò);

XCTAssertNotEqualObjects(a1, a2, format...)判斷不等猫十,[a1 isEqual:a2]值為False時(shí)通過(guò)览濒;

XCTAssertEqual(a1, a2, format...)判斷相等(當(dāng)a1和a2是 C語(yǔ)言標(biāo)量、結(jié)構(gòu)體或聯(lián)合體時(shí)使用,實(shí)際測(cè)試發(fā)現(xiàn)NSString也可以)拖云;

XCTAssertNotEqual(a1, a2, format...)判斷不等(當(dāng)a1和a2是 C語(yǔ)言標(biāo)量贷笛、結(jié)構(gòu)體或聯(lián)合體時(shí)使用);

XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)判斷相等宙项,(double或float類型)提供一個(gè)誤差范圍乏苦,當(dāng)在誤差范圍(+/-accuracy)以內(nèi)相等時(shí)通過(guò)測(cè)試;

XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判斷不等尤筐,(double或float類型)提供一個(gè)誤差范圍汇荐,當(dāng)在誤差范圍以內(nèi)不等時(shí)通過(guò)測(cè)試洞就;

XCTAssertThrows(expression, format...)異常測(cè)試,當(dāng)expression發(fā)生異常時(shí)通過(guò)拢驾;反之不通過(guò)奖磁;(很變態(tài)) XCTAssertThrowsSpecific(expression, specificException, format...) 異常測(cè)試,當(dāng)expression發(fā)生specificException異常時(shí)通過(guò)繁疤;反之發(fā)生其他異晨或不發(fā)生異常均不通過(guò);

XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)異常測(cè)試稠腊,當(dāng)expression發(fā)生具體異常躁染、具體異常名稱的異常時(shí)通過(guò)測(cè)試,反之不通過(guò)架忌;

XCTAssertNoThrow(expression, format…)異常測(cè)試吞彤,當(dāng)expression沒(méi)有發(fā)生異常時(shí)通過(guò)測(cè)試;

XCTAssertNoThrowSpecific(expression, specificException, format...)異常測(cè)試叹放,當(dāng)expression沒(méi)有發(fā)生具體異常饰恕、具體異常名稱的異常時(shí)通過(guò)測(cè)試,反之不通過(guò)井仰;

XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)異常測(cè)試埋嵌,當(dāng)expression沒(méi)有發(fā)生具體異常、具體異常名稱的異常時(shí)通過(guò)測(cè)試俱恶,反之不通過(guò)

特別注意下XCTAssertEqualObjects和XCTAssertEqual雹嗦。

XCTAssertEqualObjects(a1, a2, format...)的判斷條件是[a1 isEqual:a2]是否返回一個(gè)YES。

XCTAssertEqual(a1, a2, format...)的判斷條件是a1 == a2是否返回一個(gè)YES合是。

(2) 測(cè)試項(xiàng)目中文件中的某個(gè)方法 - 沒(méi)有返回值

<2.1>創(chuàng)建一個(gè)LoginViewController文件,并在頭文件中加上- (void)loginWithPhone:(NSString *)phone code:(NSString *)code方法:

#import "LoginViewController.h"
@interface LoginViewController ()
@end
@implementation LoginViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

//手機(jī)驗(yàn)證碼登錄
- (void)loginWithPhone:(NSString *)phone code:(NSString *)code {
    NSMutableDictionary *dic = @{}.mutableCopy;
    [dic setObject:phone forKey:@"phone"];
    [dic setObject:code forKey:@"code"];
    NSLog(@"%@",dic);
}
@end

<2.2>在Tests文件夾下創(chuàng)建一個(gè)測(cè)試文件LoginVCtrlTests,創(chuàng)建變量,并調(diào)用其loginWithPhone方法

#import <XCTest/XCTest.h>
#import "LoginViewController.h"
@interface LoginVCtrlTests : XCTestCase
@property(nonatomic,strong)LoginViewController *loginVC;
@end

@implementation LoginVCtrlTests

- (void)setUp {
    [super setUp];
    self.loginVC = [[LoginViewController alloc]init];
}

- (void)tearDown {
    [super tearDown];
    self.loginVC = nil;
}

- (void)testExample {
    [self.loginVC loginWithPhone:nil code:@"3345"];
}

點(diǎn)擊按鈕測(cè)試testExample方法,運(yùn)行后發(fā)現(xiàn)報(bào)錯(cuò)如下,意思是可變字典setObject插入對(duì)象不能為nil:

caught "NSInvalidArgumentException", 
"*** -[__NSDictionaryM setObject:forKey:]: 
object cannot be nil (key: phone)"

顯然loginWithPhone方法對(duì)參數(shù)沒(méi)有判斷完整.所以實(shí)際測(cè)試中,可以填入各種類型數(shù)據(jù)來(lái)完善該方法比如null,nil,@"",等等

- (void)testExample {
    [self.loginVC loginWithPhone:nil code:@"3345"];
    [self.loginVC loginWithPhone:null code:nil];
    [self.loginVC loginWithPhone:@"" code:null];
}

(3) 測(cè)試項(xiàng)目中文件中的某個(gè)方法 - 有返回值

在LoginViewController添加下面校驗(yàn)手機(jī)號(hào)合法性方法,返回一個(gè)bool值:

- (BOOL)checkPhoneStr:(NSString *)phone {
    //判斷phone是否合法的代碼
    //....

    return YES;
}

然后在測(cè)試文件- (void)testExample 中再加上下面兩行代碼,判斷手機(jī)是否合法(返回值是否為true,不然報(bào)錯(cuò)),然后運(yùn)行測(cè)試, 結(jié)果報(bào)錯(cuò),如下圖所示:

手機(jī)是否合法

(所以現(xiàn)在要做的是傳各種參數(shù)吧)

二 : 異步功能方法測(cè)試 ,通過(guò)分析AFNetworking框架描述

AFNetworking涉及多線程和異步等功能,所以拿來(lái)學(xué)習(xí),下載 AFNetworking 項(xiàng)目,打開項(xiàng)目后直接進(jìn)入Tests目錄下面:

找到AFImageDownloaderTests.m文件,copy前部分代碼如下:

#import "AFTestCase.h"
#import "AFImageDownloader.h"

@interface AFImageDownloaderTests : AFTestCase
@property (nonatomic, strong) NSURLRequest *pngRequest;
@property (nonatomic, strong) NSURLRequest *jpegRequest;
@property (nonatomic, strong) AFImageDownloader *downloader;
@end

@implementation AFImageDownloaderTests

- (void)setUp {
    [super setUp];
    self.downloader = [[AFImageDownloader alloc] init];
    [[AFImageDownloader defaultURLCache] removeAllCachedResponses];
    [[[AFImageDownloader defaultInstance] imageCache] removeAllImages];
    self.pngRequest = [NSURLRequest requestWithURL:self.pngURL];
    self.jpegRequest = [NSURLRequest requestWithURL:self.jpegURL];
}

- (void)tearDown {
    [self.downloader.sessionManager invalidateSessionCancelingTasks:YES];
    self.downloader = nil;
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
    self.pngRequest = nil;
}

#pragma mark - Image Download

- (void)testThatImageDownloaderSingletonCanBeInitialized {
    AFImageDownloader *downloader = [AFImageDownloader defaultInstance];
    XCTAssertNotNil(downloader, @"Downloader should not be nil");
}

- (void)testThatImageDownloaderCanBeInitializedAndDeinitializedWithActiveDownloads {
    [self.downloader downloadImageForURLRequest:self.pngRequest
                                   success:nil
                                   failure:nil];
    self.downloader = nil;
    XCTAssertNil(self.downloader, @"Downloader should be nil");
}

- (void)testThatImageDownloaderReturnsNilWithInvalidURL
{
    NSMutableURLRequest *mutableURLRequest = [NSMutableURLRequest requestWithURL:self.pngURL];
    [mutableURLRequest setURL:nil];
    /** NSURLRequest nor NSMutableURLRequest can be initialized with a nil URL, 
     *  but NSMutableURLRequest can have its URL set to nil 
     **/
    NSURLRequest *invalidRequest = [mutableURLRequest copy];
    XCTestExpectation *expectation = [self expectationWithDescription:@"Request should fail"];
    AFImageDownloadReceipt *downloadReceipt = [self.downloader
                                               downloadImageForURLRequest:invalidRequest
                                               success:nil
                                               failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
                                                   XCTAssertNotNil(error);
                                                   XCTAssertTrue([error.domain isEqualToString:NSURLErrorDomain]);
                                                   XCTAssertTrue(error.code == NSURLErrorBadURL);
                                                   [expectation fulfill];
                                               }];
    [self waitForExpectationsWithCommonTimeout];
    XCTAssertNil(downloadReceipt, @"downloadReceipt should be nil");
}

簡(jiǎn)單解釋一下上面代碼:
1.導(dǎo)入測(cè)試文件
2.聲明屬性
3.setUp方法設(shè)置屬性,初始化
4.tearDown中銷毀屬性
5.前兩個(gè)方法testThatImageDownloaderSingletonCanBeInitialized 和testThatImageDownloaderCanBeInitializedAndDeinitializedWithActiveDownloads :通過(guò)XCTAssertNotNil和 XCTAssertNil 判斷不為nil 和 為nil. 簡(jiǎn)單使用
6.下載方法測(cè)試:testThatImageDownloaderReturnsNilWithInvalidURL
首先是創(chuàng)建NSMutableURLRequest 和 AFImageDownloadReceipt 對(duì)象來(lái)下載圖片

然后在[self.downloader downloadImageForURLRequest...]方法中block回調(diào)進(jìn)行判斷:XCTAssertNotNil和XCTAssertTrue 等等.

關(guān)鍵是下面這行代碼:

[expectation fulfill];
fulfill:異步請(qǐng)求結(jié)束后需要調(diào)用expectation 的 fulfill方法, 通知測(cè)試異步請(qǐng)求已結(jié)束. 然后執(zhí)行下面等待超時(shí)的方法:

[self waitForExpectationsWithCommonTimeout];
點(diǎn)擊該方法,發(fā)現(xiàn)跳轉(zhuǎn)到AFTestCase文件中,最后發(fā)現(xiàn)執(zhí)行了下面代碼:
[self waitForExpectationsWithTimeout:self.networkTimeout handler:handler];
顯然該方法是指多少秒后超時(shí),因?yàn)檎?qǐng)求是需要時(shí)間的,設(shè)置Timeout就很有必要了.

所以對(duì)于異步執(zhí)行測(cè)試一般以下步驟(OC):
- (void)testExample {
    
    //1: 創(chuàng)建XCTestExpectation對(duì)象
    XCTestExpectation* expect = [self expectationWithDescription:@"請(qǐng)求超時(shí)timeout!"];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        sleep(5); //2: 假設(shè)請(qǐng)求需要耗時(shí)5秒
        NSError *error = [[NSError alloc]init];//3: 假設(shè)回調(diào)返回一個(gè)error
        XCTAssertNotNil(error); //4: 對(duì)結(jié)果進(jìn)行判斷
        XCTAssertTrue([error.domain isEqualToString:NSURLErrorDomain]);
      
        dispatch_async(dispatch_get_main_queue(), ^{
            //主線程操作....
        });
        [expect fulfill];//5: 異步結(jié)束調(diào)用fulfill,告知請(qǐng)求結(jié)束
        });

    [self waitForExpectationsWithTimeout:15 handler:^(NSError *error) {
        //6: 如果15秒內(nèi)沒(méi)有收到fulfill方法通知調(diào)用次方法
        //超時(shí)后執(zhí)行一些操作:
        }];
    
    //7: 對(duì)象被回收
    XCTAssertNil(expect, @"expect should be nil");
    
}
異步請(qǐng)求單元測(cè)試Swift代碼:
func testAsyncURLConnection(){
        let URL = NSURL(string: "http://www.baidu.com")!
        let expect = expectation(description: "GET \(URL)")
        
        let session = URLSession.shared
        let task = session.dataTask(with: URL as URL, completionHandler: {(data, response, error) in
            
            XCTAssertNotNil(data, "返回?cái)?shù)據(jù)不應(yīng)該為空")
            XCTAssertNil(error, "error應(yīng)該為nil")
            expect.fulfill() //請(qǐng)求結(jié)束通知測(cè)試
            
            if response != nil {
                let httpResponse: HTTPURLResponse = response as! HTTPURLResponse
                
                XCTAssertEqual(httpResponse.statusCode, 200, "請(qǐng)求失敗!")
                
                DispatchQueue.main.async {
                    //主線程中干事情
                }
                
            } else {
                XCTFail("請(qǐng)求失敗!")
            }
        })
        
        task.resume()
        
        //請(qǐng)求超時(shí)
        waitForExpectations(timeout: (task.originalRequest?.timeoutInterval)!, handler: {error in
            task.cancel()
        })
    }

三 : 單元測(cè)試之Mock使用

使用前需要參考Mock 介紹及下載

Mock是什么?
使用場(chǎng)景:
比如上面(1)異步加載測(cè)試:沒(méi)有網(wǎng)絡(luò)或者不佳時(shí),自行創(chuàng)建數(shù)據(jù). (2)復(fù)雜數(shù)據(jù)庫(kù)查詢:數(shù)據(jù)庫(kù)在內(nèi)網(wǎng)或者暫時(shí)無(wú)法查詢時(shí),自行創(chuàng)建數(shù)據(jù).(3)多重網(wǎng)絡(luò)交互:避免復(fù)雜交互,需要簡(jiǎn)化測(cè)試流程,等等才能得到返回?cái)?shù)據(jù)時(shí).
或者說(shuō)是,在測(cè)試過(guò)程中了罪,對(duì)于一些不容易構(gòu)造或不容易獲取的對(duì)象,此時(shí)你可以創(chuàng)建一個(gè)虛擬的對(duì)象(mock object)來(lái)完成測(cè)試, Mock卻很方便,它直接返回你需要的數(shù)據(jù),不用初始化對(duì)象,避免復(fù)雜的數(shù)據(jù)獲取過(guò)程:

如下網(wǎng)站給出的示例代碼片段:

- (void)testDisplaysTweetsRetrievedFromConnection
{
  Controller *controller = [[[Controller alloc] init] autorelease];
 //聲明id類型對(duì)象(不需要TwitterConnection類直接初始化對(duì)象)
  id mockConnection = OCMClassMock([TwitterConnection class]);
  controller.connection = mockConnection;

  Tweet *testTweet = /* create a tweet somehow */;   
  NSArray *tweetArray = [NSArray arrayWithObject:testTweet];
 //模擬返回?cái)?shù)據(jù)
  OCMStub([mockConnection fetchTweets]).andReturn(tweetArray);

  [controller updateTweetView];
}

比如創(chuàng)建tableview測(cè)試:

id mockTableView = [OCMockObject mockForClass:[UITableView class]];
    UITableViewCell *cell = [[UITableViewCell alloc] init];
    [[[mockTableView expect] andReturn:cell] dequeueReusableCellWithIdentifier:@"MockTableViewCell" forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
總而言之,Mock可以方便的創(chuàng)建你想要的object對(duì)象,并調(diào)用其公共方法.詳細(xì)Mock語(yǔ)法和使用這里不做介紹

四 : 性能耗時(shí)測(cè)試

當(dāng)項(xiàng)目創(chuàng)建完測(cè)試文件時(shí),OC就會(huì)自動(dòng)創(chuàng)建下面方法:

- (void)testPerformanceExample {
    // This is an example of a performance test case.
    [self measureBlock:^{
        // Put the code you want to measure the time of here.
    }];
}
這個(gè)方法意思是將耗時(shí)操作丟到measureBlock里就行了:
[self measureBlock:^{
        // Put the code you want to measure the time of here.
        NSMutableDictionary *dic = @{}.mutableCopy;
        for (NSInteger i = 0; i < 10000; i++) {
            NSString *obj = [NSString stringWithFormat:@"%ld",(long)i];
            [dic setObject:obj forKey:obj];;
        }
    }];

測(cè)試后打印日志,其中有平均average: 0.011,所有耗時(shí)values: [0.012836, 0.015668, 0.012153, 0.010468, 0.011057, 0.009932, 0.010598, 0.010772, 0.010296, 0.010185],等等:

Test Case '-[ARKit_OCTests testPerformanceExample]' started.
/Users/niexiaobo/Desktop/demo/ARKit-OC/ARKit-OCTests/ARKit_OCTests.m:58: Test Case '-[ARKit_OCTests testPerformanceExample]' 
measured [Time, seconds] average: 0.011, relative standard deviation: 14.609%, 
values: [0.012836, 0.015668, 0.012153, 0.010468, 0.011057, 0.009932, 0.010598, 0.010772, 0.010296, 0.010185], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
Test Case '-[ARKit_OCTests testPerformanceExample]' passed (0.419 seconds).

傳統(tǒng)耗時(shí)測(cè)試:

NSTimeInterval start = CACurrentMediaTime();
        
        NSMutableDictionary *dic = @{}.mutableCopy;
        for (NSInteger i = 0; i < 10000; i++) {
            NSString *obj = [NSString stringWithFormat:@"%ld",(long)i];
            [dic setObject:obj forKey:obj];;
        }
        NSLog(@"%lf",CACurrentMediaTime() - start);

五 : 單例測(cè)試

定義單例,在公共頭文件導(dǎo)入宏定義:

#define singleH(name) +(instancetype)share##name;

#if __has_feature(objc_arc)

#define singleM(name) static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        _instance = [super allocWithZone:zone];\
    });\
    return _instance;\
}\
\
+(instancetype)share##name\
{\
    return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
    return _instance;\
}\
\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
    return _instance;\
}
#else
#define singleM static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)shareTools\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(oneway void)release\
{\
}\
\
-(instancetype)retain\
{\
    return _instance;\
}\
\
-(NSUInteger)retainCount\
{\
    return MAXFLOAT;\
}
#endif

既然單例的目的是不管怎么初始化創(chuàng)建對(duì)象永遠(yuǎn)都是返回唯一且相同的那個(gè),那么測(cè)試也一樣,測(cè)試不同,重復(fù)的方法應(yīng)該返回同一對(duì)象,并且可用:

- (void)testFilesManagerSingle
{
    NSMutableArray *managerArray = [NSMutableArray array];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        FilesManager *tempManager = [[FilesManager alloc] init];
        [managerArray addObject:tempManager];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        FilesManager *tempManager = [[FilesManager alloc] init];
        [managerArray addObject:tempManager];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        FilesManager *tempManager = [FilesManager shareManager];
        [managerArray addObject:tempManager];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        FilesManager *tempManager = [FilesManager shareManager];
        [managerArray addObject:tempManager];
    });
    
    FilesManager *managerObj = [FilesManager shareManager];
    
    [managerArray enumerateObjectsUsingBlock:^(FilesManager *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        XCTAssertEqual(managerObj, obj, @"FilesManager is not single");
    }];
}

然后測(cè)試FilesManager的open和close等方法是否正常.等等

六 : 編寫測(cè)試用例該注意要點(diǎn)

(1) 要注意創(chuàng)建完成一個(gè)測(cè)試文件自動(dòng)創(chuàng)建的幾個(gè)方法:

- (void)setUp {
    [super setUp];

}
- (void)tearDown {
}

使用:
我們?cè)诜椒╯etup()中聲明并創(chuàng)建一個(gè)Test對(duì)象
然后在方法tearDown()中釋放它. (有點(diǎn)像init 和 dealloc )

(2) 異步和性能測(cè)試往往比較耗時(shí),所以要注意和邏輯測(cè)試等分開測(cè)試
(3) 測(cè)試框架有好幾個(gè),對(duì)于中小型項(xiàng)目個(gè)人覺(jué)得考慮兼容性直接使用XCTest
(4) 公用方法等盡量抽離或者寫一個(gè)宏,比如本節(jié)中單例,或者[self waitForExpectationsWithCommonTimeout]; 方法寫一個(gè)TimeoutTest宏等等.

七 : 封裝測(cè)試庫(kù)

當(dāng)你的測(cè)試內(nèi)容越來(lái)越多時(shí),測(cè)試代碼就像工程一樣,甚至更復(fù)雜, 同樣單元測(cè)試也需要封裝,繼承,設(shè)計(jì)等等.

比如上面第二節(jié)里異步測(cè)試,AFImageDownloaderTests測(cè)試文件繼承自AFTestCase:


AFTestCase

八 : 自動(dòng)化測(cè)試,Jenkins的安裝和使用

[編輯中]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末聪全,一起剝皮案震驚了整個(gè)濱河市泊藕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌难礼,老刑警劉巖吱七,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鹤竭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)景醇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門臀稚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人三痰,你說(shuō)我怎么就攤上這事吧寺〈芄埽” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵稚机,是天一觀的道長(zhǎng)幕帆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赖条,這世上最難降的妖魔是什么失乾? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮纬乍,結(jié)果婚禮上碱茁,老公的妹妹穿的比我還像新娘。我一直安慰自己仿贬,他們只是感情好纽竣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茧泪,像睡著了一般蜓氨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上队伟,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天穴吹,我揣著相機(jī)與錄音,去河邊找鬼缰泡。 笑死刀荒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棘钞。 我是一名探鬼主播缠借,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宜猜!你這毒婦竟也來(lái)了泼返?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤姨拥,失蹤者是張志新(化名)和其女友劉穎绅喉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叫乌,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柴罐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了憨奸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片革屠。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出似芝,到底是詐尸還是另有隱情那婉,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布党瓮,位于F島的核電站详炬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏寞奸。R本人自食惡果不足惜呛谜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝇闭。 院中可真熱鬧呻率,春花似錦、人聲如沸呻引。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)逻悠。三九已至元践,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間童谒,已是汗流浹背单旁。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饥伊,地道東北人象浑。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓审轮,卻偏偏與公主長(zhǎng)得像狈惫,于是被迫代替她去往敵國(guó)和親洼专。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屠凶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354