基于spring-boot的應(yīng)用程序的單元測試方案

概述

本文主要介紹如何對基于spring-boot的web應(yīng)用編寫單元測試、集成測試的代碼。

此類應(yīng)用的架構(gòu)圖一般如下所示:

web-structure.png

我們項(xiàng)目的程序,對應(yīng)到上圖中的web應(yīng)用部分。這部分一般分為Controller層轴合、service層、持久層碗短。除此之外受葛,應(yīng)用程序中還有一些數(shù)據(jù)封裝類,我們稱之為domain。上述各組件的職責(zé)如下:

  • Controller層/Rest接口層: 負(fù)責(zé)對外提供Rest服務(wù)总滩,接收Rest請求纲堵,返回處理結(jié)果。
  • service層: 業(yè)務(wù)邏輯層闰渔,根據(jù)Controller層的需要席函,實(shí)現(xiàn)具體的邏輯。
  • 持久層: 訪問數(shù)據(jù)庫冈涧,進(jìn)行數(shù)據(jù)的讀寫茂附。向上支撐service層的數(shù)據(jù)庫訪問需求。

在Spring環(huán)境中督弓,我們通常會把這三層注冊到Spring容器营曼,上圖中使用淺藍(lán)色背景就是為了表示這一點(diǎn)。

在本文的后續(xù)內(nèi)容咽筋,我們將介紹如何對應(yīng)用進(jìn)行集成測試溶推,包括啟動web容器的請求測試、不啟動web容器而使用模擬環(huán)境的測試奸攻;介紹如何對應(yīng)用進(jìn)行單元測試,包括單獨(dú)測試Controller層虱痕、service層睹耐、持久層。

集成測試和單元測試的區(qū)別是部翘,集成測試通常只需要測試最上面一層硝训,因?yàn)樯蠈訒詣诱{(diào)用下層,所以會測試完整的流程鏈新思,流程鏈中每一個(gè)環(huán)節(jié)都是真實(shí)窖梁、具體的。單元測試是單獨(dú)測試流程鏈中的某一環(huán)夹囚,這一個(gè)環(huán)所直接依賴的下游環(huán)節(jié)使用模擬的方式來提供支撐纵刘,這一技術(shù)稱為Mock。在介紹單元測試的時(shí)候荸哟,我們會介紹如何mock依賴對象假哎,并簡單對mock的原理進(jìn)行介紹。

本文所關(guān)注的另一個(gè)主題鞍历,是在持久層測試時(shí)舵抹,如何消除修改數(shù)據(jù)庫的副作用。

集成測試

集成測試是在所有組件都已經(jīng)開發(fā)完成之后劣砍,進(jìn)行組裝測試惧蛹。有兩種測試方式:啟動web容器進(jìn)行測試,使用模擬環(huán)境測試。這兩種測試的效果沒有什么差別香嗓,只是使用模擬環(huán)境測試的話爵政,可以不用啟動web容器,從而會少一些開銷陶缺。另外钾挟,兩者的測試API會有所不同。

啟動web容器進(jìn)行測試

我們通過測試最上層的Controller來實(shí)施集成測試饱岸,我們的測試目標(biāo)如下:

@RestController
public class CityController {

    @Autowired
    private CityService cityService;

    @GetMapping("/cities")
    public ResponseEntity<?> getAllCities() {
        List<City> cities = cityService.getAllCities();
        return ResponseEntity.ok(cities);
    }
}

這是一個(gè)Controller掺出,它對外提供一個(gè)服務(wù)/cities,返回一個(gè)包含所有城市的列表苫费。這個(gè)Controller通過調(diào)用下一層的CityService來完成自己的職責(zé)汤锨。

針對這個(gè)Controller的集成測試方案如下:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CityControllerWithRunningServer {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void getAllCitiesTest() {
        String response = restTemplate.getForObject("/cities", String.class);
        Assertions.assertThat(response).contains("San Francisco");
    }
}

首先我們使用@RunWith(SpringRunner.class)聲明在Spring的環(huán)境中進(jìn)行單元測試,這樣Spring的相關(guān)注解才會被識別并起效百框。然后我們使用@SpringBootTest闲礼,它會掃描應(yīng)用程序的spring配置,并構(gòu)建完整的Spring Context铐维。我們?yōu)槠鋮?shù)webEnvironment賦值為SpringBootTest.WebEnvironment.RANDOM_PORT柬泽,這樣就會啟動web容器,并監(jiān)聽一個(gè)隨機(jī)的端口嫁蛇,同時(shí)锨并,為我們自動裝配一個(gè)TestRestTemplate類型的bean來輔助我們發(fā)送請求。

