單元測試框架:JUnit

簡介

測試 在軟件開發(fā)中是一個很重要的方面羔杨,良好的測試可以在很大程度決定一個應(yīng)用的命運。
軟件測試中,主要有3大種類:

  • 單元測試
    單元測試主要是用于測試程序模塊轧铁,確保代碼運行正確端铛。單元測試是由開發(fā)者編寫并進(jìn)行運行測試泣矛。一般使用的測試框架是 JUnit 或者 TestNG。測試用例一般是針對方法 級別的測試禾蚕。
  • 集成測試
    集成測試用于檢測系統(tǒng)是否能正常工作您朽。集成測試也是由開發(fā)者共同進(jìn)行測試,與單元測試專注測試個人代碼組件不同的是,集成測試是系統(tǒng)進(jìn)行跨組件測試哗总。
  • 功能性測試
    功能性測試是一種質(zhì)量保證過程以及基于測試軟件組件的規(guī)范下的由輸入得到輸出的一種黑盒測試几颜。功能性測試通常由不同的測試團(tuán)隊進(jìn)行測試,測試用例的編寫要遵循組件規(guī)范讯屈,然后根據(jù)測試輸入得到的實際輸出與期望值進(jìn)行對比蛋哭,判斷功能是否正確運行。

概述

本文只對 單元測試 進(jìn)行介紹涮母,主要介紹如何在 Android Studio 下進(jìn)行單元測試谆趾,單元測試使用的測試框架為 JUnit

好處

可能目前仍有很大一部分開發(fā)者未使用 單元測試 對他們的代碼進(jìn)行測試,一方面可能是覺得沒有必要叛本,因為即使沒有進(jìn)行單元測試沪蓬,程序照樣運行得很好;另一方面来候,也許有些人也認(rèn)同單元測試的好處怜跑,但是由于需要額外的學(xué)習(xí)成本,所以很多人也是沒有時間或者說是沒有耐心進(jìn)行學(xué)習(xí)······
這里我想說的是吠勘,如果大家去看下 github 上目前主流的開源框架性芬,star 數(shù)比較多的項目,一般都有很詳盡的測試用例剧防。所以說植锉,單元測試對于我們的項目開發(fā),還是挺有好處的峭拘。
至于單元測試的好處俊庇,我這里提及幾點:

  • 保證代碼運行與我們預(yù)想的一樣,代碼正確性可以得到保證
  • 程序運行出錯時鸡挠,有利于我們對錯誤進(jìn)行查找(因為我們忽略我們測試通過的代碼)
  • 有利于提升代碼架構(gòu)設(shè)計(用于測試的用例應(yīng)力求簡單低耦合辉饱,因此編寫代碼的時候,開發(fā)者往往會為了對代碼進(jìn)行測試拣展,將其他耦合的部分進(jìn)行解耦處理)
    ······

JUnit 簡介

JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.

JUnit 是一個支持可編寫重復(fù)測試用例的簡單框架彭沼。它是 xUnit 單元測試框架架構(gòu)的一個子集。

名稱 解釋
Assertions 單元測試實用方法
Test Runners 測試實例應(yīng)當(dāng)怎樣被執(zhí)行(測試運行器)
Aggregating tests in Suites 合并多個相關(guān)測試用例到一個測試套件中(當(dāng)運行測試套件時备埃,相關(guān)用例就會一起被執(zhí)行)
Test Execution Order 指定測試用例運行順序
Exception Testing 如何指定測試用例期望的異常
Matchers and assertThat 如何使用 Hamcrest 的匹配器 (matchers) 和更加具備描述性的斷言 (assertions)
Ignoring Tests 失能類或方法的測試用例
Timeout for Tests 指定測試用例的最大運行時間(超過這個時間姓惑,自動結(jié)束測試用例)
Parameterized Tests 測試用例運行多次,每次都使用不同的參數(shù)值
Assumptions with Assume 類似斷言按脚,但不會使測試用例失敗
Rules 為測試用例增加Rules(相當(dāng)于添加功能)
Theories 使用隨機(jī)生成的數(shù)據(jù)使測試用例更加科學(xué)嚴(yán)謹(jǐn)
Test Fixtures 為測試方法或者類指定預(yù)備的set upclean up方法
Categories 將測試用例組織起來于毙,方便過濾
··· ···

