手把手教你搭建数据驱动测试框架

前言

在自动化测试框架中,数据驱动的意思指定的是测试用例或者说测试套件是由外部数据集合来驱动的框架。这里说的数据集可以是任何类型的数据文件比如xls,xlsx,csv等等。它的核心的思想就是数据和测试代码分离,及时当测试数据发生大量变化的情况下测试代码(或者说测试用例)可以保持不变。数据集合里有1条数据和100条甚至更多,是不会影响到测试用例的执行。如果想使用一组不同的数据来执行的相同的操作没那么你就可以选择数据驱动。

比如,在这么一种场景下,例如你需要用多个不同的账号和密码来登陆某个邮箱,来验证哪些是有效的值,哪些是错误的值,或者哪些值可以导致出错,这只是一个简单的例子。另外一个简单的例子就是网络电话的测试,当我们模拟拨号、挂断、回拨等等操作时我们希望验证每个按钮是否能正常工作并能得到正确的结果。

本篇文章是基于Selenium WebDriver的数据驱动测试框架,如果大家跟着一步一步搭建下来,最终的框架将最终实现下面的这些功能:

1、可以在测试套件列表里设置指定的测试套件是否执行

2、可以在测试用例列表里设置指定的测试用例是否执行

3、可以在测试数据表里设置某行测试数据是否被读取执行

4、一个测试用例可以由多个测试数据驱动运行

5、可以打印测试结果报告,显示指定数据行执行之后的结果

6、可以打印整体测试结果报告,显示哪些用例执行通过,哪些执行失败、哪些跳过没执行

7、报告可以是testng的形式或者是XSLT

接下来的过程中不会讲太多理论的东西,更注重于实践的步骤,我将一步一步的介绍如何搭建这个框架,演示总个过程。

一、搭建

1、创建工作空间

按照下面步骤在创建 项目工作空间

1.在F盘下创建文件夹backup

2.在backup文件夹下创建Training文件夹

创建完毕就有这样的目录结构F:\backup\Training。然后打开eclipse,选择上面创建的文件夹为工作空间,如下图所示:

2、创建项目

创建一个名为WDDF的项目,我们将使用这个项目来搭建我们的测试框架,创建完毕,包结构如图所示:

3、创建项目的目录结构

创建项目目录结构也就是在项目下创建需要的包和文件夹结构。包结构合理就很容易对项目的资源进行管理而不胡乱。在“WDDF”项目下创建如下包:

com.stta.ExcelFiles: --存放.xsl文件

com.stta.Logging: --存放.log文件

com.stta.property: --存放.property文件

com.stta.SuiteOne: --存放测试套件一相关文件

com.stta.SuiteTwo: --存放测试套件二相关文件

com.stta.TestSuiteBase: --存放基本类文件

com.stta.utility: --存放工具类文件

com.stta.xslt: --存放testng-results.xsl文件

同时创建文件夹:

1.JarFiles: --存放所有需要的相关的jar文件

到此,项目结构就如下图所示:

针对不同的文件类型创建分离的包的是由好处的,这样子可以在我们查找、修改文件的时候很便利,例如你要修稿.xls文件的数据,那么你几可以直接在com.stta.ExcelFiles包里面找,同样的你想查看执行的日志,就可以直接在com.stta.Logging里面查看,非常直观。

好了,到此为止,可以说我们项目的基本初始化工作基本完成。

二、下载需要的Jar包

现在项目结构都创建好,接下来要做的就是下载所有用到的Jar包。这里我会罗列所有需要下载jar包。并一个个下载下来保存到JarFiles文件夹下。

1、Apache POI API

Apache POI API是用来从.xls文件中写入或者读取数据用的,所以必须下载Apache POI API和它依赖相关的jar包,并设置它门在build path中,我们才能使用它。

Apache POI API可以在它的官网直接下载。进去后点击“poi-bin-3.10-FINAL-20140208.tar.gz”。下载完毕后解压,解压目录下和子目录里面的下列文件拷贝到“WDDF"项目的JarFiles文件夹下:

