SpringMVC+MockMvc+dbUnit+cobertura构建测试框架

本文基于SpringMVC搭建测试框架
通常情况我们可以借助easyMock及powerMock进行单元测试,但有时我们希望进行集成测试,可以通过发送http请求,测试某功能的完整性。
一般情况我们可以通过MockMvc模拟post或get请求,完成测试。但是当碰到delete或update进行测试时,容易对数据库造成污染,这时我们可以借助dbunit,对数据库进行测试数据的准备,测试完成后对事务进行回滚,方便下次测试。
1. maven集成测试组件
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<!--数据库单元测试:消除单元测试对数据库的污染 start-->
<dependency>
    <groupId>com.github.springtestdbunit</groupId>
    <artifactId>spring-test-dbunit</artifactId>
    <version>1.1.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.dbunit</groupId>
    <artifactId>dbunit</artifactId>
    <version>2.4.9</version>
    <scope>test</scope>
</dependency>
<!--数据库单元测试:消除单元测试对数据库的污染 end-->
2. 定义测试基类,包括SpringMVC框架的集成,junit集成
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@PropertySource("classpath:common.properties")
@TestPropertySource("classpath:common.properties")
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class,
        ServletTestExecutionListener.class,
        DbUnitTestExecutionListener.class}) //@1
