iOS 自動化測試的那些干貨

前言

如果有測試大佬發(fā)現(xiàn)內(nèi)容不對,歡迎指正,我會及時修改。

大多數(shù)的iOS App(沒有持續(xù)集成)迭代流程是這樣的

也就是說,測試是發(fā)布之前的最后一道關(guān)卡。如果bug不能在測試中發(fā)現(xiàn),那么bug

就會抵達用戶,所以測試的完整性和可靠性十分重要。

目前,大多數(shù)App還停留在人工測試階段,人工測試投入的成本最低,能夠保證核心功能的使用,而且測試人員不需要會寫代碼。

但是,在很多測試場景下,人工測試的效率太低,容易出錯。舉兩個常見的例子:

一個App的核心功能,在每一次發(fā)布版本前的測試必定會跑一遍所有的測試用例,不管對應的業(yè)務在當前版本有沒有變化(天知道開發(fā)在做業(yè)務A的時候,對業(yè)務B有沒有影響),如果這次測出新的bug,測試人員在下一次發(fā)版測試中,又不得不做這些重復的工作。

開發(fā)在寫API請求相關(guān)代碼的時候沒有做數(shù)據(jù)容錯,測試在人工測試的時候都是正常的數(shù)據(jù),所以測試通過。上線了之后,后臺配置數(shù)據(jù)的時候出了點小問題,導致大面積崩潰,boom~。

然后,老板就要過來找你了

本文所講解的均是基于XCode 8.2.1,有些概念可能不適用于低版本的XCode

自動化測試

自動化測試就是寫一些測試代碼,用代碼代替人工去完成模塊和業(yè)務的測試。

其實不管是開發(fā)還是測試,如果你在不斷的做重復性工作的時候,就應該問自己一個問題:是不是有更高效的辦法?

自動化測試有很多優(yōu)點:

測試速度快,避免重復性的工作

避免regression,讓開發(fā)更有信心去修改和重構(gòu)代碼(個人認為最大的優(yōu)點)

具有一致性。

有了自動化測試,持續(xù)集成(CI)會變得更可靠。

迫使開發(fā)人員寫出更高質(zhì)量的代碼。(自動化測試不通過,代碼不允許合并)

當然,自動化測試也有一些缺點。

開發(fā)和維護成本高。

不能完全替代人工測試。

無法完全保證測試的準確性 – 讓代碼去判斷一段邏輯是否正確很容易,但是,讓代碼判斷一個控件顯示是否正確卻沒那么容易。

所以,在做自動化測試之前,首先要問自己幾個問題?

這個測試業(yè)務的變動是否頻繁?

這個測試業(yè)務是否屬于核心功能?

編寫測試代碼的成本有多少?

自動化測試能保證測試結(jié)果的準確么?

通常,我們會選擇那些業(yè)務穩(wěn)定,需要頻繁測試的部分來編寫自動化測試腳本,其余的采用人工測試,人工測試仍然是iOS App開發(fā)中不可缺少的一部分。

測試種類

從是否接觸源代碼的角度來分類:測試分為黑盒和白盒(灰盒就是黑盒白盒結(jié)合,這里不做討論)。

白盒測試的時候,測試人員是可以直接接觸待測試App的源代碼的。白盒測試更多的是單元測試,測試人員針對各個單元進行各種可能的輸入分析,然后測試其輸出。白盒測試的測試代碼通常由iOS開發(fā)編寫。

黑盒測試。黑盒測試的時候,測試人員不需要接觸源代碼。是從App層面對其行為以及UI的正確性進行驗證,黑盒測試由iOS測試完成。

從業(yè)務的層次上來說,測試金字塔如圖:

而iOS測試通常只有以下兩個層次:

Unit,單元測試,保證每一個類能夠正常工作

UI,UI測試,也叫做集成測試,從業(yè)務層的角度保證各個業(yè)務可以正常工作。

框架選擇

啰里八嗦講的這么多,自動化測試的效率怎么樣,關(guān)鍵還是在測試框架上。那么,如何選擇測試框架呢?框架可以分為兩大類:XCode內(nèi)置的和三方庫。

