iOS 單元測(cè)試

此篇文章并非原創(chuàng),? 轉(zhuǎn)載鏈接:http://www.reibang.com/p/c54f0cc08c20

單元測(cè)試不是一個(gè)小工程容为,需要多用些時(shí)間才能做好秽浇,不要希望通過(guò)這個(gè)文章就能掌握單元測(cè)試蟹地,這只是一個(gè)入門(mén)姑子,需要自己動(dòng)手操作

1鸠蚪、單元測(cè)試解釋

單元測(cè)試是開(kāi)發(fā)者編寫(xiě)的一小段代碼重慢,用于檢驗(yàn)被測(cè)代碼中的一個(gè)很明確的功能是否正確饥臂。通常而言,一個(gè)單元測(cè)試是用于判斷某個(gè)特定條件(或者場(chǎng)景)下某個(gè)特定函數(shù)的行為似踱。

執(zhí)行單元測(cè)試隅熙,是為了證明某段代碼的行為確實(shí)和開(kāi)發(fā)者所期望的一致。因此核芽,我們所要測(cè)試的是規(guī)模很小的囚戚、非常獨(dú)立的功能片段。通過(guò)對(duì)所有單獨(dú)部分的行為建立起信心轧简。然后驰坊,才能開(kāi)始測(cè)試整個(gè)系統(tǒng)

2、單元測(cè)試好處

image

2.1哮独、單元測(cè)試的好處:

單元測(cè)試使工作完成的更輕松

經(jīng)過(guò)單元測(cè)試的代碼拳芙,質(zhì)量能夠得到保證

單元測(cè)試發(fā)現(xiàn)的問(wèn)題很容易定位。

修改代碼犯的錯(cuò)皮璧,經(jīng)過(guò)單元測(cè)試易發(fā)現(xiàn)

單元測(cè)試可以在早期就發(fā)現(xiàn)性能問(wèn)題

單元測(cè)試使你的設(shè)計(jì)更好

大大減少花在調(diào)試上的時(shí)間

2.2不做單元測(cè)試的壞處:

代碼會(huì)暗藏很多缺陷,健壯性不強(qiáng)

系統(tǒng)測(cè)試發(fā)現(xiàn)的缺陷比較難以定位

為了修復(fù)缺陷而修改代碼舟扎,很可能會(huì)不小心犯錯(cuò),但是又不能及時(shí)發(fā)現(xiàn)這些新錯(cuò)誤悴务。

性能問(wèn)題很難定位睹限,性能優(yōu)化的時(shí)間很難控制。

3讯檐、前期準(zhǔn)備

3.1羡疗、創(chuàng)建項(xiàng)目

如同普通創(chuàng)建項(xiàng)目一樣,在輸入項(xiàng)目名以及其他信息后點(diǎn)擊選擇“Include Unit Tests”和“Include UI Tests”别洪,前者標(biāo)示單元測(cè)試顺囊,后者表示UI測(cè)試,源碼地址UnitTestDemo蕉拢。如下所示:

image

3.2特碳、引入OCMock

可以前往OCMock的官方GitHub上下載demo以及三方庫(kù)文件,不準(zhǔn)備使用OCMock的可以忽略晕换。

1.下載靜態(tài)庫(kù)的包午乓,并引入到工程里的對(duì)應(yīng)的TestDemo的target里

image

2.配置TARGETS:TestDemoTests Other linker flags,中間是靜態(tài)庫(kù)的絕對(duì)路徑闸准。$(SRCROOT)/usr/lib/libOCMock.a

image

3.在Header Search Paths 中增加 $(PROJECT)/usr/include,里面包含OCMock的文件益愈。

image

Libray Search Paths 包含 $(PROJECT)/usr/lib,靜態(tài)庫(kù)的相對(duì)路徑

image

4、單元測(cè)試

4.1蒸其、系統(tǒng)方法解釋

TestDemoTests.m是創(chuàng)建項(xiàng)目時(shí)選擇單元測(cè)試自動(dòng)生成的文件敏释。

/**

每個(gè)test方法執(zhí)行之前調(diào)用,在此方法中可以定義一些全局屬性摸袁,類(lèi)似controller中的viewdidload方法

*/

