Unit Testing Tutorial:Mocking Objects(Part2)

本文翻譯自Unit Testing Turorial:Mocking Objects
這是文章的下半截.

  • Writing Mocks

模擬對象能夠使你在應(yīng)用中測試當(dāng)某些事情發(fā)生時(shí)方法是否調(diào)用或者屬性是否被設(shè)定.例如,在PeopleListViewController的viewDidLoad()中,table view設(shè)置屬性dataProvider.
你將編寫一個(gè)測試來檢查它是否發(fā)生.

  • 測試的準(zhǔn)備

首先,項(xiàng)目做測試前你需要做些充分準(zhǔn)備.
選擇項(xiàng)目的導(dǎo)航欄,在Birthdays對象下的Build Settings中搜索Defines Module,將其設(shè)置為Yes,如下圖:

Screen-Shot-2015-03-28-at-17.40.12-700x195.png

在BirthdaysTest文件夾里以Test Case Class為模板添加名為PeopleListViewControllerTests的Swift文件.

如果Xcode讓你選擇是否創(chuàng)建橋接文件,選No.這是Xcode的一個(gè)bug.

打開新創(chuàng)建的PeopleListViewControllerTests.swift文件.確保你在其他導(dǎo)入文件下面導(dǎo)入了Birthdays,效果如下:

import UIKit
import XCTest
import Birthdays

刪除下面的兩個(gè)方法:

func testExample() {
  // This is an example of a functional test case.
  XCTAssert(true, "Pass")
}
 
func testPerformanceExample() {
  // This is an example of a performance test case.
  self.measureBlock() {
    // Put the code you want to measure the time of here.
  }
}

你現(xiàn)在需要一個(gè)PeopleListViewController實(shí)例來進(jìn)行測試.
在PeopleListViewControllerTests的開頭添加如下的代碼:

var viewController: PeopleListViewController!

替換setUp()里的代碼:

override func setUp() {
  super.setUp()
 
  viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PeopleListViewController") as! PeopleListViewController
}

這個(gè)方法用main storyboard來創(chuàng)建一個(gè)PeopleListViewController的實(shí)例并把它賦給viewController.
點(diǎn)擊Product\Test;Xcode會運(yùn)行項(xiàng)目中已有的所有測試方法.雖然現(xiàn)在你并沒有任何測試代碼,但它能夠確保目前為止一切都是正常的.不一會,Xcode會報(bào)告所有測試都是成功的.
你現(xiàn)在可以創(chuàng)建你的第一個(gè)mock了.

  • 編寫你的首個(gè)Mock

你正在使用Core Data,在PeopleListViewControllerTests.swift里面導(dǎo)入:

import CoreData

然后在PeopleListViewControllerTests里添加:

class MockDataProvider: NSObject, PeopleListDataProviderProtocol {
 
  var managedObjectContext: NSManagedObjectContext?
  weak var tableView: UITableView!
  func addPerson(personInfo: PersonInfo) { }
  func fetch() { }
  func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 }
  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    return UITableViewCell()
  }
}

這看起來像個(gè)比較復(fù)雜的mock類.然而,這僅是最基礎(chǔ)的需要,設(shè)定一個(gè)PeopleListViewController中dataProvider屬性的模擬類實(shí)例.你的模擬類也遵從PeopleListDataProviderProtocol和UITableViewDataSource協(xié)議.

點(diǎn)擊Product\Test;你的項(xiàng)目會再次運(yùn)行且你的0個(gè)測試函數(shù)會有0個(gè)失敗.但這并不意味著通過率100%. :] 但現(xiàn)在你已經(jīng)為你的第一單元測試做好了準(zhǔn)備.

單元測試中好的做法是將其分為given,when和then三個(gè)部分.'Given'設(shè)置測試環(huán)境條件;'when'運(yùn)行你想測試的代碼;'then'檢查是否得到預(yù)期的結(jié)果.

你的測試將在viewDidload()運(yùn)行之后檢查data provider中的tableView的屬性.

在PeopleListViewControllerTests添加如下測試:

func testDataProviderHasTableViewPropertySetAfterLoading() {
  // given
  // 1
  let mockDataProvider = MockDataProvider()
 
  viewController.dataProvider = mockDataProvider
 
  // when
  // 2
  XCTAssertNil(mockDataProvider.tableView, "Before loading the table view should be nil")
 
  // 3
  let _ = viewController.view
 
  // then    
  // 4
  XCTAssertTrue(mockDataProvider.tableView != nil, "The table view should be set")
  XCTAssert(mockDataProvider.tableView === viewController.tableView, 
    "The table view should be set to the table view of the data source")
}

下面為以上代碼的做的事情:

  1. 創(chuàng)建實(shí)例MockDataProvider并把其設(shè)為view controller的dataProvider屬性.
  2. 在測試之前通過斷言設(shè)置tableView屬性為nil.
  3. 訪問view來觸發(fā)viewDidLoad().
  4. 通過斷言設(shè)置測試類中的tableview屬性不為nil并設(shè)置view controller中的tableView.

再次點(diǎn)擊Product\Test;測試完成后,選擇test導(dǎo)航(或者按快捷鍵Cmd+5).你將看到如下結(jié)果:

Screen-Shot-2015-03-29-at-10.54.20.png

通過綠色的對號可以看到你的一個(gè)模擬測試通過啦! :]

  • Testing addPerson(_:)

接下來要通過調(diào)用data provider中的addPerson(_:)來測試下通訊錄選擇.
在MockDataProvider類中增加如下屬性:

var addPersonGotCalled = false

修改addPerson(_:):

func addPerson(personInfo: PersonInfo) { addPersonGotCalled = true }

此時(shí),當(dāng)你調(diào)用addPerson(_:)時(shí),會在實(shí)例MockDataProvider中設(shè)置addPersonGotCalled為true.
在進(jìn)行測試之前你需要導(dǎo)入AddressBookUI框架.
在PeopleListViewControllerTests.swift導(dǎo)入:

import AddressBookUI

現(xiàn)在添加如下測試代碼:

func testCallsAddPersonOfThePeopleDataSourceAfterAddingAPersion() {
  // given
  let mockDataSource = MockDataProvider()
 
  // 1
  viewController.dataProvider = mockDataSource
 
  // when
  // 2
  let record: ABRecord = ABPersonCreate().takeRetainedValue()
  ABRecordSetValue(record, kABPersonFirstNameProperty, "TestFirstname", nil)
  ABRecordSetValue(record, kABPersonLastNameProperty, "TestLastname", nil)
  ABRecordSetValue(record, kABPersonBirthdayProperty, NSDate(), nil)
 
  // 3
  viewController.peoplePickerNavigationController(ABPeoplePickerNavigationController(), 
    didSelectPerson: record)
 
  // then
  // 4
  XCTAssert(mockDataSource.addPersonGotCalled, "addPerson should have been called")
}

上面代碼做了哪些操作呢兰迫?

  1. 首先你將view controller中的data provider設(shè)置為你的模擬data provider實(shí)例.
  2. 繼而通過ABPersonCreate()創(chuàng)建通訊錄.
  3. 手動調(diào)用代理方法peoplePickerNavigationController(_:didSelectPerson:).通常,手動調(diào)用代理方法是個(gè)code smell,但對測試來講也還好啦.
  4. 最后通過data provider模擬設(shè)置為true查看addPersonGotCalled來斷言addPerson(_:).

點(diǎn)擊測試—你將會全部通過.測試是很簡單的事情吧戈稿!
但稍等,怎樣知道測試正是你想要測試的內(nèi)容呢望浩?

ragecomic_wat.png
  • Testing Your Tests

一個(gè)檢測測試真正使一些事情生效的方法是移出這個(gè)生效的測試實(shí)體.

在PeopleListViewController.swift中的peoplePickerNavigationController(_:didSelectPerson:)下面注釋掉:

dataProvider?.addPerson(person)

運(yùn)行測試;你最后寫的測試將會失敗.好了—你現(xiàn)在知道你的測試方法真正測試了一些東西了.這是個(gè)測試你的測試代碼的好方法;你應(yīng)該測試你最復(fù)雜的測試方法來確保他們工作正常.