選擇框架的時候有幾個方面要考慮

測試代碼編寫的成本

是否可調(diào)式

框架的穩(wěn)定性

測試報告(截圖,代碼覆蓋率,…)

WebView的支持(很多App都用到了H5)

自定義控件的測試

是否需要源代碼

能否需要連著電腦

是否支持CI(持續(xù)集成)

….

我們首先來看看XCode內(nèi)置的框架:XCTest。XCTest又可以分為兩部分:Unit

Test 和 UI

Test,分別對應單元測試和UI測試。有一些三方的測試庫也是基于XCTest框架的,這個在后文會講到。由于是Apple官方提供的,所以這個框架會不斷完善。

成熟的三方框架通常提供了很多封裝好的有好的接口,筆者綜合對比了一些,推薦以下框架:

單元測試:

以下三個框架都是BDD(Behavior-driven

development) – 行為驅(qū)動開發(fā)。行為驅(qū)動開發(fā)簡單來說就是先定義行為,然后定義測試用例,接著再編寫代碼。

實踐中發(fā)現(xiàn),通常沒有那么多時間來先定義行為,不過BDD中的domain-specific language (DSL)能夠很好的描述用例的行為。

Kiwi 老牌測試框架

specta 另一個BDD優(yōu)秀框架

Quick 三個項目中Star最多,支持OC和Swift,優(yōu)先推薦。

UI測試

KIF 基于XCTest的測試框架,調(diào)用私有API來控制UI,測試用例用Objective C或Swift編寫。

appium 基于Client – Server的測試框架。App相當于一個Server,測試代碼相當于Client,通過發(fā)送JSON來操作APP,測試語言可以是任意的,支持android和iOS。

篇幅有限,本文會先介紹XCtest,接著三方的Unit框架會以Quick為例,UI Test框架側(cè)重分析KIF,appium僅僅做原理講解。

XCTest

對于XCTest來說,最后生成的是一個bundle。bundle是不能直接執(zhí)行的,必須依賴于一個宿主進程。關(guān)于XCTest進行單元測試的基礎(chǔ)(XCode的使用,異步測試,性能測試,代碼覆蓋率等),我在這篇文章里講解過,這里不再詳細講解。

iOS 單元測試之XCTest詳解

blog.csdn.net/hello_hwc/article/details/46671053

單元測試用例

比如,我有以下一個函數(shù):

//驗證一段Text是否有效。(不能以空字符開頭,不能為空)

-(BOOL)validText:(NSString *)texterror:(NSError *__autoreleasing *)error{

}

那么,我該如何為這個函數(shù)編寫單元測試的代碼?通常,需要考慮以下用例:

輸入以空白字符或者換行符開頭的,error不為空,返回 NO

輸入正確的內(nèi)容,error為空,返回YES

輸入為nil,error不為空,返回 NO (邊界條件)

輸入為非NSString類型,驗證不通過,返回NO (錯誤輸入)

特殊輸入字符(標點符號,非英文等等)

UI測試

UI測試是模擬用戶操作,進而從業(yè)務處層面測試。關(guān)于XCTest的UI測試,建議看看WWDC 2015的這個視頻:

UI Testing in Xcode

https://developer.apple.com/videos/play/wwdc2015/406/

關(guān)于UI測試,有幾個核心類需要掌握

XCUIApplication 測試應用的代理

XCUIElement 一個UI上可見的視圖對象

XCUIElementQuery 查找XCUIElement

UI測試還有一個核心功能是UI Recording。選中一個UI測試用例,然后點擊圖中的小紅點既可以開始UI Recoding。你會發(fā)現(xiàn):

隨著點擊模擬器,自動合成了測試代碼。(通常自動合成代碼后,還需要手動的去調(diào)整)

在寫UI測試用例的時候要注意:測試行為而不是測試代碼。比如,我們測試這樣一個case

進入Todo首頁,點擊add,進入添加頁面,輸入文字,點擊save。

