Vert.x 導論之四:單元測試和集成測試

’Vert.x導論‘回顧

現(xiàn)在讓我們快速回顧到目前為止在Vert.x導論系列中我們開發(fā)了些什么。在第一篇帖子中唉堪,我們開發(fā)了一個非常簡單的Vert.x 3應(yīng)用模聋,并且學習了這個應(yīng)用如何被測試,打包及執(zhí)行唠亚。在第二篇帖子中,我們學習了這個應(yīng)用如何可配置链方,并在測試中采用了隨機端口。最后灶搜,在上一篇帖子中祟蚀,展示了如何使用vertx-web以及如何實現(xiàn)一個小型的REST API。然而割卖,我們忘記了一個重要任務(wù)前酿。我們沒有測試新增的API。在這篇帖子中鹏溯,我們會通過實現(xiàn)單元測試以及集成測試來增加我們對新功能的信心罢维。

這篇帖子的代碼在Introduction-to-Vert.x-Demo項目的post-4分支。起始代碼在post-3分支剿涮。

測試言津,測試攻人,再測試取试。。怀吻。

這篇帖子主要關(guān)于測試瞬浓。我們區(qū)分兩種測試:單元測試和集成測試。兩者同等重要蓬坡,但是關(guān)注點不同猿棉。單元測試確保你應(yīng)用的一個組件正常工作磅叛,通常就是Java世界內(nèi)的一個class行為符合預(yù)期。應(yīng)用并沒有作為一個整體被測試萨赁,而是一部分一部分的測試弊琴。集成測試感覺更黑盒測試因為應(yīng)用通常從外部啟動和測試。

在這篇帖子中杖爽,我們將從更多的單元測試起步作為熱身敲董,然后聚焦于集成測試。如果你之前實現(xiàn)過集成測試慰安,你可能會被嚇到腋寨,這說得通。但不用怕化焕,用Vert.x開發(fā)沒有隱藏的驚嚇萄窜。

熱身:更多的單元測試

我們慢慢來。在第一篇帖子里撒桨,我們用vertx-unit實現(xiàn)了一個單元測試查刻。我們之前做的這個測試超級簡單:

  1. 我們在測試前啟動了應(yīng)用
  2. 我們檢驗它是否以"Hello"作為響應(yīng)

為了方便你回憶,讓我們看看這段代碼

@Before
public void setUp(TestContext context) throws IOException {
  vertx = Vertx.vertx();
  ServerSocket socket = new ServerSocket(0);
  port = socket.getLocalPort();
  socket.close();
  DeploymentOptions options = new DeploymentOptions()
      .setConfig(new JsonObject().put("http.port", port)
      );
  vertx.deployVerticle(MyFirstVerticle.class.getName(), options, context.asyncAssertSuccess());
}

setUp方法在每次測試前都會被調(diào)用(@Before注解指定這樣操作)凤类。這個方法首先創(chuàng)建一個Vert.x的新 實例赖阻,然后獲取一個可用端口,最后根據(jù)對應(yīng)的配置來部署我們的verticle踱蠢。context.asyncAssertSuccess()方法會一直等待直到verticle被成功部署好為止火欧。

tearDown方法是簡單明了的,只是關(guān)閉了Vert.x實例茎截。它自動卸載了verticles:

@After
public void tearDown(TestContext context) {
  vertx.close(context.asyncAssertSuccess());
}

最終苇侵,我們的單個測試是:

@Test
public void testMyApplication(TestContext context) {
  final Async async = context.async();
  vertx.createHttpClient().getNow(port, "localhost", "/", response -> {
    response.handler(body -> {
      context.assertTrue(body.toString().contains("Hello"));
      async.complete();
    });
  });
 }

這個測試只是檢測當我們對"/"地址發(fā)送一個HTTP請求時,應(yīng)用是否回復(fù)了"Hello"∑笮浚現(xiàn)在我們嘗試實現(xiàn)一些單元測試來確認我們的web應(yīng)用和REST API接口的行為是否符合預(yù)期榆浓。我們首先檢查"index.html"頁面是否正確工作。這個測試和之前那個測試很相似撕攒。