ragecomic_testsOfTests.png

取消注釋使代碼保持原來的狀態(tài);再次運(yùn)行測試來確保一切正常.

  • Mocking Apple Framework Classes

你也許用過單例,例如NSNotificationCenter.defaultCenter()和NSUserDefaults.standardUserDefaults().但你如何來測試一個(gè)notification是否真正發(fā)送或者一個(gè)default被設(shè)置了?蘋果不允許你測試這些類的狀態(tài).

你可以添加一個(gè)想要的notifications觀察測試類.但這也許會使你的測試變得非常慢且實(shí)現(xiàn)這些類變得不可靠.Notification還可能從你的其他代碼處被觸發(fā),使測試變得不再是單獨(dú)的行為了.

想要打破這些限制,你可以在這些單例的地方使用mocks.

運(yùn)行程序;在人員列表中和切換姓和名的分類中添加John Appleseed和David Taylor.你會發(fā)現(xiàn)通訊錄的列表是按順序排列的.

代碼中是通過PeopleListViewController.swift中的changeSort()來實(shí)現(xiàn)的.

@IBAction func changeSorting(sender: UISegmentedControl) {
    userDefaults.setInteger(sender.selectedSegmentIndex, forKey: "sort")
    dataProvider?.fetch()
}

通過user defaults存儲的sort key來進(jìn)行選擇并調(diào)用data provider的方法fetch(). fetch()會讀取你存儲在user default中的新的排序關(guān)鍵字并且刷新通訊錄列表,在PeopleListDataProvider中:

public func fetch() {
  let sortKey = NSUserDefaults.standardUserDefaults().integerForKey("sort") == 0 ? "lastName" : "firstName"
 
  let sortDescriptor = NSSortDescriptor(key: sortKey, ascending: true)
  let sortDescriptors = [sortDescriptor]
 
  fetchedResultsController.fetchRequest.sortDescriptors = sortDescriptors
  var error: NSError? = nil
  if !fetchedResultsController.performFetch(&error) {
    println("error: \(error)")
  }
  tableView.reloadData()
}

PeopleListDataProvider使用NSFetchedResultsController來從Core Data中解析數(shù)據(jù).為了改變列表的順序,fetch()創(chuàng)建一個(gè)排序后的數(shù)組并把它賦給取數(shù)據(jù)的請求中來獲取結(jié)果.然后將數(shù)據(jù)傳到列表中進(jìn)行刷新.

你現(xiàn)在增加了一個(gè)測試用戶選擇存儲在NSUserDefaults中的排序.

在PeopleListViewControllerTests.swift中的MockDataProvider下面添加如下定義的類:

class MockUserDefaults: NSUserDefaults {
  var sortWasChanged = false
  override func setInteger(value: Int, forKey defaultName: String) {
    if defaultName == "sort" {
      sortWasChanged = true
    }
  }
}

MockUserDefaults為NSUserDefaults的子類;它有一個(gè)默認(rèn)為false的名為sortWasChanged的布爾屬性.且重寫了setImage(_:forKey:)的方法來改變sortWasChanged為true.

在你測試類的最后測試方法下面添加:

func testSortingCanBeChanged() {
  // given
  // 1
  let mockUserDefaults = MockUserDefaults(suiteName: "testing")!
  viewController.userDefaults = mockUserDefaults
 
  // when
  // 2
  let segmentedControl = UISegmentedControl()
  segmentedControl.selectedSegmentIndex = 0
  segmentedControl.addTarget(viewController, action: "changeSorting:", forControlEvents: .ValueChanged)
  segmentedControl.sendActionsForControlEvents(.ValueChanged)
 
  // then
  // 3
  XCTAssertTrue(mockUserDefaults.sortWasChanged, "Sort value in user defaults should be altered")
}

下面是以上代碼的釋義:

  1. 你首先創(chuàng)建一個(gè)MockUserDefaults的實(shí)例賦給viewController中的userDefaults;這種做法叫做dependency injection.
  2. 然后創(chuàng)建一個(gè)UISegmentedControl的實(shí)例,為這個(gè)view controller添加.ValueChanged值來控制事件的發(fā)生.
  3. 最后模擬類的user defaults中的斷言setImage(_:forKey:)被調(diào)用.