測試效果如下:


對應測試代碼:

-(void)testAddNewItems{

//獲取app代理

XCUIApplication *app=[[XCUIApplicationalloc]init];

//找到第一個tabeview,就是我們想要的tableview

XCUIElement *table=[app.tableselementBoundByIndex:0];

//記錄下來添加之前的數(shù)量

NSIntegeroldCount=table.cells.count;

//點擊Add

[app.navigationBars[@"ToDo"].buttons[@"Add"]tap];

//找到Textfield

XCUIElement *inputWhatYouWantTodoTextField=app.textFields[@"Input what you want todo"];

//點擊Textfield

[inputWhatYouWantTodoTextFieldtap];

//輸入字符

[inputWhatYouWantTodoTextFieldtypeText:@"somethingtodo"];

//點擊保存

[app.navigationBars[@"Add"].buttons[@"Save"]tap];

//獲取當前的數(shù)量

NSIntegernewCount=table.cells.count;

//如果cells的數(shù)量加一,則認為測試成功

XCTAssert(newCount==oldCount+1);

}

這里是通過前后tableview的row數(shù)量來斷言成功或者失敗。

等待

通常,在視圖切換的時候有轉(zhuǎn)場動畫,我們需要等待動畫結(jié)束,然后才能繼續(xù),否則query的時候很可能找不到我們想要的控件。

比如,如下代碼等待VC轉(zhuǎn)場結(jié)束,當query只有一個table的時候,才繼續(xù)執(zhí)行后續(xù)的代碼。

[selfexpectationForPredicate:[NSPredicatepredicateWithFormat:@"self.count = 1"]

evaluatedWithObject:app.tables

handler:nil];

[selfwaitForExpectationsWithTimeout:2.0handler:nil];

//后續(xù)代碼....

Tips: 當你的UI結(jié)構(gòu)比較復雜的時候,比如各種嵌套childViewController,使用XCUIElementQuery的代碼會很長,也不好維護。

另外,UI測試還會在每一步操作的時候截圖,方便對測試報告進行驗證。

查看測試結(jié)果

使用基于XCTest的框架,可以在XCode的report navigator中查看測試結(jié)果。

其中:

Tests 用來查看詳細的測試過程

Coverage 用來查看代碼覆蓋率

Logs 用來查看測試的日志

點擊圖中的紅色框指向的圖標可以看到每一步UI操作的截圖

除了利用XCode的GUI,還可以通過后文提到的命令行工具來測試,查看結(jié)果。

Stub/Mock

首先解釋兩個術(shù)語:

mock 表示一個模擬對象

stub 追蹤方法的調(diào)用,在方法調(diào)用的時候返回指定的值。

通常,如果你采用純存的XCTest,推薦采用OCMock來實現(xiàn)mock和stub,單元測試的三方庫通常已集成了stub和mock。

那么,如何使用mock呢?舉個官方的例子:

//mock一個NSUserDefaults對象

iduserDefaultsMock=OCMClassMock([NSUserDefaultsclass]);

//在調(diào)用stringForKey的時候,返回http://testurl

OCMStub([userDefaultsMock

stringForKey:@"MyAppURLKey"]).andReturn(@"http://testurl");

再比如,我們要測試打開其他App,那么如何判斷確實打開了其他App呢?

idapp=OCMClassMock([UIApplicationclass]);

OCMStub([appsharedInstance]).andReturn(app);