Assertions - 斷言
JUnit 為所有的原始類型和對象,數(shù)組(原始類型數(shù)組或者對象數(shù)組)提供了多個重載的斷言方法(assertion method)辅搬。斷言方法的參數(shù)第一個為預(yù)期值唯沮,第二個為實際運行的值。另一個可選方法的第一個參數(shù)是作為失敗輸出的字符串信息。還有一個稍微有些區(qū)別的斷言方法:assertThat介蛉。assertThat的參數(shù)有一個可選的失敗信息輸出夯缺,實際運行的值和一個 Matcher 對象。請知悉assertThat的預(yù)期值和實際運行值與其他的斷言方法位置是相反的甘耿。
ps:實際開發(fā)中踊兜,建議采用 Hamcrest 提供的斷言方法:assertThat,因為這個方法一方面寫出的代碼更具可讀性佳恬,一方面當(dāng)斷言失敗時捏境,這個方法會給出具體的錯誤提示信息。

更多的 Assertions 信息毁葱,請查看文檔:Assert

Test Runners - 測試運行器
當(dāng)一個類被注解@RunWith或者集成一個被@RunWith注解的類時垫言,JUnit 會把測試用例運行在該類上,而不是內(nèi)置的運行器上倾剿。

ps: JUnit 的默認(rèn)運行器是 BlockJUnit4ClassRunner筷频。
如果類注解為@RunWith(JUnit4.class),則使用的是默認(rèn)的測試運行器 BlockJUnit4ClassRunner前痘。

更多詳細(xì)信息凛捏,請查看文檔:@RunWith

Aggregating tests in Suites - 測試套件
使用套件(Suite)作為運行器使得你可以手動建造一個可以容納許多類的測試用例。使用測試套件時芹缔,你需要創(chuàng)建一個類坯癣,然后為其注解上@RunWith(Suite.class)@SuiteClasses(TestClass1.class, ...),這樣最欠,當(dāng)你運行這個類時示罗,測試套件各個類的測試用例就會全部被執(zhí)行。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({
  TestFeatureLogin.class,
  TestFeatureLogout.class,
  TestFeatureNavigate.class,
  TestFeatureUpdate.class
})

public class FeatureTestSuite {
  // the class remains empty,
  // used only as a holder for the above annotations
}

Test Execution Order
JUnit 4.11版本開始芝硬,JUnit 默認(rèn)使用確定的蚜点,不可預(yù)見性的測試用例執(zhí)行順序(MethodSorters.DEFAULT)。要改變測試用例執(zhí)行順序拌阴,只需簡單為測試類添加@FixMethodOrder注解绍绘,并指定一個方法排序規(guī)則:
@FixMethodOrder(MethodSorters.JVM):由JVM決定方法執(zhí)行順序,在不同的JVM上皮官,執(zhí)行順序可能不同脯倒。
@FixMethodOrder(MethodSorters.NAME_ASCENDING):按方法名進(jìn)行排序(字典序)進(jìn)行執(zhí)行实辑。

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestMethodOrder {

    @Test
    public void testA() {
        System.out.println("first");
    }
    @Test
    public void testB() {
        System.out.println("second");
    }
    @Test
    public void testC() {
        System.out.println("third");
    }
}

Exception Testing
你如何驗證代碼拋出的異常是你所期望的捺氢?驗證代碼正常走完是很重要,但是確保代碼在異常情況下表現(xiàn)也與預(yù)期一樣也是很重要的剪撬,比如:

new ArrayList<Object>().get(0);

這句代碼應(yīng)該拋出一個 IndexOutOfBoundsException異常摄乒。@Test注解有一個可選的參數(shù) expected,它可以攜帶一個Throwable的子類。如果我們希望驗證ArrayList能正確拋出一個異常馍佑,我們應(yīng)該這樣寫:

@Test(expected = IndexOutOfBoundsException.class) 
public void empty() { 
     new ArrayList<Object>().get(0); 
}

參數(shù)expected的使用應(yīng)該慎重斋否。只要測試代碼中的任何一句拋出一個IndexOutOfBoundsException異常,那么上面的測試用例就會通過拭荤。對于代碼比較長的測試用例茵臭,推薦使用 ExpectedException 規(guī)則。