@ContextConfiguration(
        {"classpath*:/spring-context.xml", "classpath*:/spring-mvc.xml", "classpath*:/spring-mybatis.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)//@2
@Transactional 
public abstract class BaseSpringJUnitTest {

    public static Logger logger = LoggerFactory.getLogger(BaseSpringJUnitTest.class);

    @Autowired
    private UserInfoService userInfoService;


    private static boolean inited = false;

    /**
     * junit模拟用户名
     */
    private final static String USER_NAME = "admin";

    /**
     * junit模拟验证码
     */
    private final static String VALIDATE_CODE = "1234";

    /**
     * junit模拟密码
     */
    private final static String PASSWORD = "Admin123456";

    public static String token = "";

    protected MockMvc mockMvc; //@3

    @Before //@4
    public void setUp() throws Exception {
        if (!inited) {
            String code = userInfoService.getValidateKey().get("validateKey").toString();
            RedisUtils.set("validCode:" + code, VALIDATE_CODE);
            UserInfoRequestParams params = new UserInfoRequestParams();
            params.setLoginName(USER_NAME);
            params.setPassword(MD5Util.encoderHexByMd5(PASSWORD));
            params.setValidateKey(code);
            params.setValidateCode(VALIDATE_CODE);

            JSONOutputObject result = userInfoService.webLogin(params);
            token = result.get("token").toString();
            TestCase.assertEquals(RsmsConstant.RESULT_SUCCESS_CODE, result.get(RsmsConstant.RESULT_CODE));
            inited = true;
        }
    }

}

@1:ServletTestExecutionListener 用于设置spring web框架启动时的RequestContextHolder本地线程变量。我们的项目比较特殊,在service层中是通过RequestContextHolder获取httpRequest对象(并非通过controller透传),如果不设置,则在service中或者切面中无法获取到request对象

@2:添加事务回滚,避免对数据库的污染

@3:定义MockMvc对象,模拟客户端的http请求

@4:初始化登录信息,根据自身需要设置,有些集成测试场景可能需要登录信息

3.编写测试类
public class UserInfoControllerTest extends BaseSpringJUnitTest {

    @Test
    @DatabaseSetup(type = DatabaseOperation.INSERT, value = {"/data/user-info.xml"})//@1
    public void testDeleteUsers() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
        //@2
        String responseString = mockMvc.perform(post("/user/deleteUsers")
                .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .content("{\"idList\":10007}")
                .header("Current-Menu-Id", "102100")
                .header("Menu-Id", "102100")
                .accept(MediaType.ALL_VALUE)
                .header("token", token)
                .header("User-Agent", "Windows NT")
        ).andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        JSONObject jsonObject = JSONObject.parseObject(responseString);
        Assert.assertEquals(jsonObject.get("resultCode"), "0000");
    }
}

@1:准备测试数据,因为在测试时,如果不自己准备数据,依赖数据库数据,那么数据库数据有可能被其他人误删或者数据库做了迁移或更新之后,我们的测试用例将无法跑通。

@2:通过mockMvc发送http请求,并解析请求结果,判断测试结果的正确性

4.准备测试数据(resource/data/user_info.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
    <t_user_info id="10007" login_name="admin_junit" password="" salt="" user_name="admin_junit" user_name_spell=""
              user_name_initial="" eid="" cellphone="" company_id="200" org_id="200" position=""/>
    <t_user_role id="1000" user_id="10007" role_id="1" />
</dataset>
5.jenkins集成,并统计单元测试覆盖率
<!-- 统计junit覆盖率 -->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.1</version>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>
jenkins添加普通的maven构建任务,设置好git地址

插件管理中安装jacoco插件
build项设置如下
Root POM pom.xml
Goals and options test -Dmaven.repo.local=/opt/repository
构建后操作添加:Record JaCoCo coverage report

统计结果效果如下
单元测试覆盖率统计
6.配置邮件发送

邮件发送本人是集成了cobertura(和jacoco共一样,都是用于覆盖率的统计)

pom集成

<!-- cobertura统计junit覆盖率 -->
          <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.7</version>
                <configuration>
                    <formats>
                        <format>html</format>
                        <format>xml</format>
                    </formats>
                </configuration>
            </plugin>

jenkins全局配置:jenkins->系统管理->系统设置
系统管理员地址:*********@qq.com
Extended E-mail Notification中配置如下
SMTP:smtp.qq.com
后缀@qq.com
点开高级
使用SMTP认证
用户名:*********@qq.com
密码:qq给的授权码(非邮箱的登录密码,授权码的获取:登录QQ邮箱:设置-SMTP设置-开启,需要发送短信,发送短信后,页面会显示授权码)

jenkins构建任务配置邮件发送
构建后操作:增加Editable Email Notification,点开高级,一定要设置triggers,否则无法触发

配置coberture插件.png

邮件发送.png

邮件发送我用的是jelly模板,使用的时候只需要填写模板文件名称(.jelly后缀不需要),模板文件放在jenkins服务器上,具体模板代码见附件一,见下图


邮件模板位置.png

配置trigger.png
7.邮件发送最终效果

邮件主要内容:构建状态、覆盖率整体情况、包覆盖率情况、UT趋势、变更集等等


邮件效果.png
附件一:jelly邮件模板
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
  <html>
    <head>
      <title>${project.name}</title>
      <style>
        body table, td, th, p, h1, h2 {
        margin:0;
        font:normal normal
        100% Georgia, Serif;
        background-color: #ffffff;
        }
        h1, h2 {
        border-bottom:dotted 1px #999999;
        padding:5px;
        margin-top:10px;
        margin-bottom:10px;
        color: #000000;
        font: normal bold 130%
        Georgia,Serif;
        background-color:#f0f0f0;
        }
        tr.gray {
        background-color:#f0f0f0;
        }
        h2 {
        padding:5px;
        margin-top:5px;
        margin-bottom:5px;
        font: italic bold 110% Georgia,Serif;
        }
        .bg2 {
        color:black;
        background-color:#E0E0E0;
        font-size:110%
        }
        th {
        font-weight: bold;
        }
        tr, td, th {
        padding:2px;
        }
        td.test_passed {
        color:blue;
        }
        td.test_failed {
        color:red;
        }
        td.center {
          text-align: center;
        }
        td.test_skipped {
        color:grey;
        }
        .console {
        font: normal normal 90% Courier New,
        monotype;
        padding:0px;
        margin:0px;
        }
        div.content, div.header {
        background: #ffffff;
        border: dotted
        1px #666;
        margin: 2px;
        content:
        2px;
        padding: 2px;
        }
        table.border, th.border, td.border {
        border:
        1px solid black;
        border-collapse:collapse;
        }
      </style>
    </head>
    <body>
      <div class="header">
        <j:set var="spc" value="&amp;nbsp;&amp;nbsp;" />
        <!-- GENERAL INFO -->
        <table>
          <tr class="gray">
            <td align="right">
              <j:choose>
                <j:when test="${build.result=='SUCCESS'}">
                  <img src="${rooturl}static/e59dfe28/images/32x32/blue.gif" />
                </j:when>
                <j:when test="${build.result=='FAILURE'}">
                  <img src="${rooturl}static/e59dfe28/images/32x32/red.gif" />
                </j:when>
                <j:otherwise>
                  <img
                    src="${rooturl}static/e59dfe28/images/32x32/yellow.gif" />
                </j:otherwise>
              </j:choose>
            </td>
            <td valign="center">
              <b style="font-size: 200%;">BUILD ${build.result}</b>
            </td>
          </tr>
          <tr>
            <td>构建地址</td>
            <td>
              <a href="${rooturl}${build.url}">${rooturl}${build.url}</a>
            </td>
          </tr>
          <tr>
            <td>项  目:</td>
            <td>${project.name}</td>
          </tr>
          <tr>
            <td>构建日期:</td>
            <td>${it.timestampString}</td>
          </tr>
          <tr>
            <td>构建时长:</td>
            <td>${build.durationString}</td>
          </tr>
          <tr>
            <td>Build cause:</td>
            <td>
              <j:forEach var="cause" items="${build.causes}">${cause.shortDescription}
              </j:forEach>
            </td>
          </tr>
          <tr>
            <td>Build description:</td>
            <td>${build.description}</td>
          </tr>
          <tr>
            <td>Built on:</td>
            <td>
              <j:choose>
                <j:when test="${build.builtOnStr!=''}">${build.builtOnStr}</j:when>
                <j:otherwise>master</j:otherwise>
              </j:choose>
            </td>
          </tr>
        </table>
      </div>
      
      <!-- COBERTURA TEMPLATE -->
      <j:set var="coberturaAction" value="${it.coberturaAction}" />
      <j:if test="${coberturaAction!=null}">
        <div class="content">
          <j:set var="coberturaResult" value="${coberturaAction.result}" />
          <j:if test="${coberturaResult!=null}">
            
            <a href="${rooturl}${build.url}/cobertura">
              <h1>Cobertura报告</h1>
            </a>
            <h2>趋势</h2>
            <img src="http://*.*.*.*:****/${build.url}../cobertura/graph" width="600" height="200px" />
            <h2>项目覆盖率汇总</h2>
            <table class="border">
              <tr>
                <th class="border">Name</th>
                <j:forEach var="metric" items="${coberturaResult.metrics}">
                  <th class="border">${metric.name}</th>
                </j:forEach>
              </tr>
              <tr>
                <td class="border">${coberturaResult.name}</td>
                <j:forEach var="metric" items="${coberturaResult.metrics}">
                  <!--
                  <td class="border"
                    data="${coberturaResult.getCoverage(metric).percentageFloat}">${coberturaResult.getCoverage(metric).percentage}%
                    (${coberturaResult.getCoverage(metric)})
                  </td>
                  -->
                  <td class="border">
                      <div style="background-color:#ff9090;width:100px;height:20px;">
                        <div style="background-color:#80ff80;width: ${coberturaResult.getCoverage(metric).percentage}px;height:20px;">
                            <span style="text-align:center;position:absolute;width:100px;" vertical-align="middle">
                                ${coberturaResult.getCoverage(metric).percentage}%(${coberturaResult.getCoverage(metric)})
                            </span>
                        </div>
                      </div>
                  </td>
                </j:forEach>
              </tr>
            </table>
            
            <j:if test="${coberturaResult.sourceCodeLevel}">
              <h2>Source</h2>
              <j:choose>
                <j:when test="${coberturaResult.sourceFileAvailable}">
                  <div style="overflow-x:scroll;">
                    <table class="source">
                      <thead>
                        <tr>
                          <th colspan="3">${coberturaResult.relativeSourcePath}
                          </th>
                        </tr>
                      </thead>
                      ${coberturaResult.sourceFileContent}
                    </table>
                  </div>
                </j:when>
                <j:otherwise>
                  <p>
                    <i>Source code is unavailable</i>
                  </p>
                </j:otherwise>
              </j:choose>
            </j:if>
            
            <j:forEach var="element" items="${coberturaResult.childElements}">
              <j:set var="childMetrics"
                value="${coberturaResult.getChildMetrics(element)}" />
              <h2>Coverage Breakdown by ${element.displayName}</h2>
              <table class="border">
                <tr>
                  <th class="border">Name</th>
                  <j:forEach var="metric" items="${childMetrics}">
                    <th class="border">${metric.name}</th>
                  </j:forEach>
                </tr>
                <j:forEach var="c" items="${coberturaResult.children}">
                  <j:set var="child" value="${coberturaResult.getChild(c)}" />
                  <tr>
                    <td class="border">
                      ${child.xmlTransform(child.name)}
                    </td>
                    <j:forEach var="metric" items="${childMetrics}">
                      <j:set var="childResult" value="${child.getCoverage(metric)}" />
                      <j:choose>
                        <j:when test="${childResult!=null}">
                          <!--
                          <td class="border" data="${childResult.percentageFloat}">${childResult.percentage}%
                            (${childResult})
                          </td>
                          -->
                          <td class="border" data="${childResult.percentageFloat}">
                              <div style="background-color:#ff9090;width:100px;height:20px;">
                                <div style="background-color:#80ff80;width: ${childResult.percentage}px;height:20px;">
                                    <span style="text-align:center;position:absolute;width:100px;">
                                        ${childResult.percentage}%(${childResult})
                                    </span>
                                </div>
                              </div>
                          </td>
                        </j:when>
                        <j:otherwise>
                          <td class="border" data="101">N/A</td>
                        </j:otherwise>
                      </j:choose>
                    </j:forEach>
                  </tr>
                </j:forEach>
              </table>
            </j:forEach>
          </j:if>
          <br />
        </div>
      </j:if>
      
      <!-- 覆盖率趋势统计 -->
      <!--
      <j:set var="coberturaAction" value="${it.coberturaAction}" />
      <j:if test="${coberturaAction!=null}">
        <div class="content">
          <j:set var="coberturaResult" value="${coberturaAction.result}" />
          <j:if test="${coberturaResult!=null}">
            <a href="${rooturl}${build.url}/cobertura">
              <h1>覆盖率趋势</h1>
            </a>
            
            <j:forEach var="metric" items="${coberturaResult.metrics}">
                <table style="padding: 0 10px; width:480px;">
                    <tbody>
                    <tr>
                        <th align="left">${metric.name}</th>
                        <td align="right"
                            data="${coberturaResult.getCoverage(metric).percentageFloat}">${coberturaResult.getCoverage(metric).percentage}%
                            (${coberturaResult.getCoverage(metric)})
                        </td>
                    </tr>
                    </tbody>
                </table>
                <table style="height: 3px; padding: 0 10px; width:480px;">
                    <tbody>
                    <tr>
                        <td width="${coberturaResult.getCoverage(metric).percentage}%" height="20" style="background-color:#bfb;">
                        </td>
                        <td style="background-color:#fdd;">
                        </td>
                    </tr>
                    </tbody>
                </table>
            </j:forEach>

            
          </j:if>
          <br />
        </div>
      </j:if>
      -->
      
      <!-- HEALTH TEMPLATE -->
      <div class="content">
        <j:set var="healthIconSize" value="16x16" />
        <j:set var="healthReports" value="${project.buildHealthReports}" />
        <j:if test="${healthReports!=null}">
          <h1>健康报告</h1>
          <table>
            <tr>
              <th>W</th>
              <th>描述</th>
              <th>分数</th>
            </tr>
            <j:forEach var="healthReport" items="${healthReports}">
              <tr>
                <td>
                  <img
                    src="${rooturl}${healthReport.getIconUrl(healthIconSize)}" />
                </td>
                <td>${healthReport.description}</td>
                <td>${healthReport.score}</td>
              </tr>
            </j:forEach>
          </table>
          <br />
        </j:if>
      </div>

      <!-- CHANGE SET -->
      <div class="content">
        <j:set var="changeSet" value="${build.changeSet}" />
        <j:if test="${changeSet!=null}">
          <j:set var="hadChanges" value="false" />
          <a href="${rooturl}${build.url}/changes">
            <h1>变更集</h1>
          </a>
          <j:forEach var="cs" items="${changeSet.logs}"
            varStatus="loop">
            <j:set var="hadChanges" value="true" />
            <h2>${cs.msgAnnotated}</h2>
            <p>
              by
              <em>${cs.author}</em>
            </p>
            <table>
              <j:forEach var="p" items="${cs.affectedFiles}">
                <tr>
                  <td width="10%">${spc}${p.editType.name}</td>
                  <td>
                    <tt>${p.path}</tt>
                  </td>
                </tr>
              </j:forEach>
            </table>
          </j:forEach>
          <j:if test="${!hadChanges}">
            <p>无</p>
          </j:if>
          <br />
        </j:if>
      </div>

      <!-- ARTIFACTS -->
      <j:set var="artifacts" value="${build.artifacts}" />
      <j:if test="${artifacts!=null and artifacts.size()&gt;0}">
        <div class="content">
          <h1>Build Artifacts</h1>
          <ul>
            <j:forEach var="f" items="${artifacts}">
              <li>
                <a href="${rooturl}${build.url}artifact/${f}">${f}</a>
              </li>
            </j:forEach>
          </ul>
        </div>
      </j:if>

      <!-- MAVEN ARTIFACTS -->
      <j:set var="mbuilds" value="${build.moduleBuilds}" />
      <j:if test="${mbuilds!=null}">
        <div class="content">
          <h1>Build Artifacts</h1>
          <j:forEach var="m" items="${mbuilds}">
            <h2>${m.key.displayName}</h2>
            <j:forEach var="mvnbld" items="${m.value}">
              <j:set var="artifacts" value="${mvnbld.artifacts}" />
              <j:if test="${artifacts!=null and artifacts.size()&gt;0}">
                <ul>
                  <j:forEach var="f" items="${artifacts}">
                    <li>
                      <a href="${rooturl}${mvnbld.url}artifact/${f}">${f}</a>
                    </li>
                  </j:forEach>
                </ul>
              </j:if>
            </j:forEach>
          </j:forEach>
          <br />
        </div>
      </j:if>
      <!-- JUnit TEMPLATE -->
      <j:set var="junitResultList" value="${it.JUnitTestResult}" />
      <j:if test="${junitResultList.isEmpty()!=true}">
        <div class="content">
          <a href="${rooturl}${build.url}/testReport">
            <h1>单元测试</h1>
          </a>
          <table class="border">
            <tr>
              <th class="border">包路径</th>
              <th class="border">失败</th>
              <th class="border">通过</th>
              <th class="border">跳过</th>
              <th class="border">总计</th>
            </tr>
            <j:forEach var="junitResult" items="${it.JUnitTestResult}">
              <j:forEach var="packageResult" items="${junitResult.getChildren()}">
                <tr>
                  <td class="border">
                    <tt>${packageResult.getName()}</tt>
                  </td>
                  <td class="border test_failed">${packageResult.getFailCount()}</td>
                  <td class="border test_passed">${packageResult.getPassCount()}</td>
                  <td class="border test_skipped">${packageResult.getSkipCount()}</td>
                  <td class="border">
                    <b>${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()}
                    </b>
                  </td>
                </tr>
                <j:forEach var="failed_test"
                  items="${packageResult.getFailedTests()}">
                  <tr>
                    <td class="test_failed" colspan="5">
                      <tt>${failed_test.getFullName()}</tt>
                    </td>
                  </tr>
                </j:forEach>
              </j:forEach>
            </j:forEach>
          </table>
          <br />
        </div>
      </j:if>


      <!-- Static Analysis -->
      <j:set var="actions" value="${it.staticAnalysisActions}" />
      <j:if test="${!actions.isEmpty()}">
        <div class="content">
          <h1>Static Analysis Results</h1>
          <table>
            <tr>
              <th></th>
              <th>Name</th>
              <th>Result</th>
              <th>Total</th>
              <th>High</th>
              <th>Normal</th>
              <th>Low</th>
            </tr>
            <j:forEach var="action" items="${actions}">
              <tr>
                <td>
                  <img src="${rooturl}${action.smallImageName}" />
                </td>
                <td>
                  <a href="${rooturl}${build.url}/${action.urlName}">${action.displayName}</a> 
                </td>
                <td class="center">
                  <j:choose>
                    <j:when test="${action.result.pluginResult=='SUCCESS'}">
                      <img src="${rooturl}static/e59dfe28/images/16x16/blue.gif" />
                    </j:when>
                    <j:when test="${action.result.pluginResult=='FAILURE'}">
                      <img src="${rooturl}static/e59dfe28/images/16x16/red.gif" />
                    </j:when>
                    <j:otherwise>
                      <img src="${rooturl}static/e59dfe28/images/16x16/yellow.gif" />
                    </j:otherwise>
                  </j:choose>
                </td>
                <td class="center">${action.result.numberOfAnnotations} </td>
                <td class="center">${action.result.getNumberOfAnnotations('HIGH')} </td>
                <td class="center">${action.result.getNumberOfAnnotations('NORMAL')} </td>
                <td class="center">${action.result.getNumberOfAnnotations('LOW')} </td>
              </tr>
            </j:forEach>
          </table>
        </div>
      </j:if>
    </body>
  </html>
</j:jelly>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容