GoConvey框架使用指南

序言

在軟件開發(fā)中浅乔,產(chǎn)品代碼的正確性通過測(cè)試代碼來保證靖苇,而測(cè)試代碼的正確性誰來保證贤壁?答案是毫無爭(zhēng)議的芯砸,肯定是程序員自己。這就要求測(cè)試代碼必須足夠簡(jiǎn)單且表達(dá)力強(qiáng)双揪,讓錯(cuò)誤無處藏身渔期。我們要有一個(gè)好鼻子疯趟,能夠嗅出測(cè)試的壞味道信峻,及時(shí)的進(jìn)行測(cè)試重構(gòu)盹舞,從而讓測(cè)試代碼易于維護(hù)踢步。筆者從大量的編碼實(shí)踐中感悟道:雖然能寫出好的產(chǎn)品代碼的程序員很牛获印,但能寫出好的測(cè)試代碼的程序員更牛街州,尤其對(duì)于TDD實(shí)踐。

要寫出好的測(cè)試代碼取募,必須精通相關(guān)的框架玩敏。對(duì)于Golang程序員來說旺聚,至少需要掌握下面兩個(gè)框架:

本文將主要介紹GoConvey框架的基本使用方法砰粹,從而指導(dǎo)讀者更好的進(jìn)行測(cè)試實(shí)踐碱璃,最終寫出簡(jiǎn)單優(yōu)雅的測(cè)試代碼嵌器。

GoConvey簡(jiǎn)介

GoConvey是一款針對(duì)Golang的測(cè)試框架爽航,可以管理和運(yùn)行測(cè)試用例讥珍,同時(shí)提供了豐富的斷言函數(shù)衷佃,并支持很多 Web 界面特性纲酗。
Golang雖然自帶了單元測(cè)試功能,并且在GoConvey框架誕生之前也出現(xiàn)了許多第三方測(cè)試框架右蕊,但沒有一個(gè)測(cè)試框架像GoConvey一樣能夠讓程序員如此簡(jiǎn)潔優(yōu)雅的編寫測(cè)試代碼帕翻。

安裝

在命令行運(yùn)行下面的命令:

go get github.com/smartystreets/goconvey

運(yùn)行時(shí)間較長(zhǎng),運(yùn)行完后你會(huì)發(fā)現(xiàn):

  1. 在$GOPATH/src目錄下新增了github.com子目錄紫岩,該子目錄里包含了GoConvey框架的庫代碼
  2. 在$GOPATH/bin目錄下新增了GoConvey框架的可執(zhí)行程序goconvey

注:上面是在gopath時(shí)代使用GoConvey的API前的安裝方法泉蝌,而在gomod時(shí)代一般不需要先顯式安裝(gomod機(jī)制會(huì)自動(dòng)從goproxy拉取依賴到本地cache)勋陪,除非要使用GoConvey的web界面诅愚,這時(shí)需要提前安裝GoConvey的二進(jìn)制劫映,命令為go install github.com/smartystreets/goconvey@latest等浊。

基本使用方法

我們通過一個(gè)案例來介紹GoConvey框架的基本使用方法筹燕,并對(duì)要點(diǎn)進(jìn)行歸納撒踪。

產(chǎn)品代碼

我們實(shí)現(xiàn)一個(gè)判斷兩個(gè)字符串切片是否相等的函數(shù)StringSliceEqual制妄,主要邏輯包括:

  • 兩個(gè)字符串切片長(zhǎng)度不相等時(shí)耕捞,返回false
  • 兩個(gè)字符串切片一個(gè)是nil俺抽,另一個(gè)不是nil時(shí)磷斧,返回false
  • 遍歷兩個(gè)切片冕末,比較對(duì)應(yīng)索引的兩個(gè)切片元素值档桃,如果不相等藻肄,返回false
  • 否則仅炊,返回true

根據(jù)上面的邏輯抚垄,代碼實(shí)現(xiàn)如下所示:

func StringSliceEqual(a, b []string) bool {
    if len(a) != len(b) {
        return false
    }

    if (a == nil) != (b == nil) {
        return false
    }

    for i, v := range a {
        if v != b[i] {
            return false
        }
    }
    return true
}

對(duì)于邏輯“兩個(gè)字符串切片一個(gè)是nil,另一個(gè)不是nil時(shí)毁兆,返回false”的實(shí)現(xiàn)代碼有點(diǎn)不好理解:

if (a == nil) != (b == nil) {
    return false
}

我們實(shí)例化一下a和b气堕,即[]string{}和[]string(nil)茎芭,這時(shí)兩個(gè)字符串切片的長(zhǎng)度都是0梅桩,但肯定不相等宿百。