使用模擬環(huán)境測試

測試的目標(biāo)不變睬棚,測試的方案如下:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class CityControllerWithMockEnvironment {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void getAllCities() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/cities"))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("San Francisco")));
    }
}

我們依然使用@SpringBootTest第煮,但是沒有設(shè)置其webEnvironment屬性,這樣依然會構(gòu)建完整的Spring Context抑党,但是不會再啟動web容器包警。為了進(jìn)行測試,我們需要使用MockMvc實(shí)例發(fā)送請求底靠,而我們使用@AutoConfigureMockMvc則是因?yàn)檫@樣可以獲得自動配置的MockMvc實(shí)例害晦。

具體測試的代碼中出現(xiàn)很多新的API,對于API細(xì)節(jié)的研究不在本文計(jì)劃范圍內(nèi)苛骨。

單元測試

上文中描述的兩種集成測試的方案篱瞎,相同的一點(diǎn)是都會構(gòu)建整個(gè)Spring Context。這表示所有聲明的bean痒芝,而不管聲明的方式為何俐筋,都會被構(gòu)建實(shí)例,并且都能被依賴严衬。這里隱含的意思是從上到下整條依賴鏈上的代碼都已實(shí)現(xiàn)澄者。

Mock技術(shù)

在開發(fā)的過程中進(jìn)行測試,無法滿足上述的條件,Mock技術(shù)可以讓我們屏蔽掉下層的依賴粱挡,從而專注于當(dāng)前的測試目標(biāo)赠幕。Mock技術(shù)的思想是,當(dāng)測試目標(biāo)的下層依賴的行為是可預(yù)期的询筏,那么測試目標(biāo)本身的行為也是可預(yù)期的榕堰,測試就是把實(shí)際的結(jié)果和測試目標(biāo)的預(yù)期結(jié)果做比較,而Mock就是預(yù)先設(shè)定下層依賴的行為表現(xiàn)嫌套。

Mock的流程

  1. 將測試目標(biāo)的依賴對象進(jìn)行mock逆屡,設(shè)定其預(yù)期的行為表現(xiàn)。
  2. 對測試目標(biāo)進(jìn)行測試踱讨。
  3. 檢測測試結(jié)果魏蔗,檢查在依賴對象的預(yù)期行為下,測試目標(biāo)的結(jié)果是否符合預(yù)期痹筛。

Mock的使用場景

  1. 多人協(xié)作時(shí)莺治,可以通過mock進(jìn)行無等待的測試先行。
  2. 當(dāng)測試目標(biāo)的依賴對象需要訪問外部的服務(wù)帚稠,而外部服務(wù)不易獲得時(shí)谣旁,可以通過mock來模擬服務(wù)可用。
  3. 當(dāng)在排查不容易復(fù)現(xiàn)的問題場景時(shí)翁锡,通過mock來模擬問題蔓挖。

測試web層

測試的目標(biāo)不變,測試的方案如下:

/**
 * 不構(gòu)建整個(gè)Spring Context馆衔,只構(gòu)建指定的Controller進(jìn)行測試。需要對相關(guān)的依賴進(jìn)行mock.<br>
 * Created by lijinlong9 on 2018/8/22.
 */