@Test
public void checkThatTheIndexPageIsServed(TestContext context) {
  Async async = context.async();
  vertx.createHttpClient().getNow(port, "localhost", "/assets/index.html", response -> {
    context.assertEquals(response.statusCode(), 200);
    context.assertEquals(response.headers().get("Content-Type"), "text/html");
    response.bodyHandler(body -> {
      context.assertTrue(body.toString().contains("<title>My Whisky Collection</title>"));
      async.complete();
    });
  });
}

我們檢索了index.html頁面并檢查:

  1. 頁面存在(狀態(tài)碼200)
  2. 這是個HTML頁面(Content-Type被設(shè)置為"text/html")
  3. 頁面的標題正確("My Whisky Collection")
    檢索內(nèi)容
    如你所見陡鹃,我們可以在HTTP響應(yīng)上直接測試狀態(tài)碼和消息頭,但我們需要檢索消息體來確保它是正確的抖坪。這通過接受整個消息體作為參數(shù)的消息體句柄來做到的萍鲸。一旦最后的檢驗完成,我們通過調(diào)用complete來釋放async擦俐。
    很好脊阴,但這實際上并沒有測試我們的REST API。先確認我們可以在集合中增加一瓶葡萄酒。不像之前的測試嘿期,這個測試使用post方法post數(shù)據(jù)到服務(wù)器:
@Test
public void checkThatWeCanAdd(TestContext context) {
  Async async = context.async();
  final String json = Json.encodePrettily(new Whisky("Jameson", "Ireland"));
  final String length = Integer.toString(json.length());
  vertx.createHttpClient().post(port, "localhost", "/api/whiskies")
      .putHeader("content-type", "application/json")
      .putHeader("content-length", length)
      .handler(response -> {
        context.assertEquals(response.statusCode(), 201);
        context.assertTrue(response.headers().get("content-type").contains("application/json"));
        response.bodyHandler(body -> {
          final Whisky whisky = Json.decodeValue(body.toString(), Whisky.class);
          context.assertEquals(whisky.getName(), "Jameson");
          context.assertEquals(whisky.getOrigin(), "Ireland");
          context.assertNotNull(whisky.getId());
          async.complete();
        });
      })
      .write(json)
      .end();
}

首先我們創(chuàng)建我們想要添加的內(nèi)容品擎。服務(wù)器消費JSON數(shù)據(jù),所以我們需要一個JSON字符串备徐。你可以手工寫出你的JSON文檔萄传,或者和這里一樣使用Vert.x方法(Json.encodePrettily)。一旦我們準備好了內(nèi)容蜜猾,我們做一個POST請求盲再。我們需要配置一些消息頭來確保我們的JSON數(shù)據(jù)被服務(wù)器正確讀取。我們表示我們在發(fā)送JSON數(shù)據(jù)并且還設(shè)置了消息體的長度瓣铣。我們還附加了一個響應(yīng)句柄做了類似前面測試的檢測答朋。請注意我們可以使用JSON.decodeValue方法將服務(wù)器發(fā)送的JSON文檔重構(gòu)成我們需要的對象。這樣做可以避免很多樣板代碼所以很方便棠笑。此刻梦碗,請求還沒有發(fā)送,我們需要寫出數(shù)據(jù)并調(diào)用end()方法蓖救。這通過 .write(json).end();來辦到洪规。

方法的順序很重要。如果你沒有配置好響應(yīng)句柄循捺,你不能寫出數(shù)據(jù)斩例。最后不要忘記調(diào)用end()

你可以使用如下命令來執(zhí)行測試:

mvn clean test

我們可以寫更多類似這樣的單元測試从橘,但這將變得很復(fù)雜念赶。下面將使用集成測試來繼續(xù)我們的測試工作。

集成測試很傷人

我想我們首先需要明確恰力,集成測試很折磨人叉谜。如果你在這個領(lǐng)域有經(jīng)驗,你還記得要花多久讓一切事物就緒踩萎?一想起這事我就頭疼停局。為何集成測試越來越麻煩了?主要在于安裝環(huán)節(jié):

  1. 我們必須以近似生產(chǎn)環(huán)境的方式來啟動應(yīng)用
  2. 接下來要運行測試(配置測試確保檢查的是所需的應(yīng)用實例)
  3. 最后必須停止應(yīng)用

聽上去并不麻煩香府,但如果你需要Linux董栽,MacOS X和Windows的支持,事情很快變得凌亂起來企孩。有很多了不起的框架可以解決這個問題比如Arquillian锭碳,但這里我們將不使用框架做集成測試,以便更好的理解工作機理柠硕。