運(yùn)行你的測試代碼—將會全部通過.

如果你的應(yīng)用有非常復(fù)雜的API或框架,但你只想測試其中一個(gè)非常小的特性時(shí),如何做呢存筏?

"face"該登場了! :]

  • 編寫Fakes

Fakes像一個(gè)它偽造的全功能的類.利用它可以當(dāng)做替代類或者處理測試中過于復(fù)雜的結(jié)構(gòu)體.

在例子中,你并不想在測試時(shí)給真實(shí)的Core Data數(shù)據(jù)庫中添加或讀取數(shù)據(jù).因此,你要fake Core Data數(shù)據(jù)存儲.

添加新的測試類PeopleListDataProviderTests.

在新類中刪除下面的示例測試:

func testExample() {
  // ...
}
 
func testPerformanceExample() {
  // ...
}

在類中導(dǎo)入:

import Birthdays
import CoreData

添加如下屬性:

var storeCoordinator: NSPersistentStoreCoordinator!
var managedObjectContext: NSManagedObjectContext!
var managedObjectModel: NSManagedObjectModel!
var store: NSPersistentStore!
 
var dataProvider: PeopleListDataProvider!

這些屬性包含了Core Data所需的大部分組件.如果對Core Data不熟,可以看看Core Data Tutorial: Getting Started
在setUp()里添加如下代碼:

// 1
managedObjectModel = NSManagedObjectModel.mergedModelFromBundles(nil)
storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
store = storeCoordinator.addPersistentStoreWithType(NSInMemoryStoreType, 
  configuration: nil, URL: nil, options: nil, error: nil)
 
managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = storeCoordinator
 
// 2
dataProvider = PeopleListDataProvider()
dataProvider.managedObjectContext = managedObjectContext

下面是以上代碼的釋義:

  1. setUp() 在內(nèi)存中創(chuàng)建一個(gè)管理對象.通常Core Data存儲在設(shè)配的文件系統(tǒng)中.但對于這些測試,你存儲在設(shè)配的內(nèi)存中.
  2. 繼而創(chuàng)建一個(gè)PeopleListDataProvider的實(shí)例和將存儲在內(nèi)存中的管理對象設(shè)置為它的managedObjectContext.意味著你的新data provider將會和真實(shí)情況效果一樣,但不會在應(yīng)用中真實(shí)的添加刪除對象.

在PeopleListDataProviderTests中添加下面兩個(gè)屬性:

var tableView: UITableView!
var testRecord: PersonInfo!

在setUp()的底部添加如下代碼:

let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PeopleListViewController") as! PeopleListViewController
viewController.dataProvider = dataProvider
 
tableView = viewController.tableView
 
testRecord = PersonInfo(firstName: "TestFirstName", lastName: "TestLastName", birthday: NSDate())

這從storyboard的view controller中獲取table view 的設(shè)置并創(chuàng)建將在測試中用到的PersonInfo.

但測試結(jié)束時(shí),你將清除這些數(shù)據(jù)對象.

將tearDown()中的代碼替換為:

override func tearDown() {
  managedObjectContext = nil
 
  var error: NSError? = nil
  XCTAssert(storeCoordinator.removePersistentStore(store, error: &error), 
    "couldn't remove persistent store: \(error)")
 
  super.tearDown()
}

上面代碼將managedObjectContext設(shè)為nil用來清除內(nèi)存中存儲的數(shù)據(jù).這是要做的基本工作.你需要在進(jìn)行下個(gè)測試之前有個(gè)干凈的存儲空間.

現(xiàn)在開始編寫真正的測試文件了!在你的測試類中添加:

func testThatStoreIsSetUp() {
  XCTAssertNotNil(store, "no persistent store")
}

這將測試存儲的是否為nil.將檢查存儲沒有被創(chuàng)建的失敗情況.

運(yùn)行你的測試,一切正常.