@RunWith(SpringRunner.class)
@WebMvcTest(CityController.class)
public class CityControllerWebLayer {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private CityService service;

    @Test
    public void getAllCities() throws Exception {

        City city = new City();
        city.setId(1L);
        city.setName("杭州");
        city.setState("浙江");
        city.setCountry("中國");

        Mockito.when(service.getAllCities()).thenReturn(Collections.singletonList(city));

        mvc.perform(MockMvcRequestBuilders.get("/cities"))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("杭州")));
    }
}

這里不再使用@SpringBootTest怨绣,而代之以@WebMvcTest角溃,這樣只會構(gòu)建web層或者指定的一到多個(gè)Controller的bean。@WebMvcTest同樣可以為我們自動配置MockMvc類型的bean篮撑,我們可以使用它來模擬發(fā)送請求减细。

@MockBean是一個(gè)新接觸的注解,它表示對應(yīng)的bean是一個(gè)模擬的bean赢笨。因?yàn)槲覀円獪y試CityController未蝌,對其依賴的CityService,我們需要mock其預(yù)期的行為表現(xiàn)茧妒。在具體的測試方法中萧吠,使用Mockito的API對sercive的行為進(jìn)行mock,它表示當(dāng)調(diào)用service的getAllCities時(shí)桐筏,會返回預(yù)先設(shè)定的一個(gè)City對象的列表纸型。

之后就是發(fā)起請求,并預(yù)測結(jié)果。

Mockito是Java語言的mock測試框架狰腌,spring以自己的方式集成了它除破。

測試持久層

持久層的測試方案跟具體的持久層技術(shù)相關(guān)。這里我們介紹基于Mybatis的持久層的測試琼腔。

測試目標(biāo)是:

@Mapper
public interface CityMapper {

    City selectCityById(int id);

    List<City> selectAllCities();

    int insert(City city);

}

測試方案是:

@RunWith(SpringRunner.class)
@MybatisTest
@FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
// @Transactional(propagation = Propagation.NOT_SUPPORTED)
public class CityMapperTest {

    @Autowired
    private CityMapper cityMapper;

    @Test
    public void /*selectCityById*/ test1() throws Exception {
        City city = cityMapper.selectCityById(1);
        Assertions.assertThat(city.getId()).isEqualTo(Long.valueOf(1));
        Assertions.assertThat(city.getName()).isEqualTo("San Francisco");
        Assertions.assertThat(city.getState()).isEqualTo("CA");
        Assertions.assertThat(city.getCountry()).isEqualTo("US");
    }

    @Test
    public void /*insertCity*/ test2() throws Exception {
        City city = new City();
        city.setId(2L);
        city.setName("HangZhou");
        city.setState("ZheJiang");
        city.setCountry("CN");

        int result = cityMapper.insert(city);
        Assertions.assertThat(result).isEqualTo(1);
    }

    @Test
    public void /*selectNewInsertedCity*/ test3() throws Exception {
        City city = cityMapper.selectCityById(2);
        Assertions.assertThat(city).isNull();
    }
}

這里使用了@MybatisTest瑰枫,它負(fù)責(zé)構(gòu)建mybatis-mapper層的bean,就像上文中使用的@WebMvcTest負(fù)責(zé)構(gòu)建web層的bean一樣丹莲。值得一提的是@MybatisTest來自于mybatis-spring-boot-starter-test項(xiàng)目光坝,它是mybatis團(tuán)隊(duì)根據(jù)spring的習(xí)慣來實(shí)現(xiàn)的。Spring原生支持的兩種持久層的測試方案是@DataJpaTest@JdbcTest圾笨,分別對應(yīng)JPA持久化方案和JDBC持久化方案教馆。