我們需要一份戰(zhàn)斗計劃

在投入復(fù)雜的配置前工禾,我們先花點時間確認下任務(wù):
第一步 - 保留一個可用端口 我們需要獲取一個應(yīng)用可以監(jiān)聽的可用端口运提,并且我們需要將這個端口注入到集成測試中蝗柔。

第二步 - 生成應(yīng)用配置 一旦準備好了可用端口闻葵,我們需要寫一個JSON文件配置這個端口為應(yīng)用的HTTP端口

第三步 - 啟動應(yīng)用 聽起來很容易?由于我們需要在后臺進程中啟動應(yīng)用癣丧,所以也并不那么簡單槽畔。

第四步 - 執(zhí)行集成測試 最后,重點部分胁编,運行測試厢钧。但在這之前,我們應(yīng)該事先一些集成測試嬉橙。我們后面將會提到早直。

第五步 - 停止應(yīng)用 一旦測試都執(zhí)行完成,無論測試中是否有失敗或錯誤市框,我們需要停止應(yīng)用霞扬。

有多種方式可以實現(xiàn)這份計劃。我們打算采用一種通用的方式杆查。這也許不是最好的页滚,但幾乎可以在任何場合使用泊愧。這種方法和Apache Maven綁的很緊。如果你想提議一種替代方案(采用Gradle或者其他工具)斧拍,我很高興能把你的方法添加到這篇帖子中。

實現(xiàn)這份計劃

如上所說杖小,這章節(jié)以Maven為中心肆汹,大部分代碼在pom.xml文件中。如果你從未使用過不同的Maven生命周期階段予权,推薦你讀一下introduction to the Maven lifecycle县踢。

我們需要添加和配置一些插件。打開pom.xml文件伟件,在<plugins>部分添加:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <version>1.9.1</version>
  <executions>
    <execution>
      <id>reserve-network-port</id>
      <goals>
        <goal>reserve-network-port</goal>
      </goals>
      <phase>process-sources</phase>
      <configuration>
        <portNames>
          <portName>http.port</portName>
        </portNames>
      </configuration>
    </execution>
  </executions>
</plugin>

我們使用build-helper-maven-plugin(如果你經(jīng)常使用Maven你應(yīng)該去了解下)來獲取一個可用端口硼啤。一旦確定,這個插件將可用端口賦值給http.port變量斧账。我們在構(gòu)建過程的早期執(zhí)行這個插件(在process-sources階段)谴返,這樣我們可以在其他插件中使用http.port變量。這是為了第一步做準備咧织。

第二步需要執(zhí)行兩個動作嗓袱。首先,在pom.xml文件中习绢,緊跟在<build>開放標簽下渠抹,添加:

<testResources>
  <testResource>
    <directory>src/test/resources</directory>
    <filtering>true</filtering>
  </testResource>
</testResources>

這里指示Maven從 src/test/resources目錄過濾資源蝙昙。Filter意味著用真實值代替占位符。這正是我們所需的梧却,現(xiàn)在我們有了http.port變量∑娴撸現(xiàn)在用如下內(nèi)容來創(chuàng)建 src/test/resources/my-it-config.json文件:

{
  "http.port": ${http.port}
}

這個配置文件類似于我們在之前帖子中創(chuàng)建的那個。唯一的差別在于${http.port}放航,這也是Maven過濾用的默認語法烈拒。所以,當Maven需要處理文件時广鳍,它將會用被選的端口來替換${http.port}荆几。這就是第二步。

第三步和第五步的處理比較麻煩赊时。我們要啟動和停止應(yīng)用吨铸。我們打算用maven-antrun-plugin來辦到。在pom.xml文件中祖秒,在build-helper-maven-plugin下诞吱,添加:

<!-- We use the maven-antrun-plugin to start the application before the integration tests
and stop them afterward -->
<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.8</version>
  <executions>
    <execution>
      <id>start-vertx-app</id>
      <phase>pre-integration-test</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <target>
          <!--
          Launch the application as in 'production' using the fatjar.
          We pass the generated configuration, configuring the http port to the picked one
          -->
          <exec executable="${java.home}/bin/java"
                dir="${project.build.directory}"
                spawn="true">
            <arg value="-jar"/>
            <arg value="${project.artifactId}-${project.version}-fat.jar"/>
            <arg value="-conf"/>
            <arg value="${project.build.directory}/test-classes/my-it-config.json"/>
          </exec>
        </target>
      </configuration>
    </execution>
    <execution>
      <id>stop-vertx-app</id>
      <phase>post-integration-test</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <!--
          Kill the started process.
          Finding the right process is a bit tricky. Windows command is in the windows profile (below)
          -->
        <target>
          <exec executable="bash"
                dir="${project.build.directory}"
                spawn="false">
            <arg value="-c"/>
            <arg value="ps ax | grep -Ei '[\-]DtestPort=${http.port}\s+\-jar\s+${project.artifactId}' | awk 'NR==1{print $1}' | xargs kill -SIGTERM"/>
          </exec>
        </target>
      </configuration>
    </execution>
  </executions>
</plugin>

這里有一大堆XML。我們?yōu)檫@個插件配置了兩個執(zhí)行階段狈涮。第一個狐胎,在pre-integration-test階段,執(zhí)行一系列bash命令來啟動應(yīng)用歌馍。主要是執(zhí)行:

java -jar my-first-app-1.0-SNAPSHOT-fat.jar -conf .../my-it-config.json

fatfar被創(chuàng)建了握巢?
嵌入了我們應(yīng)用的fatfar在package階段被創(chuàng)建,在pre-integration-test之前松却,所以暴浦,fatjar是被創(chuàng)建了。
如上晓锻,我們?nèi)缭谏a(chǎn)環(huán)境一樣啟動了應(yīng)用歌焦。

一旦集成測試被執(zhí)行了(第四步我們還沒說起),我們需要停止應(yīng)用(所以在post-integration-test階段)砚哆。為了關(guān)閉應(yīng)用独撇,我們會使用一些shell魔法命令來查找我們的進程號,會用到ps命令并發(fā)送SIGTERM信號躁锁,這些等同于:

ps
.... -> find your process id
kill your_process_id -SIGTERM

還有Windows纷铣?
我之前提起過,我們希望支持Windows而這些命令在Windows下不工作战转。不用擔心搜立,Windows配置在下文會提到...
我們現(xiàn)在將要做之前跳過的第四步。為了執(zhí)行我們的集成測試槐秧,我們將使用maven-failsafe-plugin啄踊。將如下插件配置添加到你的pom.xml文件中:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>2.18.1</version>
  <executions>
    <execution>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <configuration>
        <systemProperties>
          <http.port>${http.port}</http.port>
        </systemProperties>
      </configuration>
    </execution>
  </executions>
</plugin>

如你所見忧设,我們將http.port屬性作為一個系統(tǒng)變量傳遞,這樣我們的測試能夠連接到正確的端口颠通。

就這樣了址晕,現(xiàn)在來試試(就Windows用戶而言,你必須更有耐心或直接跳到最后一節(jié))蒜哀。

mvn clean verify

我們不該使用 mvn integration-test 因為這樣應(yīng)用不會停止斩箫。verify階段在post-integration-test階段后吏砂,會分析集成測試的結(jié)果撵儿。由于集成測試失敗造成的構(gòu)建失敗會在這階段報告。

我們還沒有具體的集成測試內(nèi)容狐血!

我們準備好了集成測試所需的材料淀歇,但我們還沒有一個集成測試。為了簡化實現(xiàn)匈织,我們使用兩個庫:AssertJRest-Assured浪默。

AssertJ提供很多斷言,這些斷言你能夠鏈化并順暢使用缀匕。Rest Assured是一個用來測試REST API的框架纳决。

pom.xml文件中,在</dependencies>前添加如下兩個依賴:

<dependency>
  <groupId>com.jayway.restassured</groupId>
  <artifactId>rest-assured</artifactId>
  <version>3.0.2</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.assertj</groupId>
  <artifactId>assertj-core</artifactId>
  <version>3.10.0</version>
  <scope>test</scope>
</dependency>

然后創(chuàng)建 src/test/java/io/vertx/blog/first/MyRestIT.java 文件乡小。不像單元測試阔加,集成測試以IT結(jié)束。對Failsafe插件來說满钟,很容易區(qū)分單元測試(以Test開始結(jié)束)和集成測試(以IT開始結(jié)束)胜榔。在新增的文件中添加:

package io.vertx.blog.first;

import com.jayway.restassured.RestAssured;
import org.junit.AfterClass;
import org.junit.BeforeClass;

public class MyRestIT {