測(cè)試代碼

先寫一個(gè)正常情況的測(cè)試用例,如下所示:

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual should return true when a != nil  && b != nil", t, func() {
        a := []string{"hello", "goconvey"}
        b := []string{"hello", "goconvey"}
        So(StringSliceEqual(a, b), ShouldBeTrue)
    })
}

由于GoConvey框架兼容Golang原生的單元測(cè)試雀费,所以可以使用go test -v來運(yùn)行測(cè)試。
打開命令行外臂,進(jìn)入$GOPATH/src/infra/alg目錄下坐儿,運(yùn)行g(shù)o test -v律胀,則測(cè)試用例的執(zhí)行結(jié)果日下:

=== RUN   TestStringSliceEqual

  TestStringSliceEqual should return true when a != nil  && b != nil ?


1 total assertion

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok      infra/alg       0.006s

上面的測(cè)試用例代碼有如下幾個(gè)要點(diǎn):

  1. import goconvey包時(shí)宋光,前面加點(diǎn)號(hào)"."貌矿,以減少冗余的代碼。凡是在測(cè)試代碼中看到Convey和So兩個(gè)方法罪佳,肯定是convey包的精绎,不要在產(chǎn)品代碼中定義相同的函數(shù)名
  2. 測(cè)試函數(shù)的名字必須以Test開頭,而且參數(shù)類型必須為*testing.T
  3. 每個(gè)測(cè)試用例必須使用Convey函數(shù)包裹起來,它的第一個(gè)參數(shù)為string類型的測(cè)試描述,第二個(gè)參數(shù)為測(cè)試函數(shù)的入?yún)ⅲ愋蜑?testing.T),第三個(gè)參數(shù)為不接收任何參數(shù)也不返回任何值的函數(shù)(習(xí)慣使用閉包)
  4. Convey函數(shù)的第三個(gè)參數(shù)閉包的實(shí)現(xiàn)中通過So函數(shù)完成斷言判斷,它的第一個(gè)參數(shù)為實(shí)際值旗芬,第二個(gè)參數(shù)為斷言函數(shù)變量誊薄,第三個(gè)參數(shù)或者沒有(當(dāng)?shù)诙€(gè)參數(shù)為類ShouldBeTrue形式的函數(shù)變量)或者有(當(dāng)?shù)诙€(gè)函數(shù)為類ShouldEqual形式的函數(shù)變量)

我們故意將該測(cè)試用例改為不過:

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual should return true when a != nil  && b != nil", t, func() {
        a := []string{"hello", "goconvey"}
        b := []string{"hello", "goconvey"}
        So(StringSliceEqual(a, b), ShouldBeFalse)
    })
}

測(cè)試用例的執(zhí)行結(jié)果日下:

=== RUN   TestStringSliceEqual

  TestStringSliceEqual should return true when a != nil  && b != nil ?


Failures:

  * /Users/zhangxiaolong/Desktop/D/go-workspace/src/infra/alg/slice_test.go 
  Line 45:
  Expected: false
  Actual:   true


1 total assertion

--- FAIL: TestStringSliceEqual (0.00s)
FAIL
exit status 1
FAIL    infra/alg       0.006s


我們?cè)傺a(bǔ)充3個(gè)測(cè)試用例:

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual should return true when a != nil  && b != nil", t, func() {
        a := []string{"hello", "goconvey"}
        b := []string{"hello", "goconvey"}
        So(StringSliceEqual(a, b), ShouldBeTrue)
    })

    Convey("TestStringSliceEqual should return true when a == nil  && b == nil", t, func() {
        So(StringSliceEqual(nil, nil), ShouldBeTrue)
    })

    Convey("TestStringSliceEqual should return false when a == nil  && b != nil", t, func() {
        a := []string(nil)
        b := []string{}
        So(StringSliceEqual(a, b), ShouldBeFalse)
    })

    Convey("TestStringSliceEqual should return false when a != nil  && b != nil", t, func() {
        a := []string{"hello", "world"}
        b := []string{"hello", "goconvey"}
        So(StringSliceEqual(a, b), ShouldBeFalse)
    })
}

從上面的測(cè)試代碼可以看出定鸟,每一個(gè)Convey語句對(duì)應(yīng)一個(gè)測(cè)試用例,那么一個(gè)函數(shù)的多個(gè)測(cè)試用例可以通過一個(gè)測(cè)試函數(shù)的多個(gè)Convey語句來呈現(xiàn)。

測(cè)試用例的執(zhí)行結(jié)果如下:

=== RUN   TestStringSliceEqual

  TestStringSliceEqual should return true when a != nil  && b != nil ?


1 total assertion


  TestStringSliceEqual should return true when a == nil  && b == nil ?


2 total assertions


  TestStringSliceEqual should return false when a == nil  && b != nil ?


3 total assertions


  TestStringSliceEqual should return false when a != nil  && b != nil ?


4 total assertions

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok      infra/alg       0.006s

Convey語句的嵌套

Convey語句可以無限嵌套挺峡,以體現(xiàn)測(cè)試用例之間的關(guān)系箫津。需要注意的是,只有最外層的Convey需要傳入*testing.T類型的變量t。
我們將前面的測(cè)試用例通過嵌套的方式寫另一個(gè)版本:

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual", t, func() {
        Convey("should return true when a != nil  && b != nil", func() {
            a := []string{"hello", "goconvey"}
            b := []string{"hello", "goconvey"}
            So(StringSliceEqual(a, b), ShouldBeTrue)
        })

        Convey("should return true when a == nil  && b == nil", func() {
            So(StringSliceEqual(nil, nil), ShouldBeTrue)
        })

        Convey("should return false when a == nil  && b != nil", func() {
            a := []string(nil)
            b := []string{}
            So(StringSliceEqual(a, b), ShouldBeFalse)
        })

        Convey("should return false when a != nil  && b != nil", func() {
            a := []string{"hello", "world"}
            b := []string{"hello", "goconvey"}
            So(StringSliceEqual(a, b), ShouldBeFalse)
        })
    })
}

測(cè)試用例的執(zhí)行結(jié)果如下:

=== RUN   TestStringSliceEqual

  TestStringSliceEqual 
    should return true when a != nil  && b != nil ?
    should return true when a == nil  && b == nil ?
    should return false when a == nil  && b != nil ?
    should return false when a != nil  && b != nil ?


4 total assertions

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok      infra/alg       0.006s

可見,Convey語句嵌套的測(cè)試日志和Convey語句不嵌套的測(cè)試日志的顯示有差異蛤铜,筆者更喜歡這種以測(cè)試函數(shù)為單位多個(gè)測(cè)試用例集中顯示的形式穆刻。

此外朵锣,Convey語句嵌套還有一種三層嵌套的慣用法泣刹,即按BDD風(fēng)格來寫測(cè)試用例掀泳,核心點(diǎn)是通過GWT(Given…When…Then)格式來描述測(cè)試用例马僻,示例如下:

func TestStringSliceEqualIfBothNotNil(t *testing.T) {
    Convey("Given two string slice which are both not nil", t, func() {
        a := []string{"hello", "goconvey"}
        b := []string{"hello", "goconvey"}
        Convey("When the comparision is done", func() {
            result := StringSliceEqual(a, b)
            Convey("Then the result should be true", func() {
                So(result, ShouldBeTrue)
            })
        })
    })
}

GWT測(cè)試用例的執(zhí)行結(jié)果如下:

=== RUN   TestStringSliceEqualIfBothNotNil

  Given two string slice which are both not nil 
    When the comparision is done 
      Then the result should be true ?


1 total assertion

--- PASS: TestStringSliceEqualIfBothNotNil (0.00s)
ok      infra/alg       0.007s

按GWT格式寫測(cè)試用例時(shí),每一組GWT對(duì)應(yīng)一條測(cè)試用例擒权,即最內(nèi)層的Convey語句不像兩層嵌套時(shí)可以有多個(gè)瓣窄,而是只能有一個(gè)Convey語句裳凸。

我們依次寫出其余三個(gè)用例的三層嵌套形式:

func TestStringSliceEqualIfBothNil(t *testing.T) {
    Convey("Given two string slice which are both nil", t, func() {
        var a []string = nil
        var b []string = nil
        Convey("When the comparision is done", func() {
            result := StringSliceEqual(a, b)
            Convey("Then the result should be true", func() {
                So(result, ShouldBeTrue)
            })
        })
    })
}

func TestStringSliceNotEqualIfNotBothNil(t *testing.T) {
    Convey("Given two string slice which are both nil", t, func() {
        a := []string(nil)
        b := []string{}
        Convey("When the comparision is done", func() {
            result := StringSliceEqual(a, b)
            Convey("Then the result should be false", func() {
                So(result, ShouldBeFalse)
            })
        })
    })
}