@FixMethodOrder來自junit,目的是為了讓一個(gè)測試類中的多個(gè)測試方案按照設(shè)定的順序執(zhí)行擂达。一般情況下不需要如此土铺,我這里想確認(rèn)test2方法中插入的數(shù)據(jù),在test3中是否還存在板鬓,所以需要保證兩者的執(zhí)行順序悲敷。

我們注入了CityMapper,因?yàn)槠錄]有更底層的依賴俭令,所以我們不需要進(jìn)行mock后德。

@MybatisTest除了實(shí)例化mapper相關(guān)的bean之外,還會檢測依賴中的內(nèi)嵌數(shù)據(jù)庫抄腔,然后測試的時(shí)候使用內(nèi)嵌數(shù)據(jù)庫瓢湃。如果依賴中沒有內(nèi)嵌數(shù)據(jù)庫,就會失敗赫蛇。當(dāng)然绵患,使用內(nèi)嵌數(shù)據(jù)庫是默認(rèn)的行為,可以使用配置進(jìn)行修改悟耘。

@MybatisTest還會確保每一個(gè)測試方法都是事務(wù)回滾的落蝙,所以在上述的測試用例中,test2插入了數(shù)據(jù)之后暂幼,test3中依然獲取不到插入的數(shù)據(jù)筏勒。當(dāng)然,這也是默認(rèn)的行為旺嬉,可以改變管行。

測試任意的bean

service層并不作為一種特殊的層,所以沒有什么注解能表示“只構(gòu)建service層的bean”這種概念鹰服。

這里將介紹另一種通用的測試場景病瞳,我要測試的是一個(gè)普通的bean揽咕,沒有什么特殊的角色,比如不是擔(dān)當(dāng)特殊處理的controller套菜,也不是負(fù)責(zé)持久化的dao組件亲善,我們要測試的只是一個(gè)普通的bean。

上文中我們使用@SpringBootTest的默認(rèn)機(jī)制逗柴,它去查找@SpringBootApplication的配置蛹头,據(jù)此構(gòu)建Spring的上下文。查看@SpringBootTest的doc戏溺,其中有一句是:

Automatically searches for a @SpringBootConfiguration when nested @Configuration is not used, and no explicit classes are specified.

這表示我們可以通過classes屬性來指定Configuration類渣蜗,或者定義內(nèi)嵌的Configuration類來改變默認(rèn)的配置。

在這里我們通過內(nèi)嵌的Configuration類來實(shí)現(xiàn)旷祸,先看下測試目標(biāo) - CityService:

@Service
public class CityService {

    @Autowired
    private CityMapper cityMapper;

    public List<City> getAllCities() {
        return cityMapper.selectAllCities();
    }
}

測試方案:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CityServiceTest {

    @Configuration
    static class CityServiceConfig {
        @Bean
        public CityService cityService() {
            return new CityService();
        }
    }

    @Autowired
    private CityService cityService;

    @MockBean
    private CityMapper cityMapper;

    @Test
    public void getAllCities() {
        City city = new City();
        city.setId(1L);
        city.setName("杭州");
        city.setState("浙江");
        city.setCountry("CN");

        Mockito.when(cityMapper.selectAllCities())
                .thenReturn(Collections.singletonList(city));

        List<City> result = cityService.getAllCities();
        Assertions.assertThat(result.size()).isEqualTo(1);
        Assertions.assertThat(result.get(0).getName()).isEqualTo("杭州");
    }
}

同樣的耕拷,對于測試目標(biāo)的依賴,我們需要進(jìn)行mock托享。

Mock操作

單元測試中骚烧,需要對測試目標(biāo)的依賴進(jìn)行mock,這里有必要對mock的細(xì)節(jié)介紹下闰围。上文單元測試部分已對Mock的邏輯赃绊、流程和使用場景進(jìn)行了介紹,此處專注于實(shí)踐層面進(jìn)行說明羡榴。

根據(jù)方法參數(shù)設(shè)定預(yù)期行為