poi-3.10-FINAL-20140208.jar

poi-ooxml-3.10-FINAL-20140208.jar

poi-ooxml-schemas-3.10-FINAL-20140208.jar

xmlbeans-2.3.0.jar (在子目录 ooxml-lib 下)

dom4j-1.6.1.jar (在子目录 ooxml-lib 下)

2、Apache log4j

Apache log4j 是用来记录测试执行期间产生的日志的,我们也需要把它加到build path中。从Apache log4j主页下载Apache log4j jar文件,并拷贝到“WDDF"项目的JarFiles文件夹下:

log4j-1.2.17.jar

3、Selenium WebDriver

如果你使用过Selenium WebDriver,估计就知道使用selenium webdriver需要下载那些jar包了。从Selenium WebDriver主页下载Selenium WebDriver的zip包,解压,并把"selenium-x.xx.x"目录和它的子目录“libs”下的所有jar文件拷贝到“WDDF"项目的JarFiles文件夹下。

与生成XSLT报告相关的jar

为了能够产生交互式的测试报告,我们还需要一些工具的支持。到这个页面下载一个zip包。解压,并把"testng-xslt-1.1.2-master" -> "lib" 下的下列文件拷贝到“WDDF"项目的JarFiles文件夹下:

saxon-8.7.jar

三、与生成XSLT报告相关的jar

拷贝 “testng-xslt-1.1.2-master\src\main\resources"目录下的testng-results.xsl文件到包com.stta.xslt下面。

testng-results.xsl

到此为止,我们的JarFile目录和com.stta.xslt包看起来如下图所示:

四、配置环境

本框架中,我们使用Apache POI API 来从.xls 中读取数据,Apache log4j来记录测试执行过程中产生的日志,而xslt 报告用来产生交互式HTML报告。下载完相关的jar包后,我们就需要把他们加到 项目的build path中。

1.选择邮件点击项目 WDDF,选择 "Build Path" -> "Configure Build Path"

2.点击Libraries->Add external JARs

3.选择上述下载的所有jar包,点添加->OK

这样所有jar就添加到build path中了。查看一下eclipse,会多了一个“Referenced Libraries”,Referenced Libraries下包含所有所需 的jar包:

五、创建类文件

1、创建基类

com.stta.TestSuiteBase下创建SuiteBase.java,这个类用于总个框架测试套件的基类

1.SuiteBase.java

com.stta.SuiteOne这个包用来存放测试套件一的相关的类,创建如下类:

SuiteOneBase.java ---这个类用于测试套件一的基类

SuiteOneCaseOne.java ---这个为测试套件一的用例一

SuiteOneCaseOne.java ---这个为测试套件二的用例二

com.stta.SuiteTwo 这个包用来存放测试套件二相关的类,创建如下类:

SuiteTwoBase.java

SuiteTwoCaseOne.java

SuiteTwoCaseTwo.java

com.stta.utility 包作为测试框架的基本工具包,创建如下两个类:

Read_XLS.java ---读取.xls文件相关更能的类

SuiteUtility.java ---工具类

创建完毕,目录结构如图所示:

 2、创建xls文件

为了简单起见,这次搭建的测试框架只支持.xls文件,通过检索.xls文件,测试框架可以获取要测试套件名称、是否执行的标志,测试用例的名称以及是否执行用例的标志位,而且等测试执行完毕,可以把测试结果.xls文件的末尾。现在先创建如三个.xls文件:

TestSuiteList.xls

SuiteOne.xls

SuiteTwo.xls

创建完,com.stta.ExcelFiles看起来像下面的样子:

下面讲下这三个.xls文件怎么创建:

1、TestSuiteList.xls

TestSuiteList.xls文件只有第一个sheet有数据,把第一个sheet命名为SuitesList,SuitesListl里面有三列分别是SuiteName、SuiteToRun、Skipped/Executed 如图:

2、SuiteOne.xls与SuiteTwo.xls

这两文件里面的表格是一致的,就放在一起说明,这两个.xls文件有三个sheet,第一个是TestCasesList,测试用例列表,指明本测试套有几个测试用例。第二和第三个sheet名称分别是SuiteOneCaseOne和SuiteOneCaseTwo,分别是测试用例一个测试用例二的测试数据。TestCasesList如下图所示,表格也是有三列,分别表示测试用例名称、是否执行、和执行结果 三个

SuiteOneCaseOne和SuiteOneCaseTwo的表格结构如下图所示:

记住一个原则,如下图所示,即eclipse里从测试类的名称、TestCasesList表的测试用例名称和测试用例数据的sheet名称这个三个要保持一致。后续增加任何的测试用例都要遵循这个原则。

六、开始写代码

前面把要用掉的类文件先创建起来了,需要的用到的文件也都准备好了,可以开始写代码了。首先先写框架的工具类来读取数据,因为这些工具类在其他类中频繁用到先写。

Read_XLS.java代码如下

package com.stta.utility;

import java.io.FileInputStream;

import java.io.FileOutputStream;

//import java.io.IOException;

import org.apache.poi.hssf.usermodel.HSSFCell;

import org.apache.poi.hssf.usermodel.HSSFRow;

import org.apache.poi.hssf.usermodel.HSSFSheet;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import org.apache.poi.ss.usermodel.*;

public class Read_XLS {

    public  String filelocation;

    public  FileInputStream ipstr = null;

    public  FileOutputStream opstr =null;

    private HSSFWorkbook wb = null;

    private HSSFSheet ws = null;   


    public Read_XLS(String filelocation) {     

        this.filelocation=filelocation;

        try {

            ipstr = new FileInputStream(filelocation);

            wb = new HSSFWorkbook(ipstr);

            ws = wb.getSheetAt(0);

            ipstr.close();

        } catch (Exception e) {       

            e.printStackTrace();

        }


    }


    //检索 .xls 文件 sheets的行数.

    public int retrieveNoOfRows(String wsName){   

        int sheetIndex=wb.getSheetIndex(wsName);

        if(sheetIndex==-1)

            return 0;

        else{

            ws = wb.getSheetAt(sheetIndex);

            int rowCount=ws.getLastRowNum()+1;     

            return rowCount;       

        }

    }


    //检索.xls文件sheets的列数

    public int retrieveNoOfCols(String wsName){

        int sheetIndex=wb.getSheetIndex(wsName);

        if(sheetIndex==-1)

            return 0;

        else{

            ws = wb.getSheetAt(sheetIndex);

            int colCount=ws.getRow(0).getLastCellNum();       

            return colCount;

        }

    }


    //读取测试套件和测试用例的SuiteToRun and CaseToRun标志