OCMVerify([appopenURL:url]

使用Stub可以讓我們很方便的實現(xiàn)這個。

關(guān)于OCMock的使用,推薦看看objc.io的這篇文章

置換測試: Mock, Stub 和其他

https://objccn.io/issue-15-5/

Quick

Quick是建立在XCTestSuite上的框架,使用XCTestSuite允許你動態(tài)創(chuàng)建測試用例。所以,使用Quick,你仍讓可以使用XCode的測試相關(guān)GUI和命令行工具。

使用Quick編寫的測試用例看起來是這樣子的:

import Quick

import Nimble

classTableOfContentsSpec:QuickSpec{

override func spec(){

describe("the 'Documentation' directory"){

it("has everything you need to get started"){

letsections=Directory("Documentation").sections

expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups"))

expect(sections).to(contain("Installing Quick"))

}

context("if it doesn't have what you're looking for"){

it("needs to be updated"){

letyou=You(awesome:true)

expect{you.submittedAnIssue}.toEventually(beTruthy())

}

}

}

}

}

BDD的框架讓測試用例的目的更加明確,測試是否通過更加清晰。使用Quick,測試用例分為兩種:

單獨的用例 – 使用it來描述

it有兩個參數(shù),

行為描述

行為的測試代碼

比如,以下測試Dolphin行為,它具有行為is friendly和is smart

//Swift代碼

classDolphinSpec:QuickSpec{

override func spec(){

it("is friendly"){

expect(Dolphin().isFriendly).to(beTruthy())

}

it("is smart"){

expect(Dolphin().isSmart).to(beTruthy())

}

}

}

可以看到,BDD的核心是行為。也就是說,需要關(guān)注的是一個類提供哪些行為。

用例集合,用describe和context描述

比如,驗證dolphin的click行為的時候,我們需要兩個用例。一個是is loud,一個是has a high frequency,就可以用describe將用例組織起來。

classDolphinSpec:QuickSpec{

override func spec(){

describe("a dolphin"){

describe("its click"){

it("is loud"){

letclick=Dolphin().click()

expect(click.isLoud).to(beTruthy())

}

it("has a high frequency"){

letclick=Dolphin().click()

expect(click.hasHighFrequency).to(beTruthy())

}

}

}

}

}

context可以指定用例的條件:

比如

describe("its click"){

context("when the dolphin is not near anything interesting"){

it("is only emitted once"){

expect(dolphin!.click().count).to(equal(1))

}

}

}

除了這些之外,Quick也支持一些切入點,進行測試前的配置:

beforeEach

afterEach

beforeAll

afterAll

beforeSuite

afterSuite

Nimble

由于Quick是基于XCTest,開發(fā)者當然可以收使用斷言來定義測試用例成功或者失敗。Quick提供了一個更有好的Framework來進行這種斷言:https://github.com/Quick/Nimble

比如,一個常見的XCTest斷言如下:

XCTAssertTrue(ConditionCode,"FailReason")

在出錯的時候,會提示

XCAssertTrue failed, balabala

這時候,開發(fā)者要打個斷點,查看下上下文,看看具體失敗的原因在哪。

使用Nimble后,斷言變成類似

expect(1+1).to(equal(2))

expect(3)>2

expect("seahorse").to(contain("sea"))

expect(["Atlantic","Pacific"]).toNot(contain("Mississippi"))

并且,出錯的時候,提示信息會帶著上下文的值信息,讓開發(fā)者更容易的找到錯誤。

讓你的代碼更容易單元測試

測試的準確性和工作量很大程度上依賴于開發(fā)人員的代碼質(zhì)量。

通常,為了單元測試的準確性,我們在寫函數(shù)(方法)的時候會借鑒一些函數(shù)式編程的思想。其中最重要的一個思想就是

pure function(純函數(shù))

何為Pure function?就是如果一個函數(shù)的輸入一樣,那么輸出一定一樣。

比如,這樣的一個函數(shù)就不是pure function。因為它依賴于外部變量value的值。

staticNSIntegervalue=0;

-(NSInteger)function_1{

value=value+1;

returnvalue;

}

而這個函數(shù)就是pure function,因為給定輸入,輸出一定一致。

-(NSInteger)function_2:(NSInteger)base{

NSIntegervalue=base+1;

returnvalue;

}

所以,如果你寫了一個沒有參數(shù),或者沒有返回值的方法,那么你要小心了,很可能這個方法很難測試。

關(guān)于MVC

在良好的MVC架構(gòu)的App中,

View只做純粹的展示型工作,把用戶交互通過各種方式傳遞到外部

Model只做數(shù)據(jù)存儲類工作

Controller作為View和Model的樞紐,往往要和很多View和Model進行交互,也是自動化包括代碼維護的痛點。

所以,對Controller瘦身是iOS架構(gòu)中比較重要的一環(huán),一些通用的技巧包括:

邏輯抽離:

網(wǎng)絡請求獨立。可以每個網(wǎng)絡請求以Command模式封裝成一個對象,不要直接在Controller調(diào)用AFNetworking。

數(shù)據(jù)存儲獨立。建立獨立的Store類,用來做數(shù)據(jù)持久化和緩存。

共有數(shù)據(jù)服務化(協(xié)議)。比如登錄狀態(tài)等等,通過服務去訪問,這樣服務提供者之需要處理服務的質(zhì)量,服務使用者則信任服務提供者的結(jié)果。

Controller與View解耦合

建立ViewModel層,這樣Controller只需要和ViewModel進行交互。

建立UIView子類作為容器,將一些View放到容器后再把容器作為SubView添加到Controller里

建立可復用的Layout層,不管是AutoLayout還是手動布局。

Controller與Controller解耦合

建立頁面路由。每一個界面都抽象為一個URL,跳轉(zhuǎn)僅僅通過Intent或者URL跳轉(zhuǎn),這樣兩個Controller完全獨立。

如果你的App用Swift開發(fā),那么面向協(xié)議編程和不可變的值類型會讓你的代碼更容易測試。

當然,iOS組建化對自動化測試的幫助也很大,因為不管是基礎(chǔ)組件還是業(yè)務組件,都可以獨立測試。組建化又是一個很大的課題,這里不深入講解了。

KIF

KIF的全稱是Keep

it

functional。它是一個建立在XCTest的UI測試框架,通過accessibility來定位具體的控件,再利用私有的API來操作UI。由于是建立在XCTest上的,所以你可以完美的借助XCode的測試相關(guān)工具(包括命令行腳本)。

> KIF是個人非常推薦的一個框架,簡單易用。

使用KIF框架強制要求你的代碼支持accessibility。如果你之前沒接觸過,可以看看Apple的文檔

Accessibility Programming Guide for iOS

https://developer.apple.com/library/prerelease/content/documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html

簡單來說,accessibility能夠讓視覺障礙人士使用你的App。每一個控件都有一個描述AccessibilityLabel。在開啟VoiceOver的時候,點擊控件就可以選中并且聽到對應的描述。

通常UIKit的控件是支持accessibility的,自定定義控件可以通過代碼或者Storyboard上設置。

在Storyboard上設置:

上面的通過Runtime Attributes設置(KVC)

下面的通過GUI來設置

通過代碼設置:

[alertsetAccessibilityLabel:@"Label"];

[alertsetAccessibilityValue:@"Value"];

[alertsetAccessibilityTraits:UIAccessibilityTraitButton];

如果你有些Accessibility的經(jīng)驗,那么你肯定知道,像TableView的這種不應該支持VoiceOver的。我們可以用條件編譯來只對測試Target進行設置:

#ifdef DEBUG

[tableViewsetAccessibilityValue:@"Main List Table"];

#endif

#ifdef KIF_TARGET (這個值需要在build settings里設置)

[tableViewsetAccessibilityValue:@"Main List Table"];

#endif

使用KIF主要有兩個核心類:

KIFTestCase XCTestCase的子類

KIFUITestActor 控制UI,常見的三種是:點擊一個View,向一個View輸入內(nèi)容,等待一個View的出現(xiàn)

我們用KIF來測試添加一個新的ToDo

-(void)testAddANewItem{

[testertapViewWithAccessibilityLabel:@"Add"];

[testerenterText:@"Create a test to do item"intoViewWithAccessibilityLabel:@"Input what you want todo"];

[testertapViewWithAccessibilityLabel:@"Save"];

[testerwaitForTimeInterval:0.2];

[testerwaitForViewWithAccessibilityLabel:@"Create a test to do item"];

}

命令行

自動化測試中,命令行工具可以facebook的開源項目:

xctool

這是一個基于xcodebuild命令的擴展,在iOS自動化測試和持續(xù)集成領(lǐng)域很有用,而且它支持-parallelize并行測試多個bundle,大大提高測試效率。

安裝XCTool,

brew installxctool

使用

path/to/xctool.sh

-workspaceYourWorkspace.xcworkspace

-schemeYourScheme

-reporterplain:/path/to/plain-output.txt

run-test

并且,xctool對于持續(xù)集成很有用,iOS常用的持續(xù)集成的server有兩個:

Travis CI 對于公開倉庫(比如github)免費,私有倉庫收費

Jenkins 免費

優(yōu)化你的測試代碼

準確的測試用例

通常,你的你的測試用例分為三部分:

配置測試的初始狀態(tài)

對要測試的目標執(zhí)行代碼

對測試結(jié)果進行斷言(成功 or 失敗)

測試代碼結(jié)構(gòu)

當測試用例多了,你會發(fā)現(xiàn)測試代碼編寫和維護也是一個技術(shù)活。通常,我們會從幾個角度考慮:

不要測試私有方法(封裝是OOP的核心思想之一,不要為了測試破壞封裝)

對用例分組(功能,業(yè)務相似)

對單個用例保證測試獨立(不受之前測試的影響,不影響之后的測試),這也是測試是否準確的核心。

提取公共的代碼和操作,減少copy/paste這類工作,測試用例是上層調(diào)用,只關(guān)心業(yè)務邏輯,不關(guān)心內(nèi)部代碼實現(xiàn)。

一個常見的測試代碼組織如下:

appium

appium采用了Client

Server的模式。對于App來說就是一個Server,基于WebDriver JSON wire

protocol對實際的UI操作庫進行了封裝,并且暴露出RESTFUL的接口。然后測試代碼通過HTTP請求的方式,來進行實際的測試。其中,實際驅(qū)動UI的框架根據(jù)系統(tǒng)版本有所不同:

< 9.3 采用UIAutomation

>= 9.3 XCUITest

原因也比較簡單:Apple在10.0之后,移除了UIAutomation的支持,只支持XCUITest。

對比KIF,appium有它的優(yōu)點:

跨平臺,支持iOS,Android

測試代碼可以由多種語言編寫,這對測試來說門檻更低

測試腳本獨立與源代碼和測試框架

當然,任何框架都有缺點:

自定義控件支持不好

WebView的支持不好

總結(jié)

由于我不是專業(yè)的iOS測試,關(guān)于測試的一點見解如下:

單元測試還是選擇BDD框架,畢竟可讀性高一些,推薦Quick(Swift),Kiwi(Objective C)

UI測試優(yōu)先推薦KIF,如果需要兼顧安卓測試,或者測試人員對OC/Swift很陌生,可以采用appium

參考資料

Testing with Xcode 官方文檔,關(guān)于XCTest以及XCode有詳細的講解

objc.io關(guān)于測試的資料對于官方文檔的補充

騰訊移動品質(zhì)中心 鵝廠移動品質(zhì)中心,有很多好文章,強力推薦。

基于 KIF 的 iOS UI 自動化測試和持續(xù)集成 美團點評技術(shù)團隊寫的一篇博客

testing-in-swift

微信讀書排版引擎自動化測試方案

原文地址:iOS 自動化測試的那些干貨?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 大多數(shù)的iOS App (沒有持續(xù)集成)迭代流程是這樣的: 也就是說,測試是發(fā)布之前的最后一道關(guān)卡。如果bug不能...
    伯牙呀閱讀 4,902評論 1 22
  • 不要去動那古老的書, 因為古老,富有靈魂, 打開它也就濕潤了你的眼, 里面的故事并不出彩, 卻深深的扎痛著你的心,...
    若梔羽閱讀 571評論 2 0
  • 若當前IP(偏移地址)=1800H,CS(段基址)=1200H,則存取下一條待取指令的內(nèi)存地址是多少? 【解析】 ...
    0207_孫玲玉閱讀 263評論 0 0