一般的mock是對方法級別的mock碧查,在方法有入?yún)⒌那闆r下,方法的行為可能會跟方法的具體參數(shù)值有關(guān)校仑。比如一個(gè)除法的方法忠售,傳入?yún)?shù)4、2得結(jié)果2迄沫,傳入?yún)?shù)8档痪、2得結(jié)果4,傳入?yún)?shù)2邢滑、0得異常。

mock可以針對不同的參數(shù)值設(shè)定不同的預(yù)期愿汰,如下所示:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MathServiceTest {

    @Configuration
    static class ConfigTest {}

    @MockBean
    private MathService mathService;

    @Test
    public void testDivide() {
        Mockito.when(mathService.divide(4, 2))
                .thenReturn(2);

        Mockito.when(mathService.divide(8, 2))
                .thenReturn(4);

        Mockito.when(mathService.divide(ArgumentMatchers.anyInt(), ArgumentMatchers.eq(0))) // 必須同時(shí)用matchers語法
                .thenThrow(new RuntimeException("error"));

        Assertions.assertThat(mathService.divide(4, 2))
                .isEqualTo(2);

        Assertions.assertThat(mathService.divide(8, 2))
                .isEqualTo(4);

        Assertions.assertThatExceptionOfType(RuntimeException.class)
                .isThrownBy(() -> {
                    mathService.divide(3, 0);
                })
                .withMessageContaining("error");
    }
}

上面的測試可能有些奇怪困后,mock的對象也同時(shí)作為測試的目標(biāo)。這是因?yàn)槲覀兊哪康脑谟诮榻Bmock衬廷,所以簡化了測試流程摇予。

從上述測試用例可以看出,我們除了可以指定具體參數(shù)時(shí)的行為吗跋,也可以指定參數(shù)滿足一定匹配規(guī)則時(shí)的行為侧戴。

有返回的方法

對于有返回的方法宁昭,mock時(shí)可以設(shè)定的行為有:

返回設(shè)定的結(jié)果,如:

when(taskService.findResourcePool(any()))
        .thenReturn(resourcePool);

直接拋出異常酗宋,如:

when(taskService.createTask(any(), any(), any()))
        .thenThrow(new RuntimeException("zz"));

實(shí)際調(diào)用真實(shí)的方法积仗,如:

when(taskService.createTask(any(), any(), any()))
        .thenCallRealMethod();

注意,調(diào)用真實(shí)的方法有違mock的本義蜕猫,應(yīng)該盡量避免寂曹。如果要調(diào)用的方法中調(diào)用了其他的依賴,需要自行注入其他的依賴回右,否則會空指針隆圆。

無返回的方法

對于無返回的方法,mock時(shí)可以設(shè)定的行為有:

直接拋出異常翔烁,如:

doThrow(new RuntimeException("test"))
        .when(taskService).saveToDBAndSubmitToQueue(any());

實(shí)際調(diào)用(下列為Mockito類的doc中給出的示例渺氧,我并沒有遇到此需求),如:

doAnswer(new Answer() {
    public Object answer(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        Mock mock = invocation.getMock();
        return null;
    }})
.when(mock).someMethod();

附錄

相關(guān)注解的匯總