- (void)setUp {[super setUp];//定義self.VC = [[ViewController alloc] init];}

/**

每個(gè)test方法執(zhí)行之后調(diào)用,釋放測(cè)試用例的資源代碼钥顽,這個(gè)方法會(huì)每個(gè)測(cè)試用例執(zhí)行后調(diào)用

*/

- (void)tearDown {//結(jié)束后釋放self.VC = nil;[super tearDown];}

/**

測(cè)試用例的例子,注意測(cè)試用例一定要test開(kāi)頭

*/

- (void)testExample {//測(cè)試view是否加載出來(lái)XCTAssertNotNil(self.VC.view,@"view未成功加載出來(lái)");}

(void)testPerformanceExample {

//主要測(cè)試代碼性能

[self measureBlock:^{//檢測(cè)在此block中代碼的性能}];}

4.2靠汁、函數(shù)測(cè)試

在ViewController.h中定義函數(shù)并在ViewController.m實(shí)現(xiàn):

- (int)getNum; - (int)getNum{return 100;}

在里面TestDemoTests.m里面定義函數(shù)蜂大,必須以test開(kāi)頭,如果返回值不為100則測(cè)試失敗

//必須以test開(kāi)頭的函數(shù)- (void)testMyFuc{int result = self.VC.getNum;XCTAssertEqual(result, 100,@"測(cè)試普通函數(shù)不通過(guò)");}

4.3蝶怔、測(cè)試圖片處理

//測(cè)試圖片處理大小的性能奶浦,以及處理成功與否- (void)testImageResize{UIImage *image = [UIImage imageNamed:@"icon1.jpeg"];[self measureBlock:^{ //測(cè)試處理圖片代碼的性能// Put the code you want to measure the time of here.UIImage *resizedImage = [self imageWithImage:image scaledToSize:CGSizeMake(100, 100)];XCTAssertNotNil(resizedImage, @"縮放后圖片不應(yīng)為nil");CGFloat resizedWidth = resizedImage.size.width;CGFloat resizedHeight = resizedImage.size.height;XCTAssertTrue(resizedWidth == 100 && resizedHeight == 100, @"縮放后尺寸");}];}- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {UIGraphicsBeginImageContext(newSize);[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return newImage;}

4.4、異步測(cè)試

異步測(cè)試踢星,有三種方式(expectationWithDescription澳叉,expectationForPredicate和expectationForNotification)

4.4.1、expectationWithDescription

// 測(cè)試接口(異步測(cè)試)使用expectationWithDescription- (void)testAsynchronousURLConnection {[self measureBlock:^{NSLog(@"testAsynchronousURLConnection");//預(yù)先定義XCTestExpectation *expectation = [self expectationWithDescription:@"GET Baidu"];//測(cè)試地址NSURL *url = [NSURL URLWithString:@"https://www.baidu.com/"];NSURLSession *session = [NSURLSession sharedSession];NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {//? ? ? ? NSLog(@"data : %@", data);// XCTestExpectation條件已滿(mǎn)足沐悦,接下來(lái)的測(cè)試代碼可以開(kāi)始執(zhí)行了耳高。[expectation fulfill];XCTAssertNotNil(data, @"返回?cái)?shù)據(jù)不應(yīng)非nil");XCTAssertNil(error, @"error應(yīng)該為nil");if (nil != response) {NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;XCTAssertEqual(httpResponse.statusCode, 200, @"HTTPResponse的狀態(tài)碼應(yīng)該是200");XCTAssertEqual(httpResponse.URL.absoluteString, url.absoluteString, @"HTTPResponse的URL應(yīng)該與請(qǐng)求的URL一致");//? ? ? ? ? ? XCTAssertEqual(httpResponse.MIMEType, @"text/html", @"HTTPResponse的內(nèi)容應(yīng)該是text/html");} else {XCTFail(@"返回內(nèi)容不是NSHTTPURLResponse類(lèi)型");}}];[task resume];// 超時(shí)后執(zhí)行[self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) {[task cancel];}];}];}

4.4.2、expectationForPredicate

//異步測(cè)試所踊,使用expectationForPredicate,設(shè)置一個(gè)期望,在規(guī)定時(shí)間內(nèi)滿(mǎn)足期望則測(cè)試通過(guò)

- (void)testAsynExampleWithExpectationForPredicate {XCTAssertNil(self.imageView.image);self.imageView.image = [UIImage imageNamed:@"icon2"];

//設(shè)置一個(gè)期望

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"image != nil"];//若在規(guī)定時(shí)間內(nèi)滿(mǎn)足期望概荷,則測(cè)試成功[self expectationForPredicate:predicateevaluatedWithObject:self.imageViewhandler:nil];// 超時(shí)后執(zhí)行[self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) {}];}

4.4.3秕岛、expectationForNotification

//異步測(cè)試,使用expectationForNotification,該方法監(jiān)聽(tīng)一個(gè)通知,如果在規(guī)定時(shí)間內(nèi)正確收到通知?jiǎng)t測(cè)試通過(guò)- (void)testAsynExampleWithExpectationForNotification {//監(jiān)聽(tīng)通知误证,在規(guī)定時(shí)間內(nèi)受到通知继薛,則測(cè)試通過(guò)[self expectationForNotification:@"監(jiān)聽(tīng)通知的名稱(chēng)測(cè)試" object:nil handler:^BOOL(NSNotification * _Nonnull notification) {NSLog(@"請(qǐng)求成功");//做后續(xù)處理return YES;}];//下面2個(gè)地址可以查看測(cè)試通過(guò)與不通過(guò)的區(qū)別//測(cè)試通過(guò)NSURL *url = [NSURL URLWithString:@"https://www.baidu.com/"];//測(cè)試失敗//? ? NSURL *url = [NSURL URLWithString:@"www.baidu.com/"];NSURLSession *session = [NSURLSession sharedSession];NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {if (data && !error && response) {//發(fā)送通知[[NSNotificationCenter defaultCenter]postNotificationName:@"監(jiān)聽(tīng)通知的名稱(chēng)測(cè)試" object:nil];}}];[task resume];//設(shè)置延遲多少秒后,如果沒(méi)有滿(mǎn)足測(cè)試條件就報(bào)錯(cuò)[self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) {[task cancel];}];}

4.5愈捅、測(cè)試私有屬性和私有方法

在ViewController中定義一個(gè)私有屬性和一個(gè)私有方法

@interface ViewController ()@property (strong, nonatomic) IBOutlet UITableView *tv;@property (nonatomic, copy) NSString *privateString;@end//私有方法- (NSString *)privateFuc{return @"123456";}

在TestDemoTests中聲明ViewController的分類(lèi)

//測(cè)試ViewController的私有方法-通過(guò)分類(lèi)的方式@interfaceViewController(TestDemoTests)-(NSString*)privateFuc;@property(nonatomic,copy)NSString*privateString;@end

然后在測(cè)試方法中直接調(diào)用即可

- (void)testExample {//測(cè)試私有方法XCTAssertEqualObjects(self.VC.privateFuc, @"123456",@"");//測(cè)試私有屬性XCTAssertEqualObjects(self.VC.privateString, @"987654321",@"");}

4.6遏考、測(cè)試測(cè)試網(wǎng)絡(luò)請(qǐng)求---2017/05/12

項(xiàng)目的設(shè)計(jì)模式最好為mvvm模式,這樣可以獲取網(wǎng)絡(luò)請(qǐng)求的結(jié)果蓝谨,寫(xiě)單元測(cè)試時(shí)不需要更改項(xiàng)目源碼

單元測(cè)試代碼:

- (void)testLoginClick{XCTestExpectation *exp = [self expectationWithDescription:@"請(qǐng)求超時(shí)"];NSOperationQueue *queue = [[NSOperationQueue alloc]init];[queue addOperationWithBlock:^{LoginViewModel *loginModel = [[LoginViewModel alloc] initWithNormalWithMobile:@"18612334016" password:@"111111" lat:@"39.897445" lng:@"116.331398"];@weakify(self);loginModel.whenUpdated = ^(id error) {@strongify(self);XCTAssertNil(error);//如果斷言沒(méi)問(wèn)題灌具,就調(diào)用fulfill宣布測(cè)試滿(mǎn)足[exp fulfill];//? ? ? ? ? ? [self.loginVC dealWithContent];};self.loginVC.loginModel = loginModel;}];//設(shè)置延遲多少秒后,如果沒(méi)有滿(mǎn)足測(cè)試條件就報(bào)錯(cuò)[self waitForExpectationsWithTimeout:self.networkTimeout handler:^(NSError * _Nullable error) {if (error) {NSLog(@"Timeout Error: %@", error);}}];}

網(wǎng)絡(luò)請(qǐng)求源碼

//登錄- (IBAction)loginClick:(id)sender {if ([ISNull isNilOfSender:self.userName]) {self.userName = [self.mobileInput.text trim];if ([ISNull isNilOfSender:self.userName]) {[self presentFailureTips:@"請(qǐng)輸入用戶(hù)名"];return;}}if ([ISNull isNilOfSender:self.pwd]) {self.pwd = [self.passwordInput.text trim];if ([ISNull isNilOfSender:self.pwd]) {[self presentFailureTips:@"請(qǐng)輸入密碼"];return;}}[self presentLoadingTips:nil];[[BaseNetConfig shareInstance] configGlobalAPI:ICE];//獲取當(dāng)前位置Location *location = [AppLocation sharedInstance].location;NSString * lon = [NSString stringWithFormat:@"%@",location.lon];NSString * lat = [NSString stringWithFormat:@"%@",location.lat];if ([ISNull isNilOfSender:lon]){lon = @"0";}if ([ISNull isNilOfSender:lat]){lat = @"0";}self.loginModel = [[LoginViewModel alloc] initWithNormalWithMobile:self.userName password:self.pwd lat:lat lng:lon];@weakify(self);self.loginModel.whenUpdated = ^(id error) {@strongify(self);[self dismissTips];if (error){[self presentFailureTips:[NSString stringWithFormat:@"%@",error]];}else{[self dealWithContent];}};}

5譬巫、單元測(cè)試-OCMock

當(dāng)我們寫(xiě)單元測(cè)試的時(shí)候咖楣,不可避免的要去盡可能少的實(shí)例化一些具體的組件來(lái)保持測(cè)試既短又快。而且保持單元的隔離芦昔。在現(xiàn)代的面向?qū)ο笙到y(tǒng)中诱贿,測(cè)試的組件很可能會(huì)有幾個(gè)依賴(lài)的對(duì)象。我們用mock來(lái)替代實(shí)例化具體的依賴(lài)class。mock是在測(cè)試中的一個(gè)偽造的有預(yù)定義行為的具體對(duì)象的替身對(duì)象珠十。被測(cè)試的組件不知道其中的差異料扰!你的組件是在一個(gè)更大的系統(tǒng)中被設(shè)計(jì)的,你可以很有信心的用mock來(lái)測(cè)試你的組件焙蹭。

5.1晒杈、準(zhǔn)備

5.1.1、準(zhǔn)備模型-PersonModel

在target:TestDemo中新加NSObject類(lèi)型文件PersonModel

PersonModel.h@interface PersonModel : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, copy) NSString *gender;- (NSString *)getPersonName;- (NSString *)changeName:(NSString *)newName;@endPersonModel.m@implementation PersonModel- (instancetype)init{if (self = [super init]){self.name = @"liyong";self.gender = @"男";}return self;}- (NSString *)getPersonName{PersonModel *person = [[PersonModel alloc] init];return person.name;}- (NSString *)changeName:(NSString *)newName{PersonModel *person = [[PersonModel alloc] init];person.name = newName;return person.name;}@end

5.1.2壳嚎、新建單元測(cè)試文件

TestDemoTests.m是創(chuàng)建項(xiàng)目時(shí)選擇單元測(cè)試自動(dòng)生成的文件桐智,增加測(cè)試代碼時(shí)避免不了需要新建文件,繼承XCTestCase創(chuàng)建PersonTests.m烟馅,得到的是PersonTests.h和PersonTests.m兩個(gè)文件说庭,只需將PersonTests.h的聲明代碼遷移到PersonTests.m中即可刪除PersonTests.h文件。如果有需要可將系統(tǒng)自動(dòng)生成的函數(shù)- (void)setUp 和- (void)tearDown復(fù)制到PersonTests.m文件中郑趁。

image

5.2刊驴、測(cè)試沒(méi)有參數(shù)的函數(shù)

//沒(méi)有參數(shù)的方法- (void)testGetName{PersonModel *person = [[PersonModel alloc] init];//創(chuàng)建一個(gè)mock對(duì)象id mockClass = OCMClassMock([PersonModel class]);//可以給這個(gè)mock對(duì)象的方法設(shè)置預(yù)設(shè)的參數(shù)和返回值OCMStub([mockClass getPersonName]).andReturn(@"liyong");//用這個(gè)預(yù)設(shè)的值和實(shí)際的值進(jìn)行比較是否相等XCTAssertEqualObjects([mockClass getPersonName], [person getPersonName], @"值相等");}

5.3、測(cè)試有參數(shù)的函數(shù)

//有參數(shù)的方法- (void)testCahngeName{PersonModel *person = [[PersonModel alloc] init];id mockClass = OCMClassMock([PersonModel class]);//[OCMArg any]是指任意參數(shù),下面調(diào)用方法時(shí)傳的參數(shù)必須與此處的參數(shù)一樣才會(huì)返回設(shè)定的值OCMStub([mockClass changeName:[OCMArg any]]).andReturn(@"wss");//驗(yàn)證getPersonName方法有沒(méi)有被調(diào)用寡润,如果沒(méi)有調(diào)用則拋出異常//? ? OCMVerify([mockClass getPersonName]);XCTAssertEqualObjects([mockClass changeName:[OCMArg any]], [person changeName:@"wss"],@"值相等");}

5.4捆憎、測(cè)試有參數(shù)的函數(shù)調(diào)用時(shí)傳的參數(shù)

//檢查參數(shù)- (void)testArgument{id mockClass = OCMClassMock([PersonModel class]);//檢查參數(shù)OCMStub([mockClass changeName:[OCMArg checkWithBlock:^BOOL(id obj) {//判斷參數(shù)是否為NSString類(lèi)型if ([obj isKindOfClass:[NSString class]]){}else{//提示錯(cuò)誤//? ? ? ? ? ? XCTAssertFalse(obj);obj = @"456";}NSLog(@"-----------------%@",obj);return YES;}]]);[mockClass changeName:@"123"];[mockClass changeName:[OCMArg any]];}

6、單元測(cè)試-table

繼承XCTestCase創(chuàng)建TableTests.m文件

6.1梭纹、table數(shù)據(jù)源函數(shù)返回行數(shù)

//測(cè)試table數(shù)據(jù)源函數(shù)返回行數(shù)- (void)testControllerReturnsCorrectNumberOfRows{XCTAssertEqual(3, [self.VC tableView:self.VC.tableView numberOfRowsInSection:0],@"此處返回得到的行數(shù)錯(cuò)誤");}

6.2躲惰、table數(shù)據(jù)源函數(shù)返回cell

//測(cè)試table數(shù)據(jù)源函數(shù)返回cell- (void)testControllerSetsUpCellCorrectly{id mockTable = OCMClassMock([UITableView class]);[[[mockTable expect] andReturn:nil] dequeueReusableCellWithIdentifier:@"HappyNewYear"];NSIndexPath *indexPath = [NSIndexPath indexPathForRow:2 inSection:0];UITableViewCell *cell = [self.VC tableView:mockTable cellForRowAtIndexPath:indexPath];XCTAssertNotNil(cell, @"此處應(yīng)該返回一個(gè)cell");XCTAssertEqualObjects(@"x-2", cell.textLabel.text, @"返回的字符串錯(cuò)誤");[mockTable verify];}

7、UI測(cè)試

TestDemoUITests.m文件中寫(xiě)一個(gè)方法testLogin作為測(cè)試登錄流程操作的UI測(cè)試方法变抽。然后把光標(biāo)放在方法體內(nèi)础拨,然后點(diǎn)擊紅色的那個(gè)錄制按鈕,如下:

7.1绍载、測(cè)試登錄-普通點(diǎn)擊事件

image

下面這個(gè).gif可查看動(dòng)畫(huà)顯示操作步驟

image

生成代碼稍加修改

//檢測(cè)登錄- (void)testLogin{//首先從tabbars找到“登錄”然后點(diǎn)擊[[[XCUIApplication alloc] init].tabBars.buttons[@"登錄"] tap];//獲取appXCUIApplication *app = [[XCUIApplication alloc] init];//在當(dāng)前頁(yè)面尋找與“accountTF”有關(guān)系的輸入框诡宗,我測(cè)試時(shí)發(fā)現(xiàn)placeholder寫(xiě)為“accountTF”就可以尋找到XCUIElement *textField = app.textFields[@"accountTF"];[textField tap];//獲取焦點(diǎn)成為第一響應(yīng)者,否則會(huì)報(bào)“元素(此textField)未調(diào)起鍵盤(pán)”錯(cuò)誤[textField typeText:@"liyong"];//為此textField鍵入字符串XCUIElement *textField2 = app.textFields[@"passwordTF"];[textField2 tap];[textField2 typeText:@"123456"];for (int i = 0; i < 2; i ++) {//n次點(diǎn)擊登陸按鈕[app.buttons[@"login"] tap];//login標(biāo)示的button點(diǎn)擊}//如果頁(yè)面title為success則表示登錄成功击儡,也可用其他判斷方式XCTAssertEqualObjects(app.navigationBars.element.identifier, @"success");}

7.2塔沃、table下拉上拉

//列表下拉以及上拉測(cè)試- (void)testRefresh{//獲取appXCUIApplication *app = [[XCUIApplication alloc] init];//點(diǎn)擊tabbar中“列表”這個(gè)[app.tabBars.buttons[@"列表"] tap];//獲取當(dāng)前頁(yè)面的tabble(此頁(yè)面只有一個(gè)table,代碼自動(dòng)生成的)XCUIElement *table = [[[[[[[[[app childrenMatchingType:XCUIElementTypeWindow] elementBoundByIndex:0] childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeOther].element childrenMatchingType:XCUIElementTypeTable].element;//可通過(guò)循環(huán)上拉或者下拉無(wú)數(shù)次[table swipeDown];//下拉[table swipeUp];//上拉}

7.3阳谍、tablecell點(diǎn)擊以及返回

//tablecell點(diǎn)擊以及返回- (void)testCellClick{//獲取appXCUIApplication *app = [[XCUIApplication alloc] init];//點(diǎn)擊tabbar中“列表”這個(gè)[app.tabBars.buttons[@"列表"] tap];//在當(dāng)前頁(yè)面獲取table的cell隊(duì)列XCUIElementQuery *tablesQuery = app.tables;//點(diǎn)擊了第一個(gè)cell蛀柴,此cell有一個(gè)標(biāo)示為“x-x”[[[tablesQuery childrenMatchingType:XCUIElementTypeCell] elementBoundByIndex:0].staticTexts[@"x-x"] tap];//在“l(fā)ogin”為title的頁(yè)面中點(diǎn)擊了導(dǎo)航欄中“table”按鈕---login頁(yè)面為點(diǎn)擊cell進(jìn)入的頁(yè)面,table是導(dǎo)航欄左側(cè)按鈕矫夯,點(diǎn)擊返回列表頁(yè)面XCUIElement *tableButton = app.navigationBars[@"login"].buttons[@"table"];[tableButton tap];//點(diǎn)擊返回[[[tablesQuery childrenMatchingType:XCUIElementTypeCell] elementBoundByIndex:7].staticTexts[@"x-x"] tap];[tableButton tap];[[[tablesQuery childrenMatchingType:XCUIElementTypeCell] elementBoundByIndex:9].staticTexts[@"x-x"] tap];//點(diǎn)擊login頁(yè)面中“back”按鈕返回名扛,[app.buttons[@"back"] tap];}/***在執(zhí)行過(guò)程中如果只進(jìn)行多次通過(guò)點(diǎn)擊back來(lái)返回則可以使用*XCUIElement *backButton = app.buttons[@"back"];*后面直接用*[backButton tap];例如:XCUIElement *xXStaticText = [[app.tables childrenMatchingType:XCUIElementTypeCell] elementBoundByIndex:0].staticTexts[@"x-x"];[xXStaticText tap];XCUIElement *backButton = app.buttons[@"back"];[backButton tap];[xXStaticText tap];[backButton tap];[xXStaticText tap];[backButton tap];[xXStaticText tap];[backButton tap];[xXStaticText tap];[backButton tap];[xXStaticText tap];[backButton tap];*/

8、代碼覆蓋率

Code coverage 是一個(gè)計(jì)算你的單元測(cè)試覆蓋率的工具茧痒。高水平的覆蓋給你的單元測(cè)試帶來(lái)信心肮韧,也表明你的應(yīng)用被徹底的測(cè)試過(guò)了。你可能寫(xiě)了幾千個(gè)單元測(cè)試,但如果覆蓋率不高弄企,那么你寫(xiě)的這套測(cè)試可能價(jià)值也不大超燃。

在運(yùn)行測(cè)試之前,我們必須先確認(rèn) code coverage 是否被打開(kāi)了拘领,寫(xiě)代碼時(shí)意乓,默認(rèn)是關(guān)閉的。所以你需要編輯一下你的測(cè)試 scheme约素,把它打開(kāi)届良。確保"Gather coverage data"是被選中的,然后點(diǎn)擊關(guān)閉按鈕圣猎,運(yùn)行測(cè)試的 target. 我們希望剛剛創(chuàng)建的測(cè)試用例能夠順利通過(guò)士葫。

image

image

測(cè)試通過(guò)后,你就能知道 checkWord 這個(gè)方法送悔,至少有一條路徑是對(duì)的慢显。但你不知道的是,還多多少?zèng)]有被測(cè)試到欠啤。這就是code coverage這個(gè)工具的好處荚藻。當(dāng)你打開(kāi)code coverage tab后,你可以清楚的看到測(cè)試的覆蓋情況洁段。他們按找 target, file, function 進(jìn)行了自動(dòng)分組应狱。打開(kāi)Xcode左邊窗口的Report Navigator面板,選中你剛運(yùn)行的測(cè)試祠丝。然后在tab中選中 Coverage疾呻。這會(huì)展示一個(gè)你的類(lèi)、方法的列表纽疟,并展示每個(gè)的測(cè)試覆蓋情況。雙擊方法的名字憾赁,Xcode會(huì)打開(kāi)類(lèi)的代碼污朽,并且看到code coverage的情況。

image

鼠標(biāo)移動(dòng)到方法的代碼右側(cè)時(shí)會(huì)展示代碼的執(zhí)行次數(shù)龙考。

image

附件1:

單元測(cè)試準(zhǔn)則:

保持單元測(cè)試小巧, 快速

理論上, 任何代碼提交前都應(yīng)該完整跑一遍所有測(cè)試套件. 保持測(cè)試代碼執(zhí)行迅捷能夠縮短迭代開(kāi)發(fā)周期.

單元測(cè)試應(yīng)該是全自動(dòng)且無(wú)交互

測(cè)試套件通常是定期執(zhí)行的, 執(zhí)行過(guò)程必須完全自動(dòng)化才有意義. 需要人工檢查輸出結(jié)果的測(cè)試不是一個(gè)好的單元測(cè)試.

讓單元測(cè)試很容易跑起來(lái)

對(duì)開(kāi)發(fā)環(huán)境進(jìn)行配置, 最好是敲條命令或是點(diǎn)個(gè)按鈕就能把單個(gè)測(cè)試用例或測(cè)試套件跑起來(lái).

對(duì)測(cè)試進(jìn)行評(píng)估

對(duì)執(zhí)行的測(cè)試進(jìn)行覆蓋率分析, 得到精確的代碼執(zhí)行覆蓋率, 并調(diào)查哪些代碼未被執(zhí)行.

立即修正失敗的測(cè)試

每個(gè)開(kāi)發(fā)人員在提交前都應(yīng)該保證新的測(cè)試用例執(zhí)行成功, 當(dāng)有代碼提交時(shí), 現(xiàn)有測(cè)試用例也都能跑通.

如果一個(gè)定期執(zhí)行的測(cè)試用例執(zhí)行失敗, 整個(gè)團(tuán)隊(duì)?wèi)?yīng)該放下手上的工作優(yōu)先解決這個(gè)問(wèn)題.

把測(cè)試維持在單元級(jí)別

單元測(cè)試即類(lèi) (Class) 的測(cè)試. 一個(gè) "測(cè)試類(lèi)" 應(yīng)該只對(duì)應(yīng)于一個(gè) "被測(cè)類(lèi)", 并且 "被測(cè)類(lèi)" 的行為應(yīng)該被隔離測(cè)試. 必須謹(jǐn)慎避免使用單元測(cè)試框架來(lái)測(cè)試整個(gè)程序的工作流, 這樣的測(cè)試既低效又難維護(hù). 工作流測(cè)試 (譯注: 指跨模塊/類(lèi)的數(shù)據(jù)流測(cè)試) 有它自己的地盤(pán), 但它絕不是單元測(cè)試, 必須單獨(dú)建立和執(zhí)行.

由簡(jiǎn)入繁

最簡(jiǎn)單的測(cè)試也遠(yuǎn)遠(yuǎn)勝過(guò)完全沒(méi)有測(cè)試. 一個(gè)簡(jiǎn)單的 "測(cè)試類(lèi)" 會(huì)促使建立 "被測(cè)類(lèi)" 基本的測(cè)試骨架, 可以對(duì)構(gòu)建環(huán)境, 單元測(cè)試環(huán)境, 執(zhí)行環(huán)境以及覆蓋率分析工具等有效性進(jìn)行檢查, 同時(shí)也可以證明 "被測(cè)類(lèi)" 能夠被整合和調(diào)用.

下面便是單元測(cè)試版的 Hello, world! :

void testDefaultConstruction()

{

Foo foo = new Foo();

assertNotNull(foo);

}

保持測(cè)試的獨(dú)立性

為了保證測(cè)試穩(wěn)定可靠且便于維護(hù), 測(cè)試用例之間決不能有相互依賴(lài), 也不能依賴(lài)執(zhí)行的先后次序.

Keep tests close to the class being tested

[譯注: 有意翻譯該規(guī)則, 個(gè)人認(rèn)為本條規(guī)則值得商榷, 大部分 C++, Objective-C和 Python 庫(kù)均把測(cè)試代碼從功能代碼目錄中獨(dú)立出來(lái), 通常是創(chuàng)建一個(gè)和 src 目錄同級(jí)的 tests 目錄, 被測(cè)模塊/類(lèi)名之前也常常 不加 Test 前綴. 這么做保證功能代碼和測(cè)試代碼隔離, 目錄結(jié)構(gòu)清晰, 并且發(fā)布源碼的時(shí)候更容易排除測(cè)試用例.]

If the class to test is Foo the test class should be called FooTest (not TestFoo) and kept in the same package (directory) as Foo. Keeping test classes in separate directory trees makes them harder to access and maintain.

Make sure the build environment is configured so that the test classes doesn't make its way into production libraries or executables.

合理的命名測(cè)試用例

確保每個(gè)方法只測(cè)試 "被測(cè)類(lèi)" 的一個(gè)明確特性, 并相應(yīng)的命名測(cè)試方法. 典型的命名俗定是 test[what], 比如 testSaveAs(), testAddListener(), testDeleteProperty() 等.

只測(cè)公有接口

單元測(cè)試可以被定義為 通過(guò)類(lèi)的公有 API 對(duì)類(lèi)進(jìn)行測(cè)試. 一些測(cè)試工具允許測(cè)試一個(gè)類(lèi)的私有成員, 但這種做法應(yīng)該避免, 它讓測(cè)試變得繁瑣而且更難維護(hù). 如果有私有成員確實(shí)需要進(jìn)行直接測(cè)試, 可以考慮把它重構(gòu)到工具類(lèi)的公有方法中. 但要注意這么做是為了改善設(shè)計(jì), 而不是幫助測(cè)試.

看成是黑盒

站在第三方使用者的角度, 測(cè)試一個(gè)類(lèi)是否滿(mǎn)足規(guī)定的需求. 并設(shè)法讓它出問(wèn)題.

看成是白盒

畢竟被測(cè)試類(lèi)是程序員自寫(xiě)自測(cè)的, 應(yīng)該在最復(fù)雜的邏輯部分多花些精力測(cè)試.

芝麻函數(shù)也要測(cè)試

通常建議所有重要的函數(shù)都應(yīng)該被測(cè)試到, 一些芝麻方法比如簡(jiǎn)單的 setter 和 getter 都可以忽略. 但是仍然有充分的理由支持測(cè)試芝麻函數(shù):

芝麻 很難定義. 對(duì)于不同的人有不同的理解.

從黑盒測(cè)試的觀(guān)點(diǎn)看, 是無(wú)法知道哪些代碼是芝麻級(jí)別的.

即便是再芝麻的函數(shù), 也可能包含錯(cuò)誤, 通常是 "復(fù)制粘貼" 代碼的后果:

private double weight_;

private double x_, y_;

public void setWeight(int weight)

{

weight = weight_;? // error

}

public double getX()

{

return x_;

}

public double getY()

{

return x_;? // error

}

因此建議測(cè)試所有方法. 畢竟芝麻用例也容易測(cè)試.

先關(guān)注執(zhí)行覆蓋率

區(qū)別對(duì)待 執(zhí)行覆蓋率 和 實(shí)際測(cè)試覆蓋率. 測(cè)試的最初目標(biāo)應(yīng)該是確保較高的執(zhí)行覆蓋率. 這樣能保證代碼在 少量 參數(shù)值輸入時(shí)能執(zhí)行成功. 一旦執(zhí)行覆蓋率就緒, 就應(yīng)該開(kāi)始改進(jìn)測(cè)試覆蓋率了. 注意, 實(shí)際的測(cè)試覆蓋率很難衡量 (而且往往趨近于 0%).

思考以下公有方法:

void setLength(double length);

調(diào)用 setLength(1.0) 你可能會(huì)得到 100% 的執(zhí)行覆蓋率. 但要達(dá)到 100% 的實(shí)際測(cè)試覆蓋率, 有多少個(gè) double 浮點(diǎn)數(shù)這個(gè)方法就必須被調(diào)用多少次, 并且要一一驗(yàn)證行為的正確性. 這無(wú)疑是不可能的任務(wù).

覆蓋邊界值

確保參數(shù)邊界值均被覆蓋. 對(duì)于數(shù)字, 測(cè)試負(fù)數(shù), 0, 正數(shù), 最小值, 最大值, NaN (非數(shù)字), 無(wú)窮大等. 對(duì)于字符串, 測(cè)試空字符串, 單字符, 非 ASCII 字符串, 多字節(jié)字符串等. 對(duì)于集合類(lèi)型, 測(cè)試空, 1, 第一個(gè), 最后一個(gè)等. 對(duì)于日期, 測(cè)試 1月1號(hào), 2月29號(hào), 12月31號(hào)等. 被測(cè)試的類(lèi)本身也會(huì)暗示一些特定情況下的邊界值. 要點(diǎn)是盡可能徹底的測(cè)試這些邊界值, 因?yàn)樗鼈兌际侵饕?"疑犯".

提供一個(gè)隨機(jī)值生成器

當(dāng)邊界值都覆蓋了, 另一個(gè)能進(jìn)一步改善測(cè)試覆蓋率的簡(jiǎn)單方法就是生成隨機(jī)參數(shù), 這樣每次執(zhí)行測(cè)試都會(huì)有不同的輸入.

想要做到這點(diǎn), 需要提供一個(gè)用來(lái)生成基本類(lèi)型 (如: 浮點(diǎn)數(shù), 整型, 字符串, 日期等) 隨機(jī)值的工具類(lèi). 生成器應(yīng)該覆蓋各種類(lèi)型的所有取值范圍.

如果測(cè)試時(shí)間比較短, 可以考慮再裹上一層循環(huán), 覆蓋盡可能多的輸入組合. 下面的例子是驗(yàn)證兩次轉(zhuǎn)換 little endian 和 big endian 字節(jié)序后是否返回原值. 由于測(cè)試過(guò)程很快, 可以讓它跑上個(gè)一百萬(wàn)次.

void testByteSwapper()

{

for (int i = 0; i < 1000000; i++) {

double v0 = Random.getDouble();

double v1 = ByteSwapper.swap(v0);

double v2 = ByteSwapper.swap(v1);

assertEquals(v0, v2);

}

}

每個(gè)特性只測(cè)一次

在測(cè)試模式下, 有時(shí)會(huì)情不自禁的濫用斷言. 這種做法會(huì)導(dǎo)致維護(hù)更困難, 需要極力避免. 僅對(duì)測(cè)試方法名指示的特性進(jìn)行明確測(cè)試.

因?yàn)閷?duì)于一般性代碼而言, 保證測(cè)試代碼盡可能少是一個(gè)重要目標(biāo).

使用顯式斷言

應(yīng)該總是優(yōu)先使用 assertEquals(a, b) 而不是 assertTrue(a == b), 因?yàn)榍罢邥?huì)給出更有意義的測(cè)試失敗信息. 在事先不確定輸入值的情況下, 這條規(guī)則尤為重要, 比如之前使用隨機(jī)參數(shù)值組合的例子.