  @BeforeClass
  public static void configureRestAssured() {
    RestAssured.baseURI = "http://localhost";
    RestAssured.port = Integer.getInteger("http.port", 8080);
  }

  @AfterClass
  public static void unconfigureRestAssured() {
    RestAssured.reset();
  }
}

@BeforeClass@AfterClass注解的方法在類里所有的測試之前/之后分別執(zhí)行一次。這里湃番,我們只是取回http.port(作為系統(tǒng)參數(shù)傳入)并配置REST Assured夭织。

是時候?qū)崿F(xiàn)一個真的測試。讓我們檢測是否可以獲取某個特定產(chǎn)品:

@Test
public void checkThatWeCanRetrieveIndividualProduct() {
  // Get the list of bottles, ensure it's a success and extract the first id.
  final int id = RestAssured.get("/api/whiskies").then()
      .assertThat()
      .statusCode(200)
      .extract()
      .jsonPath().getInt("find { it.name=='Bowmore 15 Years Laimrig' }.id");
  // Now get the individual resource and check the content
  RestAssured.get("/api/whiskies/" + id).then()
      .assertThat()
      .statusCode(200)
      .body("name", equalTo("Bowmore 15 Years Laimrig"))
      .body("origin", equalTo("Scotland, Islay"))
      .body("id", equalTo(id));
}

這里你能夠欣賞Rest Assured的力量和表達力吠撮。我們獲取產(chǎn)品列表尊惰,確認響應(yīng)是正確的,使用JSON(Groovy)路徑表達式來提取某個特定產(chǎn)品的id泥兰。

然后弄屡,我們嘗試獲取這個產(chǎn)品的元數(shù)據(jù),并檢驗結(jié)果逾条。

現(xiàn)在實現(xiàn)一個更復(fù)雜的場景琢岩。添加和刪除一個產(chǎn)品:

@Test
public void checkWeCanAddAndDeleteAProduct() {
  // Create a new bottle and retrieve the result (as a Whisky instance).
  Whisky whisky = RestAssured.given()
      .body("{\"name\":\"Jameson\", \"origin\":\"Ireland\"}").request().post("/api/whiskies").thenReturn().as(Whisky.class);
        Assertions.assertThat(whisky.getName()).isEqualToIgnoringCase("Jameson");
    Assertions.assertThat(whisky.getOrigin()).isEqualToIgnoringCase("Ireland");
    Assertions.assertThat(whisky.getId()).isNotZero();
  // Check that it has created an individual resource, and check the content.
  RestAssured.get("/api/whiskies/" + whisky.getId()).then()
      .assertThat()
      .statusCode(200)
      .body("name", equalTo("Jameson"))
      .body("origin", equalTo("Ireland"))
      .body("id", equalTo(whisky.getId()));
  // Delete the bottle
  RestAssured.delete("/api/whiskies/" + whisky.getId()).then().assertThat().statusCode(204);
  // Check that the resource is not available anymore
  RestAssured.get("/api/whiskies/" + whisky.getId()).then()
      .assertThat()
      .statusCode(404);
}

現(xiàn)在我們有了集成測試,試著輸入如下命令:

mvn clean verify

還蠻簡單的师脂?等環(huán)境被準備好后是蠻簡單的担孔。江锨。。你能夠繼續(xù)實現(xiàn)其他集成測試來確保一切行為如你預(yù)期糕篇。

親愛的Windows用戶...

這一節(jié)是給Windows用戶的福利啄育,還有想在Windows機器上運行他們的集成測試的人們。之前我們執(zhí)行來停止應(yīng)用的命令在Windows系統(tǒng)上不起作用拌消。幸運的是挑豌,我們可以用一個在Windows系統(tǒng)上執(zhí)行的profile來擴展pom.xml。

在你的pom.xml文件中墩崩,緊跟著</build>氓英,添加:

<profiles>
  <!-- A profile for windows as the stop command is different -->
  <profile>
    <id>windows</id>
    <activation>
      <os>
        <family>windows</family>
      </os>
    </activation>
    <build>
      <plugins>
        <plugin>
          <artifactId>maven-antrun-plugin</artifactId>
          <version>1.8</version>
          <executions>
            <execution>
              <id>stop-vertx-app</id>
              <phase>post-integration-test</phase>
              <goals>
                <goal>run</goal>
              </goals>
              <configuration>
                <target>
                  <exec executable="wmic"
                      dir="${project.build.directory}"
                      spawn="false">
                    <arg value="process"/>
                    <arg value="where"/>
                    <arg value="CommandLine like '%${project.artifactId}%' and not name='wmic.exe'"/>
                    <arg value="delete"/>
                  </exec>
                </target>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