    public String retrieveToRunFlag(String wsName, String colName, String rowName){


        int sheetIndex=wb.getSheetIndex(wsName);

        if(sheetIndex==-1)

            return null;

        else{

            int rowNum = retrieveNoOfRows(wsName);

            int colNum = retrieveNoOfCols(wsName);

            int colNumber=-1;

            int rowNumber=-1;         


            HSSFRow Suiterow = ws.getRow(0);               


            for(int i=0; i<colNum; i++){

                if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){

                    colNumber=i;                   

                }                 

            }


            if(colNumber==-1){

                return "";             

            }



            for(int j=0; j<rowNum; j++){

                HSSFRow Suitecol = ws.getRow(j);               

                if(Suitecol.getCell(0).getStringCellValue().equals(rowName.trim())){

                    rowNumber=j;   

                }                 

            }


            if(rowNumber==-1){

                return "";             

            }


            HSSFRow row = ws.getRow(rowNumber);

            HSSFCell cell = row.getCell(colNumber);

            if(cell==null){

                return "";

            }

            String value = cellToString(cell);

            return value;         

        }         

    }


    //读取测试数据的DataToRun标志.

    public String[] retrieveToRunFlagTestData(String wsName, String colName){


        int sheetIndex=wb.getSheetIndex(wsName);

        if(sheetIndex==-1)

            return null;

        else{

            int rowNum = retrieveNoOfRows(wsName);

            int colNum = retrieveNoOfCols(wsName);

            int colNumber=-1;



            HSSFRow Suiterow = ws.getRow(0);               

            String data[] = new String[rowNum-1];

            for(int i=0; i<colNum; i++){

                if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){

                    colNumber=i;                   

                }                 

            }


            if(colNumber==-1){

                return null;               

            }


            for(int j=0; j<rowNum-1; j++){

                HSSFRow Row = ws.getRow(j+1);

                if(Row==null){

                    data[j] = "";

                }

                else{

                    HSSFCell cell = Row.getCell(colNumber);

                    if(cell==null){

                        data[j] = "";

                    }

                    else{

                        String value = cellToString(cell);

                        data[j] = value;   

                    } 

                }

            }


            return data;           

        }         

    }


    //从测试用例数据sheets读取测试数据.

    public Object[][] retrieveTestData(String wsName){

        int sheetIndex=wb.getSheetIndex(wsName);

        if(sheetIndex==-1)

            return null;

        else{

                int rowNum = retrieveNoOfRows(wsName);

                int colNum = retrieveNoOfCols(wsName);


                Object data[][] = new Object[rowNum-1][colNum-2];


                for (int i=0; i<rowNum-1; i++){

                    HSSFRow row = ws.getRow(i+1);

                    for(int j=0; j< colNum-2; j++){               

                        if(row==null){

                            data[i][j] = "";

                        }

                        else{

                            HSSFCell cell = row.getCell(j);


                            if(cell==null){

                                data[i][j] = "";                           

                            }

                            else{

                                cell.setCellType(Cell.CELL_TYPE_STRING);

                                String value = cellToString(cell);

                                data[i][j] = value;                   

                            }

                        }

                    }             

                }         

                return data;       

        }


    }     



    public static String cellToString(HSSFCell cell){

        int type;

        Object result;

        type = cell.getCellType();         

        switch (type){

            case 0 :

                result = cell.getNumericCellValue();

                break;


            case 1 :

                result = cell.getStringCellValue();

                break;


            default :

                throw new RuntimeException("Unsupportd cell.");       

        }

        return result.toString();

    }


    //在测试数据和测试用例表里写入测试结果

    public boolean writeResult(String wsName, String colName, int rowNumber, String Result){

        try{

            int sheetIndex=wb.getSheetIndex(wsName);

            if(sheetIndex==-1)

                return false;         

            int colNum = retrieveNoOfCols(wsName);

            int colNumber=-1;



            HSSFRow Suiterow = ws.getRow(0);           

            for(int i=0; i<colNum; i++){               

                if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){

                    colNumber=i;                   

                }                 

            }


            if(colNumber==-1){

                return false;             

            }


            HSSFRow Row = ws.getRow(rowNumber);

            HSSFCell cell = Row.getCell(colNumber);

            if (cell == null)

                cell = Row.createCell(colNumber);         


            cell.setCellValue(Result);


            opstr = new FileOutputStream(filelocation);

            wb.write(opstr);

            opstr.close();



        }catch(Exception e){

            e.printStackTrace();

            return false;

        }

        return true;

    }


    //在测试套件表里写入测试结果.

    public boolean writeResult(String wsName, String colName, String rowName, String Result){

        try{

            int rowNum = retrieveNoOfRows(wsName);

            int rowNumber=-1;

            int sheetIndex=wb.getSheetIndex(wsName);

            if(sheetIndex==-1)

                return false;         

            int colNum = retrieveNoOfCols(wsName);

            int colNumber=-1;



            HSSFRow Suiterow = ws.getRow(0);           

            for(int i=0; i<colNum; i++){               

                if(Suiterow.getCell(i).getStringCellValue().equals(colName.trim())){

                    colNumber=i;                   

                }                 

            }


            if(colNumber==-1){

                return false;             

            }


            for (int i=0; i<rowNum-1; i++){

                HSSFRow row = ws.getRow(i+1);             

                HSSFCell cell = row.getCell(0);

                cell.setCellType(Cell.CELL_TYPE_STRING);

                String value = cellToString(cell); 

                if(value.equals(rowName)){

                    rowNumber=i+1;

                    break;

                }

            }     


            HSSFRow Row = ws.getRow(rowNumber);

            HSSFCell cell = Row.getCell(colNumber);

            if (cell == null)

                cell = Row.createCell(colNumber);         


            cell.setCellValue(Result);


            opstr = new FileOutputStream(filelocation);

            wb.write(opstr);

            opstr.close();



        }catch(Exception e){

            e.printStackTrace();

            return false;

        }

        return true;

    }

}