func TestStringSliceNotEqualIfBothNotNil(t *testing.T) {
    Convey("Given two string slice which are both not nil", t, func() {
        a := []string{"hello", "world"}
        b := []string{"hello", "goconvey"}
        Convey("When the comparision is done", func() {
            result := StringSliceEqual(a, b)
            Convey("Then the result should be false", func() {
                So(result, ShouldBeFalse)
            })
        })
    })
}

我們?cè)賹⑸厦娴乃臈l用例使用測(cè)試套的形式來寫瓣颅,即一個(gè)測(cè)試函數(shù)包含多條用例粉怕,每條用例使用Convey語句四層嵌套的慣用法:

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqualIfBothNotNil", t, func() {
        Convey("Given two string slice which are both not nil", func() {
            a := []string{"hello", "goconvey"}
            b := []string{"hello", "goconvey"}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be true", func() {
                    So(result, ShouldBeTrue)
                })
            })
        })
    })

    Convey("TestStringSliceEqualIfBothNil", t, func() {
        Convey("Given two string slice which are both nil", func() {
            var a []string = nil
            var b []string = nil
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be true", func() {
                    So(result, ShouldBeTrue)
                })
            })
        })
    })

    Convey("TestStringSliceNotEqualIfNotBothNil", t, func() {
        Convey("Given two string slice which are both nil", func() {
            a := []string(nil)
            b := []string{}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be false", func() {
                    So(result, ShouldBeFalse)
                })
            })
        })
    })

    Convey("TestStringSliceNotEqualIfBothNotNil", t, func() {
        Convey("Given two string slice which are both not nil", func() {
            a := []string{"hello", "world"}
            b := []string{"hello", "goconvey"}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be false", func() {
                    So(result, ShouldBeFalse)
                })
            })
        })
    })

}

Web 界面

GoConvey不僅支持在命令行進(jìn)行自動(dòng)化編譯測(cè)試还绘,而且還支持在 Web 界面進(jìn)行自動(dòng)化編譯測(cè)試。想要使用GoConvey的 Web 界面特性又谋,需要在測(cè)試文件所在目錄下執(zhí)行g(shù)oconvey:

$GOPATH/bin/goconvey

這時(shí)彈出一個(gè)頁面娇斩,如下圖所示:

goconvey-web.png

在 Web 界面中:

  1. 可以設(shè)置界面主題
  2. 查看完整的測(cè)試結(jié)果
  3. 使用瀏覽器提醒等實(shí)用功能
  4. 自動(dòng)檢測(cè)代碼變動(dòng)并編譯測(cè)試
  5. 半自動(dòng)化書寫測(cè)試用例
  6. 查看測(cè)試覆蓋率
  7. 臨時(shí)屏蔽某個(gè)包的編譯測(cè)試

Skip

針對(duì)想忽略但又不想刪掉或注釋掉某些斷言操作志珍,GoConvey提供了Convey/So的Skip方法:

  • SkipConvey函數(shù)表明相應(yīng)的閉包函數(shù)將不被執(zhí)行
  • SkipSo函數(shù)表明相應(yīng)的斷言將不被執(zhí)行

當(dāng)存在SkipConvey或SkipSo時(shí),測(cè)試日志中會(huì)顯式打上"skipped"形式的標(biāo)記:

  • 當(dāng)測(cè)試代碼中存在SkipConvey時(shí)旁壮,相應(yīng)閉包函數(shù)中不管是否為SkipSo厦坛,都將被忽略放仗,測(cè)試日志中對(duì)應(yīng)的符號(hào)僅為一個(gè)"?"
  • 當(dāng)測(cè)試代碼Convey語句中存在SkipSo時(shí)惶傻,測(cè)試日志中每個(gè)So對(duì)應(yīng)一個(gè)"?"或"?"励翼,每個(gè)SkipSo對(duì)應(yīng)一個(gè)"?",按實(shí)際順序排列
  • 不管存在SkipConvey還是SkipSo時(shí),測(cè)試日志中都有字符串"{n} total assertions (one or more sections skipped)",其中{n}表示測(cè)試中實(shí)際已運(yùn)行的斷言語句數(shù)

定制斷言函數(shù)

我們先看一下So函數(shù)的聲明:

func So(actual interface{}, assert Assertion, expected ...interface{})

第二個(gè)參數(shù)為assert贩虾,是一個(gè)函數(shù)變量,它的類型Assertion的定義為:

type Assertion func(actual interface{}, expected ...interface{}) string

當(dāng)Assertion的變量的返回值為""時(shí)表示斷言成功考杉,否則表示失敳呔:

const assertionSuccess = ""

我們簡(jiǎn)單實(shí)現(xiàn)一個(gè)Assertion函數(shù):