提供反向測(cè)試

反向測(cè)試是指刻意編寫(xiě)問(wèn)題代碼, 來(lái)驗(yàn)證魯棒性和能否正確的處理錯(cuò)誤.

假設(shè)如下方法的參數(shù)如果傳進(jìn)去的是負(fù)數(shù), 會(huì)立馬拋出異常:

void setLength(double length) throws IllegalArgumentExcepti

可以用下面的方法來(lái)測(cè)試這個(gè)特例是否被正確處理:

try {

setLength(-1.0);

fail();? // If we get here, something went wrong

}

catch (IllegalArgumentException exception) {

// If we get here, all is fine

}

代碼設(shè)計(jì)時(shí)謹(jǐn)記測(cè)試

編寫(xiě)和維護(hù)單元測(cè)試的代價(jià)是很高的, 減少代碼中的公有接口和循環(huán)復(fù)雜度是降低成本, 使高覆蓋率測(cè)試代碼更易于編寫(xiě)和維護(hù)的有效方法.

一些建議:

使類(lèi)成員常量化, 在構(gòu)造函數(shù)中進(jìn)行初始化. 減少 setter 方法的數(shù)量.

限制過(guò)度使用繼承和公有虛函數(shù).

通過(guò)使用友元類(lèi) (C++) 或包作用域 (Java) 來(lái)減少公有接口.