annotations.png
  • @RunWith:
    junit的注解蹬屹,通過這個(gè)注解使用SpringRunner.class侣背,能夠?qū)unit和spring進(jìn)行集成。后續(xù)的spring相關(guān)注解才會起效哩治。
  • @SpringBootTest:
    spring的注解秃踩,通過掃描應(yīng)用程序中的配置來構(gòu)建測試用的Spring上下文。
  • @AutoConfigureMockMvc:
    spring的注解业筏,能夠自動配置MockMvc對象實(shí)例憔杨,用來在模擬測試環(huán)境中發(fā)送http請求。
  • @WebMvcTest:
    spring的注解蒜胖,切片測試的一種消别。使之替換@SpringBootTest能將構(gòu)建bean的范圍限定于web層,但是web層的下層依賴bean台谢,需要通過mock來模擬寻狂。也可以通過參數(shù)指定只實(shí)例化web層的某一個(gè)到多個(gè)controller。具體可參考Auto-configured Spring MVC Tests朋沮。
  • @RestClientTest:
    spring的注解蛇券,切片測試的一種。如果應(yīng)用程序作為客戶端訪問其他Rest服務(wù)樊拓,可以通過這個(gè)注解來測試客戶端的功能纠亚。具體參考Auto-configured REST Clients
  • @MybatisTest:
    mybatis按照spring的習(xí)慣開發(fā)的注解筋夏,切片測試的一種蒂胞。使之替換@SpringBootTest,能夠?qū)?gòu)建bean的返回限定于mybatis-mapper層条篷。具體可參考mybatis-spring-boot-test-autoconfigure骗随。
  • @JdbcTest:
    spring的注解蛤织,切片測試的一種。如果應(yīng)用程序中使用Jdbc作為持久層(spring的JdbcTemplate)鸿染,那么可以使用該注解代替@SpringBootTest指蚜,限定bean的構(gòu)建范圍。官方參考資料有限牡昆,可自行網(wǎng)上查找資料姚炕。
  • @DataJpaTest:
    spring的注解,切片測試的一種丢烘。如果使用Jpa作為持久層技術(shù)柱宦,可以使用這個(gè)注解,參考Auto-configured Data JPA Tests播瞳。
  • @DataRedisTest:
    spring的注解掸刊,切片測試的一種。具體內(nèi)容參考Auto-configured Data Redis Tests赢乓。

設(shè)置測試數(shù)據(jù)庫

給持久層測試類添加注解@AutoConfigureTestDatabase(replace = Replace.NONE)可以使用配置的數(shù)據(jù)庫作為測試數(shù)據(jù)庫忧侧。同時(shí),需要在配置文件中配置數(shù)據(jù)源牌芋,如下:

spring:
  datasource:
      url: jdbc:mysql://127.0.0.1/test
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver

事務(wù)不回滾

可以在測試方法上添加@Rollback(false)來設(shè)置不回滾蚓炬,也可以在測試類的級別上添加該注解,表示該類所有的測試方法都不會回滾躺屁。

參考

  1. Spring Boot Testing
  2. Spring Boot Test博客
  3. Mybatis Spring Boot Test官方資料
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肯夏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子犀暑,更是在濱河造成了極大的恐慌驯击,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耐亏,死亡現(xiàn)場離奇詭異徊都,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)广辰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門暇矫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人择吊,你說我怎么就攤上這事袱耽。” “怎么了干发?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長史翘。 經(jīng)常有香客問我枉长,道長冀续,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任必峰,我火速辦了婚禮洪唐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吼蚁。我一直安慰自己凭需,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布肝匆。 她就那樣靜靜地躺著粒蜈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旗国。 梳的紋絲不亂的頭發(fā)上枯怖,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音能曾,去河邊找鬼度硝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寿冕,可吹牛的內(nèi)容都是我干的蕊程。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼驼唱,長吁一口氣:“原來是場噩夢啊……” “哼藻茂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起曙蒸,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤捌治,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后纽窟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肖油,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年臂港,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了森枪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡审孽,死狀恐怖县袱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佑力,我是刑警寧澤式散,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站打颤,受9級特大地震影響暴拄,放射性物質(zhì)發(fā)生泄漏漓滔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一乖篷、第九天 我趴在偏房一處隱蔽的房頂上張望响驴。 院中可真熱鬧阀湿,春花似錦磨德、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琳骡。三九已至,卻和暖如春诉探,著一層夾襖步出監(jiān)牢的瞬間日熬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工肾胯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留竖席,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓敬肚,卻偏偏與公主長得像毕荐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子艳馒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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