下面將測試數(shù)據(jù)是否是想要的行數(shù).

在測試類中添加如下測試:

func testOnePersonInThePersistantStoreResultsInOneRow() {
  dataProvider.addPerson(testRecord)
 
  XCTAssertEqual(tableView.dataSource!.tableView(tableView, numberOfRowsInSection: 0), 1, 
    "After adding one person number of rows is not 1") 
}

首先,在測試存儲中添加一個(gè)通訊錄,然后斷言行數(shù)是否等于1.運(yùn)行測試,將會測試成功.

通過創(chuàng)建一個(gè)fake "persistent"存儲來避免寫入磁盤,能夠快速測試并使你的磁盤保持干凈,同時(shí)能夠使你運(yùn)行程序時(shí)更加自信,一切都如設(shè)想般運(yùn)行.

實(shí)際的測試中,你還可以測試多個(gè)sections和rows,主要取決你對項(xiàng)目的想要達(dá)到的自信程度.

如果你曾經(jīng)在一個(gè)項(xiàng)目的多個(gè)團(tuán)隊(duì)里,將會知道并不是項(xiàng)目的所有部分都會在同一時(shí)間準(zhǔn)備好,但你仍需要測試你的代碼.在服務(wù)器還沒準(zhǔn)備好時(shí),你怎么才能測試你的代碼呢?

Stubs登場了! :]

  • 編寫Stubs

Stubs假設(shè)一個(gè)方法對象的返回值.你將用stubs來測試在web 服務(wù)器還沒有完成的情況下的你的代碼.

Web組要為你的項(xiàng)目建設(shè)一個(gè)和app功能相同的網(wǎng)站.用戶通過該網(wǎng)站注冊的賬號可以同步到app端.但Web組甚至還沒有開始,你卻已經(jīng)接近完成了.這時(shí)候你需要寫個(gè)stub來模擬服務(wù)器.

本章將專注兩種測試方法:一種是解析通訊錄添加到網(wǎng)站,另一種是添加一個(gè)聯(lián)系人后從你的app中發(fā)送到網(wǎng)站.真實(shí)情況你也許還要添加一些登錄機(jī)制和錯(cuò)誤處理,但這超過了本教程的范圍.

打開APICommunicatorProtocol.swift;這個(gè)協(xié)議聲明了從服務(wù)端獲取通訊錄和發(fā)送通訊錄到服務(wù)器的兩個(gè)方法.

你將要傳遞Person實(shí)例,但這需要你使用另一種對象管理.將使用struct.

打開APICommunicator.swift.APICommunicator遵從APICommunicatorProtocol,但現(xiàn)在剛好能夠?qū)崿F(xiàn)編譯器happy.

你將創(chuàng)建stubs來支持view controller與APICommunicator的交互.

打開PeopleListViewControllerTests.swift并在PeopleListViewControllerTests類中添加如下類方法:

// 1
class MockAPICommunicator: APICommunicatorProtocol {
  var allPersonInfo = [PersonInfo]()
  var postPersonGotCalled = false
 
  // 2
  func getPeople() -> (NSError?, [PersonInfo]?) {
    return (nil, allPersonInfo)
  }
 
  // 3
  func postPerson(personInfo: PersonInfo) -> NSError? {
    postPersonGotCalled = true
    return nil
  }
}

需要闡明的是:

  1. 雖然APICommunicator是個(gè)結(jié)構(gòu)體,模擬實(shí)現(xiàn)的卻是個(gè)類.這種情況最好用一個(gè)類,因?yàn)闇y試需要的是可變的數(shù)據(jù).在類中會比結(jié)構(gòu)體中好實(shí)現(xiàn).
  2. getPeople()返回存儲在allPersonInfo的內(nèi)容.與從服務(wù)器獲取下載解析數(shù)據(jù)不同的是你通過簡單的數(shù)組來存儲通訊錄信息.
  3. postPerson(_:)設(shè)置postPersonGotCalled為true.

你已經(jīng)用不到20行的代碼創(chuàng)建好了你的"web API"! :]