更多詳情舅世,請查看:Exception testing

Matchers and assertThat

  • assertThat的一個通用格式為:
assertThat([value], [matcher statement])

示例:

assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));

assertThat的第二個參數(shù)是一個Matcher.
詳細(xì)的Matcher介紹旦委,可以查看以下兩個文檔:

Ignoring Tests
由于某些原因,你不希望測試用例運行失敗雏亚,你只想忽略它缨硝,那你只需暫時失能這個測試用例即可。
JUnit 中罢低,你可以通過注釋方法或者刪除@Test注解來忽略測試用例查辩;但是這樣的話測試運行器就不會對該測試用例進(jìn)行相關(guān)報告。另一個方案是為測試用例在@Test注解前面或后面添加上@Ignore注解网持;那么測試運行器運行后宜岛,就會輸出相關(guān)測試用例忽略數(shù)目,運行所有測試用例的數(shù)目和測試用例失敗的數(shù)目顯示功舀。
注意下@Ignore注解可以攜帶一個可選參數(shù)(String類型)谬返,如果你想記錄測試用例忽略的原因,可以使用這個參數(shù):

@Ignore("Test is ignored as a demonstration")
@Test
public void testSame() {
    assertThat(1, is(1));
}

Timeout for Tests
對于失控或者運行時間太長的測試用例日杈,則自動被認(rèn)為失敗遣铝,有兩種方法可以實現(xiàn)這個動作。

  • @Test增加timeout參數(shù)
    你可以為一個測試用例指定一個超時時間(毫秒)莉擒,在規(guī)定時間內(nèi)酿炸,如果測試用例沒有運行結(jié)束,那么測試用例運行所在線程就會拋出一個異常涨冀,從而引起測試失敗填硕。
@Test(timeout=1000)
public void testWithTimeout() {
  ...
}

這種實現(xiàn)方式是通過將測試用例方法運行在另一個單獨的線程中。如果測試用例運行時間超過規(guī)定的時間鹿鳖,那么測試用例就會失敗扁眯,JUnit 就會打斷執(zhí)行測試用例的線程。如果測試用例內(nèi)部執(zhí)行有可以中斷的操作翅帜,那么運行測試用例的線程就會退出(如果測試用例內(nèi)部是一個無限循環(huán)姻檀,那么運行測試用例的線程將會永遠(yuǎn)運行,而其他測試用例仍在其他的線程上執(zhí)行)涝滴。

  • Timeout Rule (應(yīng)用到測試類的所有測試用例)
    Timeout Rule會將同一個超時時間應(yīng)用到測試類的所有測試方法中绣版,并且如果測試用例@Test帶有timeout參數(shù)胶台,則會疊加到一起(實際測試中,并沒有疊加的效果杂抽,甚至tiemout參數(shù)并不生效诈唬,依舊還是以Timeout Rule為準(zhǔn))
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;

public class HasGlobalTimeout {
    public static String log;
    private final CountDownLatch latch = new CountDownLatch(1);

    @Rule
    public Timeout globalTimeout = Timeout.seconds(10); // 10 seconds max per method tested

    @Test
    public void testSleepForTooLong() throws Exception {
        log += "ran1";
        TimeUnit.SECONDS.sleep(100); // sleep for 100 seconds
    }

    @Test
    public void testBlockForever() throws Exception {
        log += "ran2";
        latch.await(); // will block 
    }
}

Timeout rule指定的超時時間timeout會應(yīng)用到所有的測試用例中,包括任何的@Before@After方法缩麸。如果測試方法是一個無限循環(huán)(或者是無法響應(yīng)中斷操作)铸磅,那么@Afte注解的方法永遠(yuǎn)不會被執(zhí)行。

Parameterized Tests - 參數(shù)化測試
對于單元測試來說杭朱,如果想要同一個測試用例中測試多組不同的數(shù)據(jù)愚屁,那么只能手動執(zhí)行一次后,更改數(shù)據(jù)痕檬,再進(jìn)行執(zhí)行霎槐,而使用參數(shù)化測試的話,則可以將上述的行為進(jìn)行自動化梦谜,我們所需要做的就是提供一個數(shù)據(jù)集合丘跌,然后創(chuàng)建相應(yīng)的成員變量用來接收數(shù)據(jù)集合傳遞過來的數(shù)據(jù)(在測試類構(gòu)造器中接收),最后運行測試用例時唁桩,參數(shù)化測試運行器就會依次從數(shù)據(jù)集合中取出一個數(shù)據(jù)闭树,并傳給測試用例運行:

//功能類

public class Math {
    public static int add(int a, int b) {
        return a + b;
    }
}
//單元測試類
@RunWith(Parameterized.class) //指定參數(shù)化測試運行器
public class MathTest {
    private int a;  //聲明成員變量用于接收數(shù)據(jù)
    private int b;

    public MathTest(int a, int b) { //接受集合數(shù)據(jù)
        this.a = a;
        this.b = b;
    }

    @Parameterized.Parameters //創(chuàng)建參數(shù)集合
    public static Collection<Object[]> data() {
        Collection<Object[]> collection = new ArrayList<>();
        collection.add(new Object[]{1, 2});
        collection.add(new Object[]{10, 20});
        collection.add(new Object[]{30, 40});
        return collection;
    }

    @Test
    public void add() throws Exception {
        assertThat(Math.add(a, b), is(equalTo(30)));
    }

}

Assumptions with Assume - 前置條件
前置條件與斷言類似,只是斷言在不匹配時荒澡,測試用例就會失敗报辱,而前置條件在不匹配時只會使測試用例退出。
前置條件的使用場景是:當(dāng)你的代碼在不同的環(huán)境下单山,可能有不同的結(jié)果時碍现,如果你明確后續(xù)的測試代碼是基于某一特定的環(huán)境下,才進(jìn)行測試米奸,那么昼接,借助前置條件,就可以實現(xiàn)所需功能悴晰。
比如慢睡,假設(shè) Windows 平臺的文件路徑分隔符為"\",而 Linux 平臺的為"/”铡溪,假設(shè)我們的測試用例只想在 Linux 平臺上進(jìn)行測試漂辐,那么:

@Test
public void filenameIncludesUsername() {
        assumeThat(File.separatorChar, is('/'));
        assertThat(new User("optimus").configFileName(), is("configfiles/optimus.cfg"));
    }

如果在 Windows 平臺運行測試用例時,assumeThat(File.separatorChar, is('/'))就會不匹配棕硫,那么測試用例就直接退出(類似異常機(jī)制)髓涯。

Rules - 規(guī)則
Rules允許為測試用例增加靈活的條件或者是重新定義每個類的測試用例行為。測試類可以重新或者繼承一下任一提供的Rules饲帅,或者自己自定義一個复凳。

Rule Description
TemporaryFolder 創(chuàng)建臨時文件夾/文件(測試方法完成后文件被自動刪除)
ExternalResource 外部資源Rules的一個基類
ErrorCollector 收集錯誤信息
Verifier 具備校驗功能的一個基類
TestWatcher 具備測試結(jié)果記錄的一個基類
TestName Rules對象可在測試用例內(nèi)部獲取測試用例方法名
Timeout 為測試類所有測試用例約束最長運行時間
ExpectedException 該類使得測試用例能在方法內(nèi)判別測試代碼是否拋出預(yù)期異常
ClassRule 類級別Rule瘤泪,用于靜態(tài)變量的注解灶泵,在測試類運行時只執(zhí)行一次
Rule 方法級別的Rule育八,用于成員變量的注解,在類的每個測試用例執(zhí)行時都會被執(zhí)行
RuleChain 為多個Rules指定順序
TestRule 自定義Rules基類

這里簡單介紹下自定義Rules赦邻,假設(shè)我們要為所有的測試用例輸出前后添加"------------"髓棋,那么,我們需要先創(chuàng)建一個Rule


public class CustomerRule implements TestRule {
    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement(){
            @Override
            public void evaluate() throws Throwable {
                System.out.println("--------------------------");
                base.evaluate();
                System.out.println();
                System.out.println("--------------------------");
            }
        };
    }
}

然后把自定義的TestRule運用到測試類里面即可:

    @Rule
    public CustomerRule customerRule = new CustomerRule();

    @Test
    public void testCustom() {
        assertThat(1, is(1));
    }

更多Rules詳細(xì)信息惶洲,請查看:Rules