SuiteUtility.java代码:

package com.stta.utility;

public class SuiteUtility {

public static boolean checkToRunUtility(Read_XLS xls, String sheetName, String ToRun, String testSuite){


    boolean Flag = false;     

    if(xls.retrieveToRunFlag(sheetName,ToRun,testSuite).equalsIgnoreCase("y")){

        Flag = true;

    }

    else{

        Flag = false;

    }

    return Flag;       

}

public static String[] checkToRunUtilityOfData(Read_XLS xls, String sheetName, String ColName){   

    return xls.retrieveToRunFlagTestData(sheetName,ColName);           

}

public static Object[][] GetTestDataUtility(Read_XLS xls, String sheetName){

    return xls.retrieveTestData(sheetName);

}

public static boolean WriteResultUtility(Read_XLS xls, String sheetName, String ColName, int rowNum, String Result){           

    return xls.writeResult(sheetName, ColName, rowNum, Result);       

}

public static boolean WriteResultUtility(Read_XLS xls, String sheetName, String ColName, String rowName, String Result){           

    return xls.writeResult(sheetName, ColName, rowName, Result);           

}

}

七、简单数据读取测试

测试框架的.xls文件读取工具已经准备好,按照前面创建的测试类,我们要创建两个测试套件,每个测试套件有两个测试用例,总共就是2个测试套件和4个测试用例。对于每个测试套件分别对应两个不同.xls文件。测试套件、测试用例与.xls文件之间的映射关系如下所示:

com.stta.SuiteOne -> SuiteOne.xls

SuiteOneCaseOne.java -> SuiteOneCaseOne Sheet

SuiteOneCaseTwo.java -> SuiteOneCaseTwo Sheet

com.stta.SuiteTwo -> SuiteTwo.xls

SuiteTwoCaseOne.java -> SuiteTwoCaseOne Sheet

SuiteTwoCaseTwo.java -> SuiteTwoCaseTwo Sheet

八、编写测试代码

我们先尝试从SuiteOne.xls文件的SuiteOneCaseOne sheet里读取数据,并驱动测试用例SuiteOneCaseOne.java执行。首先先写SuiteBase.java。

SuiteBase.java

SuiteBase类中创建一个初始化方法,用来初始化.xls文件路径

package com.stta.TestSuiteBase;

import java.io.IOException;

import com.stta.utility.Read_XLS;

public class SuiteBase {

public static Read_XLS TestSuiteListExcel=null;

public static Read_XLS TestCaseListExcelOne=null;

public static Read_XLS TestCaseListExcelTwo=null;

public void init() throws IOException{

//代码中文件路径根据实际情况填写

//使用Read_XLS工具类初始化测试测试套件列表TestSuiteList.xls

TestSuiteListExcel = new Read_XLS(System.getProperty("user.dir")+"\src\com\stta\ExcelFiles\TestSuiteList.xls");

//使用Read_XLS工具类初始化测试套件一SuiteOne.xls

TestCaseListExcelOne = new Read_XLS(System.getProperty("user.dir")+"\src\com\stta\ExcelFiles\SuiteOne.xls");

//使用Read_XLS工具类初始化测试套件二SuiteTwo.xls

TestCaseListExcelTwo = new Read_XLS(System.getProperty("user.dir")+"\src\com\stta\ExcelFiles\SuiteTwo.xls");

}

}