避免不必要的邏輯分支.

在邏輯分支中編寫(xiě)盡可能少的代碼.

在公有和私有接口中盡量多用異常和斷言驗(yàn)證參數(shù)參數(shù)的有效性.

限制使用快捷函數(shù). 對(duì)于黑箱而言, 所有方法都必須一視同仁的進(jìn)行測(cè)試. 思考以下簡(jiǎn)短的例子:

public void scale(double x0, double y0, double scaleFactor)

{

// scaling logic

}

public void scale(double x0, double y0)

{

scale(x0, y0, 1.0);

}

刪除后者可以簡(jiǎn)化測(cè)試, 但用戶(hù)代碼的工作量也將略微增加.

不要訪(fǎng)問(wèn)預(yù)設(shè)的外部資源

單元測(cè)試代碼不應(yīng)該假定外部的執(zhí)行環(huán)境, 以便在任何時(shí)候/任何地方都能執(zhí)行. 為了向測(cè)試提供必需的資源, 這些資源應(yīng)該由測(cè)試本身提供.

比如一個(gè)解析某類(lèi)型文件的類(lèi), 可以把文件內(nèi)容嵌入到測(cè)試代碼里, 在測(cè)試的時(shí)候?qū)懭氲脚R時(shí)文件, 測(cè)試結(jié)束再刪除, 而不是從預(yù)定的地址直接讀取.