這個profile用適用于Windows系統(tǒng)的版本替換了之前描述的版本來停止應(yīng)用。這個profile在Windows上自動啟用鹦筹。和在其他操作系統(tǒng)上一樣铝阐,執(zhí)行:

mvn clean verify

如果pom.xml配置文件有

Plugin execution not covered by lifecycle configuration: 
org.codehaus.mojo:build-helper-maven-plugin:1.12:reserve-network-port
 (execution: reserve-network-port, phase: process-sources)

這樣的報錯信息。
這是因為m2e對maven的階段支持不好造成的铐拐,具體可以參考m2e-execution-not-covered徘键。具體修正代碼如下:

<pluginManagement>
  <plugins>
    <plugin>
     <groupId>org.eclipse.m2e</groupId>
     <artifactId>lifecycle-mapping</artifactId>
     <version>1.0.0</version>
     <configuration>
       <lifecycleMappingMetadata>
         <pluginExecutions>
           <pluginExecution>
             <pluginExecutionFilter>
               <groupId>org.codehaus.mojo</groupId>
               <artifactId>build-helper-maven-plugin</artifactId>
               <versionRange>[${build-helper.maven-plugin.version},)</versionRange>
               <goals>
                 <goal>reserve-network-port</goal>
               </goals>
             </pluginExecutionFilter>
             <action>
               <ignore/>
             </action>
           </pluginExecution>
         </pluginExecutions>
       </lifecycleMappingMetadata>
     </configuration>
    </plugin>
  </plugins>
</pluginManagement>

<article class="col-xs-12 blog-post">

<article>

結(jié)論

我們完成了...在這個帖子中,我們看到通過實現(xiàn)單元測試和集成測試遍蟋,我們對自己的Vert.x應(yīng)用更有信心了吹害。單元測試,由于vert.x-unit虚青,能夠檢測Vert.x應(yīng)用的異步特性它呀,但在復(fù)雜場景下可能太復(fù)雜。感謝Rest Assured和AssertJ挟憔,集成測試寫起來簡單很多...但是準備過程不夠直觀钟些。這篇帖子展示了如何配置集成測試環(huán)境。很明顯绊谭,你也能夠在單元測試中使用AssertJ和Rest Assured政恍。

next post中,我們用一個數(shù)據(jù)庫來取代內(nèi)存后端达传,并和數(shù)據(jù)庫進行異步集成篙耗。

敬請期待!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宪赶,隨后出現(xiàn)的幾起案子宗弯,更是在濱河造成了極大的恐慌,老刑警劉巖搂妻,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙保,死亡現(xiàn)場離奇詭異,居然都是意外死亡欲主,警方通過查閱死者的電腦和手機邓厕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門逝嚎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人详恼,你說我怎么就攤上這事补君。” “怎么了昧互?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵挽铁,是天一觀的道長。 經(jīng)常有香客問我敞掘,道長叽掘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任渐逃,我火速辦了婚禮够掠,結(jié)果婚禮上民褂,老公的妹妹穿的比我還像新娘茄菊。我一直安慰自己,他們只是感情好赊堪,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布面殖。 她就那樣靜靜地躺著,像睡著了一般哭廉。 火紅的嫁衣襯著肌膚如雪脊僚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天遵绰,我揣著相機與錄音辽幌,去河邊找鬼。 笑死椿访,一個胖子當著我的面吹牛乌企,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播成玫,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼加酵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了哭当?” 一聲冷哼從身側(cè)響起猪腕,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钦勘,沒想到半個月后陋葡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡彻采,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年腐缤,在試婚紗的時候發(fā)現(xiàn)自己被綠了朵栖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡柴梆,死狀恐怖陨溅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绍在,我是刑警寧澤门扇,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站偿渡,受9級特大地震影響臼寄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜溜宽,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一吉拳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧适揉,春花似錦留攒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至剪侮,卻和暖如春拭宁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓣俯。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工杰标, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彩匕。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓腔剂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親推掸。 傳聞我的和親對象是個殘疾皇子桶蝎,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

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