SuiteOneBase.java

SuiteOneBase类在这个步骤中我们还不实现任何功能,仅仅继承了SuiteBase类

package com.stta.SuiteOne;

import com.stta.TestSuiteBase.SuiteBase;

//SuiteOneBase 类继承  SuiteBase 类.

public class SuiteOneBase extends SuiteBase{   

}

SuiteOneCaseOne.java

这是我们的测试类,在这个文件中我们从.xls文件读取数据并打印出来。这个类 包含了下列三个方法:

1.checkCaseToRun()---使用testNG的@BeforeTest标签,它将在所有@Test方法之前被运行,这个方法只要就是调用SuiteBase类的Init() 来初始化.xls文件路径,保存在变量FilePath中。

2.SuiteOneCaseOneTest():这个是测试方法,附带@Test标签,并使用dataProvider 来获取数据

3.SuiteOneCaseOneData():从.xls文件读取数据并返回给SuiteOneCaseOneTest()方法

package com.stta.SuiteOne;

import java.io.IOException;

import org.testng.annotations.BeforeTest;

import org.testng.annotations.DataProvider;

import org.testng.annotations.Test;

import com.stta.utility.Read_XLS;

import com.stta.utility.SuiteUtility;

//SuiteOneCaseOne 类 继承自  SuiteOneBase 类.

public class SuiteOneCaseOne extends SuiteOneBase{

Read_XLS FilePath = null; 

String TestCaseName = null;

@BeforeTest

public void checkCaseToRun() throws IOException{

    //调用SuiteBase类的init()来初始化.xls文件

    init();

    FilePath = TestCaseListExcelOne;

    System.out.println("FilePath Is : "+FilePath);

    TestCaseName = this.getClass().getSimpleName();

    System.out.println("TestCaseName Is : "+TestCaseName);

}

//在每个迭代中接收4列的数据.

@Test(dataProvider="SuiteOneCaseOneData")

public void SuiteOneCaseOneTest(String DataCol1,String DataCol2,String DataCol3,String ExpectedResult){

    System.out.println("Value Of DataCol1 = "+DataCol1);

    System.out.println("Value Of DataCol2 = "+DataCol2);

    System.out.println("Value Of DataCol3 = "+DataCol3);

    System.out.println("Value Of ExpectedResult = "+ExpectedResult);       

//data provider回在每个迭代中一个一个返回4列数据

@DataProvider

public Object[][] SuiteOneCaseOneData(){

    //To retrieve data from Data 1 Column,Data 2 Column,Data 3 Column and Expected Result column of SuiteOneCaseOne data Sheet.

    //Last two columns (DataToRun and Pass/Fail/Skip) are Ignored programatically when reading test data.

    return SuiteUtility.GetTestDataUtility(FilePath, TestCaseName);

    }

}

九、运行数据读取测试

到此为止,我们简单的数据读取测试已经准备完毕,可以试运行一下确保没问题,再继续往下。在SuiteOneCaseOne.java文件点击右键选择 Run As -> TestNG 。如果你是按照前述的步骤一步一步来的话,测试运行时没问题的。因为SuiteOneCaseOne数据sheet里有两行数据,所以@Test将会被执行两次。执行完,就可以在控制台看到如下信息:

如果你看到上述消息,说明到目前为止我们搭建的基于数据驱动的自动化是框架暂时是没有问题的,已经可以成功简单的数据读入测试。

创建了一个测试交流群,如果对软件测试、接口测试、自动化测试、面试经验交流感兴趣可以加测试交流群:829792258,还会有同行一起技术交流

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