現(xiàn)在你需要測試你的模擬API來確保從API返回的所有通訊錄數(shù)據(jù)通過調(diào)用addPerson()方法添加到了設(shè)置的數(shù)據(jù)存儲中.

在PeopleListViewControllerTests中添加如下測試方法:

func testFetchingPeopleFromAPICallsAddPeople() {
  // given
  // 1
  let mockDataProvider = MockDataProvider()
  viewController.dataProvider = mockDataProvider
 
  // 2
  let mockCommunicator = MockAPICommunicator()
  mockCommunicator.allPersonInfo = [PersonInfo(firstName: "firstname", lastName: "lastname", 
    birthday: NSDate())]
  viewController.communicator = mockCommunicator
 
  // when
  viewController.fetchPeopleFromAPI()
 
  // then
  // 3
  XCTAssert(mockDataProvider.addPersonGotCalled, "addPerson should have been called")
}

下面是以上的代碼釋義:

  1. 首先設(shè)置在測試中用的模擬對象mockDataProvider和mockCommunicator.
  2. 然后通過設(shè)置一些模擬的通訊錄數(shù)據(jù)并調(diào)用fetchPeopleFromAPI()來假設(shè)一個(gè)網(wǎng)絡(luò)請求.
  3. 最后測試addPerson(_:)是否被調(diào)用.

運(yùn)行,一切正常.

Girl學(xué)iOS100天 第26天

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寺晌,一起剝皮案震驚了整個(gè)濱河市瑟曲,隨后出現(xiàn)的幾起案子士鸥,更是在濱河造成了極大的恐慌闲孤,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烤礁,死亡現(xiàn)場離奇詭異讼积,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脚仔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門勤众,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鲤脏,你說我怎么就攤上這事们颜。” “怎么了猎醇?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵窥突,是天一觀的道長。 經(jīng)常有香客問我硫嘶,道長阻问,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任沦疾,我火速辦了婚禮称近,結(jié)果婚禮上第队,老公的妹妹穿的比我還像新娘。我一直安慰自己刨秆,他們只是感情好凳谦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衡未,像睡著了一般晾蜘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眠屎,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天剔交,我揣著相機(jī)與錄音,去河邊找鬼改衩。 笑死岖常,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葫督。 我是一名探鬼主播竭鞍,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼橄镜!你這毒婦竟也來了偎快?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洽胶,失蹤者是張志新(化名)和其女友劉穎晒夹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姊氓,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丐怯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翔横。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片读跷。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖禾唁,靈堂內(nèi)的尸體忽然破棺而出效览,到底是詐尸還是另有隱情,我是刑警寧澤荡短,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布丐枉,位于F島的核電站,受9級特大地震影響肢预,放射性物質(zhì)發(fā)生泄漏矛洞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沼本。 院中可真熱鬧噩峦,春花似錦、人聲如沸抽兆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辫红。三九已至凭涂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贴妻,已是汗流浹背切油。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留名惩,地道東北人澎胡。 一個(gè)月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像娩鹉,于是被迫代替她去往敵國和親攻谁。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理弯予,服務(wù)發(fā)現(xiàn)戚宦,斷路器,智...
    卡卡羅2017閱讀 134,658評論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫锈嫩、插件受楼、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評論 4 62
  • 每天早晨上班我都會路過一個(gè)公園,從去年冬天開始我就注意到公園里每天鍛煉的大媽中有一個(gè)與眾不同的身影祠挫,那是一位大爺那槽。...
    扒小怪閱讀 1,556評論 0 0
  • 知道你不懶慌植,只是想發(fā)嗲~ 戇蛋蛋派甜螺哥哥買給你吃咯,肉燥义郑,滷肉蝶柿,清茶,壽司非驮,刺身交汤,色拉,趕緊回家~……
    帶風(fēng)走路deFENG閱讀 121評論 0 1
  • 第一要推薦的就是貝親的這款小鑷子了星岗,這是干什么的呢?沒錯(cuò)戒洼,這個(gè)就是幫小寶寶拿鼻渣的一款神器俏橘,因?yàn)樾∨笥训谋强妆容^小...
    麥片麻麻閱讀 202評論 0 0