func ShouldSummerBeComming(actual interface{}, expected ...interface{}) string {
    if actual == "summer" && expected[0] == "comming" {
        return ""
    } else {
        return "summer is not comming!"
    }
}

我們?nèi)匀辉趕lice_test文件中寫一個(gè)簡(jiǎn)單測(cè)試:

func TestSummer(t *testing.T) {
    Convey("TestSummer", t, func() {
        So("summer", ShouldSummerBeComming, "comming")
        So("winter", ShouldSummerBeComming, "comming")
    })
}

根據(jù)ShouldSummerBeComming的實(shí)現(xiàn),Convey語句中第一個(gè)So將斷言成功奔则,第二個(gè)So將斷言失敗蛮寂。
我們運(yùn)行測(cè)試,查看執(zhí)行結(jié)果易茬,符合期望:

=== RUN   TestSummer

  TestSummer ??


Failures:

  * /Users/zhangxiaolong/Desktop/D/go-workspace/src/infra/alg/slice_test.go 
  Line 52:
  summer is not comming!


2 total assertions

--- FAIL: TestSummer (0.00s)
FAIL
exit status 1
FAIL    infra/alg       0.006s

小結(jié)

Golang雖然自帶了單元測(cè)試功能,但筆者建議讀者使用已經(jīng)成熟的第三方測(cè)試框架及老。本文主要介紹了GoConvey框架抽莱,通過文字結(jié)合代碼示例講解基本的使用方法,要點(diǎn)歸納如下:

  1. import goconvey包時(shí)骄恶,前面加點(diǎn)號(hào)"."食铐,以減少冗余的代碼;
  2. 測(cè)試函數(shù)的名字必須以Test開頭僧鲁,而且參數(shù)類型必須為*testing.T虐呻;
  3. 每個(gè)測(cè)試用例必須使用Convey語句包裹起來,推薦使用Convey語句的嵌套寞秃,即一個(gè)函數(shù)有一個(gè)或多個(gè)測(cè)試函數(shù)斟叼,一個(gè)測(cè)試函數(shù)嵌套兩層、三層或四層Convey語句春寿;
  4. Convey語句的第三個(gè)參數(shù)習(xí)慣以閉包的形式實(shí)現(xiàn)朗涩,在閉包中通過So語句完成斷言;
  5. 使用GoConvey框架的 Web 界面特性绑改,作為命令行的補(bǔ)充谢床;
  6. 在適當(dāng)?shù)膱?chǎng)景下使用SkipConvey函數(shù)或SkipSo函數(shù)兄一;
  7. 當(dāng)測(cè)試中有需要時(shí),可以定制斷言函數(shù)识腿。

至此出革,希望讀者已經(jīng)掌握了GoConvey框架的基本用法,從而可以寫出簡(jiǎn)單優(yōu)雅的測(cè)試代碼渡讼。

然而骂束,事情并沒有這么簡(jiǎn)單!試想硝全,如果在被測(cè)函數(shù)中調(diào)用了底層rand包的Intn函數(shù)栖雾,你會(huì)如何寫測(cè)試代碼?經(jīng)過思考伟众,你應(yīng)該會(huì)發(fā)現(xiàn)需要給rand包的Intn函數(shù)打樁析藕。如何低成本的滿足用戶各種測(cè)試場(chǎng)景的打樁訴求,這正是GoMonkey框架的專長(zhǎng)凳厢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末账胧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子先紫,更是在濱河造成了極大的恐慌治泥,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遮精,死亡現(xiàn)場(chǎng)離奇詭異居夹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)本冲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門准脂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人檬洞,你說我怎么就攤上這事狸膏。” “怎么了添怔?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵湾戳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我广料,道長(zhǎng)砾脑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任性昭,我火速辦了婚禮拦止,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己汹族,他們只是感情好萧求,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著顶瞒,像睡著了一般夸政。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榴徐,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天守问,我揣著相機(jī)與錄音,去河邊找鬼坑资。 笑死耗帕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袱贮。 我是一名探鬼主播仿便,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼攒巍!你這毒婦竟也來了嗽仪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤柒莉,失蹤者是張志新(化名)和其女友劉穎闻坚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兢孝,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窿凤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了跨蟹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卷玉。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖喷市,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情威恼,我是刑警寧澤品姓,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站箫措,受9級(jí)特大地震影響腹备,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斤蔓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一植酥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦友驮、人聲如沸漂羊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽走越。三九已至,卻和暖如春耻瑟,著一層夾襖步出監(jiān)牢的瞬間旨指,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工喳整, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谆构,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓框都,卻偏偏與公主長(zhǎng)得像搬素,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞬项,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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