Theories - 測試?yán)碚?br> JUnit 中的 Theories 可以理解成一個測試?yán)碚摪瓷摾碚摪褱y試分為兩部分:一個是提供測試數(shù)據(jù)(單個數(shù)據(jù)用@DataPoint注解,集合數(shù)據(jù)使用@DataPoints注解)恬吕,數(shù)據(jù)提供者必須為靜態(tài)成員/方法签则;另一個是理論本身,也即測試用例方法铐料。
Theories 的測試用例允許參數(shù)傳遞(普通測試用例測試方法不能攜帶參數(shù))渐裂,參數(shù)傳遞規(guī)則是首先從數(shù)據(jù)集合中取出一個作為第一個參數(shù),然后依次取出集合的元素(包含已作為參數(shù)1的那個數(shù)據(jù))作為第二個參數(shù)····
看下下面的測試用例就會比較清楚 Theories 的運作流程:

@RunWith(Theories.class)
public class MathTest {
//    @DataPoint
//    public static int arg0 = 1;
//    @DataPoint
//    public static int arg1 = 10;
//    @DataPoint
//    public static int arg2 = 0;
    @DataPoints
    public static int[] args = new int[]{1, 10, 0};
    
    @Theory
    public void divied(int a, int b) throws Exception {
        Assume.assumeTrue(b != 0);
        System.out.println(String.format("a=%d,b=%d", a, b));
        assertThat(Math.divied(a, b), not(equalTo(2)));
    }
}

運行結(jié)果如下:

result

從上面的測試用例可以看出钠惩,MathTest提供的數(shù)據(jù)集合為{1,10,0}柒凉,所以:
第一次 運行測試用例divied(int a, int b)時,從集合中取出一個參數(shù)篓跛,即1會傳遞給參數(shù)a膝捞,然后又從集合中取出一個參數(shù),也是1,傳遞給b愧沟,然后執(zhí)行測試用例蔬咬;
第二次 運行時,參數(shù)a保持不變沐寺,然后從新從集合中取出下一個元素給到b计盒,所以b=10,然后執(zhí)行測試用例芽丹;
第三次 運行時北启,參數(shù)a保持不變,然后從新從集合中取出下一個元素給到b拔第,所以b=0咕村,然后執(zhí)行測試用例時,由于不滿足Assume前置條件蚊俺,故測試用例不再往下運行懈涛,直接退出,所以看到當(dāng)b=0時泳猬,沒有打印結(jié)果批钠;
第四次 運行時宇植,由于b在前面第一輪運行時已完整取出了整個集合數(shù)據(jù),所以此時就輪到參數(shù)a取出集合的下一個數(shù)據(jù)埋心,即a=10指郁,然后就按照前一輪的執(zhí)行邏輯繼續(xù)執(zhí)行下去。

從上面的分析中可以看出拷呆,TheoriesParameterized Tests 很類似闲坎,兩者都實現(xiàn)了多組數(shù)據(jù)共同作用于同一個測試用例的功能,不過兩者的參數(shù)傳遞機(jī)制還是有很大的不同的茬斧, Parameterized Tests 可以提供多維數(shù)組的形式符合參數(shù)個數(shù)順序腰懂,而 Theories 的參數(shù)集合中的每個元素都會同時作用于各個參數(shù);個人感覺還是 Parameterized Tests 更符合通常的測試邏輯项秉。

Test Fixtures - 測試設(shè)備
Test Fixtures 是被用作測試用例運行的基準(zhǔn)的一系列對象的混合狀態(tài)绣溜,Test Fixtures 為我們提供了4個注解(均用于方法上):

Annotation Description
@BeforeClass 測試類運行時執(zhí)行
@AfterClass 測試類結(jié)束時執(zhí)行
@Before 每個測試用例執(zhí)行前先執(zhí)行
@After 每個測試用例執(zhí)行后再執(zhí)行

Categories - 分類
Categories 見名知意,就是將一系列測試類/測試方法進(jìn)行分類娄蔼,每個類或者接口都可以作為一個Category怖喻,且支持類別繼承。
比如贷屎,你指定一個測試用例屬于SuperClass.class的類別(使用@Category(SuperClass.class)注解在測試類用例上)罢防,然后@IncludeCategory(SuperClass.class),那么任何測試用例上注解了@Category(SuperClass.class)或者@Category({SubClass.class})的方法都會被執(zhí)行唉侄。
舉個例子:

  1. 首先我們需要定義一個或多個測試類別(即Category)