權(quán)衡測(cè)試成本

不寫(xiě)單元測(cè)試的代價(jià)很高, 但是寫(xiě)單元測(cè)試的代價(jià)同樣很高. 要在這兩者之間做適當(dāng)?shù)臋?quán)衡, 如果用執(zhí)行覆蓋率來(lái)衡量, 業(yè)界標(biāo)準(zhǔn)通常在 80% 左右.

很典型的, 讀寫(xiě)外部資源的錯(cuò)誤處理和異常處理就很難達(dá)到百分百的執(zhí)行覆蓋率. 模擬數(shù)據(jù)庫(kù)在事務(wù)處理到一半時(shí)發(fā)生故障并不是辦不到, 但相對(duì)于進(jìn)行大范圍的代碼審查, 代價(jià)可能太大了.

安排測(cè)試優(yōu)先次序

單元測(cè)試是典型的自底向上過(guò)程, 如果沒(méi)有足夠的資源測(cè)試一個(gè)系統(tǒng)的所有模塊, 就應(yīng)該先把重點(diǎn)放在較底層的模塊.

測(cè)試代碼要考慮錯(cuò)誤處理

考慮下面的這個(gè)例子:

Handle handle = manager.getHandle();

assertNotNull(handle);

String handleName = handle.getName();

assertEquals(handleName, "handle-01");