public class Category {
    public static interface Category01 {}

    public static interface Category02 {}

    public static interface Category01Impl extends Category01{}
}

這里有3種測試Category咒吐,其中,類別Category01Impl繼承了類別Category01属划,所以任何@IncludeCategory(Category01.class)的測試類恬叹,測試時也會執(zhí)行類別為Category01Impl的測試用例。

  1. 定義好了測試類別后同眯,我們就需要將這些類別運用到測試類或者測試用例上
public class Tests {
    public static class Test01 {
        @Test
        @Category(Category01.class) //運用到測試用例上
        public void test01() {
            System.out.println("This testCase belongs to Category01");
        }
        @Test
        @Category(Category01Impl.class)//運用到測試用例上
        public void test01Impl() {
            System.out.println("This testCase belongs to Category01Impl");
        }
    }

    @Category(Category02.class)//運用到測試類上绽昼,類中所有測試方法都屬于`Category02.class`這個類別
    public static class Test02 {
        @Test
        public void test02() {
            System.out.println("This testCase belongs to Category02");
        }
    }
}
  1. 最后,再Categories類別測試運行器上運行需要的測試用例即可
@RunWith(Categories.class)
@IncludeCategory(Category01.class)
@SuiteClasses({Tests.Test01.class, Tests.Test02.class}) // Note that Category is a kind of Suite
public class CategoryTest {
}

更多詳細(xì)信息须蜗,請查看:Categories

Android Studio 進(jìn)行單元測試

假設(shè)我們需要對一個 Java Module 進(jìn)行單元測試硅确,采用 JUnit 框架,則部署步驟如下:

  • build.gralde 中依賴 JUnit:
dependencies {
     testImplementation 'junit:junit:4.12' //or testCompile
}
  • 創(chuàng)建一個類
public class Math {
    public static int add(int a, int b) {
        return a + b;
    }
}
  • 對上面的類Mathadd方法進(jìn)行測試
    我們可以手動創(chuàng)建一個Math的測試類明肮,但是借助于 Android Studio菱农,我們可以很方面的使用快捷操作自動生成測試類和測試用例,具體做法為:打開要進(jìn)行測試的類文件柿估,雙擊類名/方法名進(jìn)行選中循未,然后按快捷鍵:<Ctrl-Shift-T>
創(chuàng)建測試用例
  • 最后,寫上測試代碼秫舌,進(jìn)行測試就可以了的妖。

更多詳細(xì)信息绣檬,請查看官網(wǎng):Building Local Unit Tests

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嫂粟,隨后出現(xiàn)的幾起案子娇未,更是在濱河造成了極大的恐慌,老刑警劉巖赋元,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忘蟹,死亡現(xiàn)場離奇詭異飒房,居然都是意外死亡搁凸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門狠毯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來护糖,“玉大人,你說我怎么就攤上這事嚼松〉樟迹” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵献酗,是天一觀的道長寝受。 經(jīng)常有香客問我,道長罕偎,這世上最難降的妖魔是什么很澄? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮颜及,結(jié)果婚禮上甩苛,老公的妹妹穿的比我還像新娘。我一直安慰自己俏站,他們只是感情好讯蒲,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肄扎,像睡著了一般墨林。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上犯祠,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天旭等,我揣著相機(jī)與錄音,去河邊找鬼雷则。 笑死辆雾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的月劈。 我是一名探鬼主播度迂,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼藤乙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惭墓?” 一聲冷哼從身側(cè)響起坛梁,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腊凶,沒想到半個月后划咐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡钧萍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年褐缠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片风瘦。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡队魏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出万搔,到底是詐尸還是另有隱情胡桨,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布瞬雹,位于F島的核電站昧谊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏酗捌。R本人自食惡果不足惜呢诬,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望意敛。 院中可真熱鬧馅巷,春花似錦、人聲如沸草姻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撩独。三九已至敞曹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間综膀,已是汗流浹背澳迫。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留剧劝,地道東北人橄登。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拢锹。 傳聞我的和親對象是個殘疾皇子谣妻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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