如果第一個(gè)斷言失敗, 后續(xù)語(yǔ)句會(huì)導(dǎo)致代碼崩潰, 剩下的測(cè)試都無(wú)法執(zhí)行. 任何時(shí)候都要為測(cè)試失敗做好準(zhǔn)備, 避免單個(gè)失敗的測(cè)試項(xiàng)中斷整個(gè)測(cè)試套件的執(zhí)行. 上面的例子可以重寫(xiě)成:

Handle handle = manager.getHandle();

assertNotNull(handle);

if (handle == null) return;

String handleName = handle.getName();

assertEquals(handleName, "handle-01");

寫(xiě)測(cè)試用例重現(xiàn) bug

每上報(bào)一個(gè) bug, 都要寫(xiě)一個(gè)測(cè)試用例來(lái)重現(xiàn)這個(gè) bug (即無(wú)法通過(guò)測(cè)試), 并用它作為成功修正代碼的檢驗(yàn)標(biāo)準(zhǔn).

了解局限

單元測(cè)試永遠(yuǎn)無(wú)法證明代碼的正確性!!

一個(gè)跑失敗的測(cè)試可能表明代碼有錯(cuò)誤, 但一個(gè)跑成功的測(cè)試什么也證明不了.

單元測(cè)試最有效的使用場(chǎng)合是在一個(gè)較低的層級(jí)驗(yàn)證并文檔化需求, 以及 回歸測(cè)試: 開(kāi)發(fā)或重構(gòu)代碼時(shí)蟆肆,不會(huì)破壞已有功能的正確性.

附件2:

XCTest測(cè)試-名詞解釋

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

XCTAssertNil(a1, format...)為空判斷晦款,a1為空時(shí)通過(guò)炎功,反之不通過(guò);

XCTAssertNotNil(a1, format…)不為空判斷缓溅,a1不為空時(shí)通過(guò)蛇损,反之不通過(guò);

XCTAssert(expression, format...)當(dāng)expression求值為T(mén)RUE時(shí)通過(guò);

XCTAssertTrue(expression, format...)當(dāng)expression求值為T(mén)RUE時(shí)通過(guò)淤齐;

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

XCTAssertEqualObjects(a1, a2, format...)判斷相等,[a1 isEqual:a2]值為T(mén)RUE時(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í)使用, 判斷的是變量的地址义锥,如果地址相同則返回TRUE柳沙,否則返回NO);

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類(lèi)型)提供一個(gè)誤差范圍贰拿,當(dāng)在誤差范圍(+/-accuracy)以?xún)?nèi)相等時(shí)通過(guò)測(cè)試蛤袒;

XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判斷不等,(double或float類(lèi)型)提供一個(gè)誤差范圍膨更,當(dāng)在誤差范圍以?xún)?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ā)生具體異常泵琳、具體異常名稱(chēng)的異常時(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ā)生具體異常击孩、具體異常名稱(chēng)的異常時(shí)通過(guò)測(cè)試,反之不通過(guò)鹏漆;

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末且改,一起剝皮案震驚了整個(gè)濱河市验烧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌又跛,老刑警劉巖碍拆,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異慨蓝,居然都是意外死亡感混,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)礼烈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)弧满,“玉大人,你說(shuō)我怎么就攤上這事此熬⊥ノ兀” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵犀忱,是天一觀(guān)的道長(zhǎng)募谎。 經(jīng)常有香客問(wèn)我,道長(zhǎng)阴汇,這世上最難降的妖魔是什么数冬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮搀庶,結(jié)果婚禮上拐纱,老公的妹妹穿的比我還像新娘。我一直安慰自己哥倔,他們只是感情好秸架,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著咆蒿,像睡著了一般东抹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜡秽,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天府阀,我揣著相機(jī)與錄音缆镣,去河邊找鬼芽突。 笑死,一個(gè)胖子當(dāng)著我的面吹牛董瞻,可吹牛的內(nèi)容都是我干的寞蚌。 我是一名探鬼主播田巴,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挟秤!你這毒婦竟也來(lái)了壹哺?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤艘刚,失蹤者是張志新(化名)和其女友劉穎管宵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體攀甚,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箩朴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秋度。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炸庞。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖荚斯,靈堂內(nèi)的尸體忽然破棺而出埠居,到底是詐尸還是另有隱情,我是刑警寧澤事期,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布滥壕,位于F島的核電站,受9級(jí)特大地震影響刑赶,放射性物質(zhì)發(fā)生泄漏捏浊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一撞叨、第九天 我趴在偏房一處隱蔽的房頂上張望金踪。 院中可真熱鬧,春花似錦牵敷、人聲如沸胡岔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)靶瘸。三九已至,卻和暖如春毛肋,著一層夾襖步出監(jiān)牢的瞬間怨咪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工润匙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诗眨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓孕讳,卻偏偏與公主長(zhǎng)得像匠楚,于是被迫代替她去往敵國(guó)和親巍膘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354