Cut: A Simple xUnit Test Framework in Modern C++11

動(dòng)機(jī)

實(shí)現(xiàn)Cut(C++ Unified Test Framework)的動(dòng)機(jī)苏遥,請(qǐng)參閱:無法忍受 Google Test 的 9 個(gè)特性

靈感

Cut(C++ Unified Test Framework)是一個(gè)簡(jiǎn)單的正林、可擴(kuò)展的、使用C\\+\\+11實(shí)現(xiàn)的xUnit測(cè)試框架创夜。Cut設(shè)計(jì)靈感來自于Java社區(qū)著名的測(cè)試框架JUnit。

安裝

GitHub

編譯環(huán)境

支持的平臺(tái):

  • [MAC OS X] supported
  • [Linux] supported
  • [Windows] not supported

支持的編譯器:

  • [CLANG] 3.4 or later.
  • [GCC] 4.8 or later.
  • [MSVC] not supported.

安裝CMake

CMake的下載地址:http://www.cmake.org仙逻。

安裝Cut

$ git clone https://gitlab.com/horance-liu/cut.git
$ cd cut
$ mkdir build
$ cd build
$ cmake ..
$ make
$ sudo make install

測(cè)試Cut

$ cd cut/build
$ cmake -DENABLE_TEST=on ..
$ make
$ test/cut-test

破冰之旅

物理目錄
quantity
├── include
│   └── quantity
├── src
│   └── quantity
└── test
│   ├── main.cpp
└── CMakeLists.txt
main函數(shù)
#include "cut/cut.hpp"

int main(int argc, char** argv)
{
    return cut::run_all_tests(argc, argv);
}
CMakeList腳本
project(quantity)

cmake_minimum_required(VERSION 2.8)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

file(GLOB_RECURSE all_files
src/*.cpp
src/*.cc
src/*.c
test/*.cpp
test/*.cc
test/*.c)

add_executable(quantity-test ${all_files})

target_link_libraries(quantity-test cut)
構(gòu)建
$ mkdir build
$ cd build
$ cmake ..
$ make
運(yùn)行
$ ./quantity-test

[==========] Running 0 test cases.
[----------] 0 tests from All Tests
[----------] 0 tests from All Tests

[==========] 0 test cases ran.
[  TOTAL   ] PASS: 0  FAILURE: 0  ERROR: 0  TIME: 0 us

體驗(yàn)Cut

第一個(gè)用例

#include <cut/cut.hpp>

#include "quantity/Length.h"

USING_CUM_NS

FIXTURE(LengthTest)
{
    TEST("1 FEET should equal to 12 INCH")
    {
        ASSERT_THAT(Length(1, FEET), eq(Length(12, INCH)));
    }
};

使用 Cut,只需要包含 cut.hpp 一個(gè)頭文件即可驰吓。Cut 使用 Hamcrest 的斷言機(jī)制,
使得斷言更加統(tǒng)一涧尿、自然,且具有良好的擴(kuò)展性;使用 USING_CUM_NS,從而可以使用 eq
cum::eq,簡(jiǎn)短明確;除非出現(xiàn)名字沖突,否則推薦使用簡(jiǎn)寫的 eq

Length實(shí)現(xiàn)

// quantity/Length.h
#include "quantity/Amount.h"

enum LengthUnit
{
    INCH = 1,
    FEET = 12 * INCH,
};

struct Length
{
    Length(Amount amount, LengthUnit unit);

    bool operator==(const Length& rhs) const;
    bool operator!=(const Length& rhs) const;

private:
    const Amount amountInBaseUnit;
};
// quantity/Length.cpp
#include "quantity/Length.h"

Length::Length(Amount amount, LengthUnit unit)
  : amountInBaseUnit(unit * amount)
{
}

bool Length::operator==(const Length& rhs) const
{
    return amountInBaseUnit == rhs.amountInBaseUnit;
}

bool Length::operator!=(const Length& rhs) const
{
    return !(*this == rhs);
}
構(gòu)建
$ mkdir build
$ cd build
$ cmake ..
$ make
運(yùn)行
$ ./quantity-test

[==========] Running 1 test cases.
[----------] 1 tests from All Tests
[----------] 1 tests from LengthTest
[ RUN      ] LengthTest::1 FEET should equal to 12 INCH
[       OK ] LengthTest::1 FEET should equal to 12 INCH(13 us)
[----------] 1 tests from LengthTest

[----------] 1 tests from All Tests

[==========] 1 test cases ran.
[  TOTAL   ] PASS: 1  FAILURE: 0  ERROR: 0  TIME: 13 us

Fixture

FIXTURE的參數(shù)可以是任意的C\\+\\+標(biāo)識(shí)符檬贰。一般而言姑廉,將其命名為CUT(Class Under Test)的名字即可。根據(jù)作用域的大小翁涤,F(xiàn)ixture可分為三個(gè)類別:獨(dú)立的Fixture桥言,共享的Fixture,全局的Fixture葵礼。

支持BDD風(fēng)格

xUnit BDD
FIXTURE CONTEXT
SETUP BEFORE
TEARDOWN AFTER
ASSERT_THAT EXPECT

獨(dú)立的Fixture

#include <cut/cut.hpp>

FIXTURE(LengthTest)
{
    Length length;

    SETUP()
    {}

    TEARDOWN()
    {}

    TEST("length test1")
    {}

    TEST("length test2")
    {}
};

執(zhí)行序列為:

  1. Length 構(gòu)造函數(shù)
  2. SETUP
  3. TEST("length test1")
  4. TEARDOWN
  5. Length 析構(gòu)函數(shù)
  6. Length 構(gòu)造函數(shù)
  7. SETUP
  8. TEST("length test2")
  9. TEARDOWN
  10. Length 析構(gòu)函數(shù)

共享的Fixture

#include <cut/cut.hpp>

FIXTURE(LengthTest)
{
    Length length;

    BEFORE_CLASS()
    {}

    AFTER_CLASS()
    {}

    BEFORE()
    {}

    AFTER()
    {}

    TEST("length test1")
    {}

    TEST("length test2")
    {}
};

執(zhí)行序列為:

  1. BEFORE_CLASS
  2. Length 構(gòu)造函數(shù)
  3. BEFORE
  4. TEST("length test1")
  5. AFTER
  6. Length 析構(gòu)函數(shù)
  7. Length 構(gòu)造函數(shù)
  8. BEFORE
  9. TEST("length test2")
  10. AFTER
  11. Length 析構(gòu)函數(shù)
  12. AFTER_CLASS

全局的Fixture

有時(shí)候需要在所有用例啟動(dòng)之前完成一次性的全局性的配置号阿,在所有用例運(yùn)行完成之后完成一次性的清理工作。Cut則使用BEFORE_ALLAFTER_ALL兩個(gè)關(guān)鍵字來支持這樣的特性鸳粉。

#include <cut/cut.hpp>

BEFORE_ALL("before all 1")
{
}

BEFORE_ALL("before all 2")
{
}

AFTER_ALL("after all 1")
{
}

AFTER_ALL("after all 2")
{
}

BEFORE_ALLAFTER_ALL向系統(tǒng)注冊(cè)Hook即可扔涧,Cut便能自動(dòng)地發(fā)現(xiàn)它們,并執(zhí)行它們届谈。猶如C\\+\\+不能保證各源文件中全局變量初始化的順序一樣枯夜,避免在源文件之間的BEFORE_ALLAFTER_ALL設(shè)計(jì)不合理的依賴關(guān)系。

#include <cut/cut.hpp>

FIXTURE(LengthTest)
{
    Length length;

    BEFORE_CLASS()
    {}

    AFTER_CLASS()
    {}

    BEFORE()
    {}

    AFTER()
    {}

    TEST("length test1")
    {}

    TEST("length test2")
    {}
};
#include <cut/cut.hpp>

FIXTURE(VolumeTest)
{
    Volume volume;

    BEFORE_CLASS()
    {}

    AFTER_CLASS()
    {}

    BEFORE()
    {}

    AFTER()
    {}

    TEST("volume test1")
    {}

    TEST("volume test1")
    {}
};

Cut可能的一個(gè)執(zhí)行序列為:

  1. BEFORE_ALL("before all 1")
  2. BEFORE_ALL("before all 2")
  3. LengthTest::BEFORE_CLASS
  4. Length構(gòu)造函數(shù)
  5. LengthTest::BEFORE
  6. TEST("length test1")
  7. LengthTest::AFTER
  8. Length析構(gòu)函數(shù)
  9. Length構(gòu)造函數(shù)
  10. LengthTest::BEFORE
  11. TEST("length test2")
  12. LengthTest::AFTER
  13. Length析構(gòu)函數(shù)
  14. LengthTest::AFTER_CLASS
  15. VolumeTest::BEFORE_CLASS
  16. Volume構(gòu)造函數(shù)
  17. LengthTest::BEFORE
  18. TEST("volume test1")
  19. LengthTest::AFTER
  20. Volume析構(gòu)函數(shù)
  21. Volume構(gòu)造函數(shù)
  22. LengthTest::BEFORE
  23. TEST("volume test2")
  24. LengthTest::AFTER
  25. Volume析構(gòu)函數(shù)
  26. VolumeTest::AFTER_CLASS
  27. AFTER_ALL("after all 2")
  28. AFTER_ALL("after all 1")

用例設(shè)計(jì)

自動(dòng)標(biāo)識(shí)

Cut能夠自動(dòng)地實(shí)現(xiàn)測(cè)試用例的標(biāo)識(shí)功能艰山,用戶可以使用字符串來解釋說明測(cè)試用例的意圖湖雹,使得用戶在描述用例時(shí)更加自然和方便。

#include <cut/cut.hpp>
#include "quantity/length/Length.h"

USING_CUM_NS

FIXTURE(LengthTest)
{
    TEST("1 FEET should equal to 12 INCH")
    {
        ASSERT_THAT(Length(1, FEET), eq(Length(12, INCH)));
    }

    TEST("1 YARD should equal to 3 FEET")
    {
        ASSERT_THAT(Length(1, YARD), eq(Length(3, FEET)));
    }

    TEST("1 MILE should equal to 1760 YARD")
    {
        ASSERT_THAT(Length(1, MILE), eq(Length(1760, YARD)));
    }
};

面向?qū)ο?/h4>

Cut實(shí)現(xiàn)xUnit時(shí)非常巧妙程剥,使得用戶設(shè)計(jì)用例時(shí)更加面向?qū)ο蟆?code>RobotCleaner robot在每個(gè)用例執(zhí)行時(shí)都將獲取一個(gè)獨(dú)立的劝枣、全新的實(shí)例。

#include "cut/cut.hpp"
#include "robot-cleaner/RobotCleaner.h"
#include "robot-cleaner/Position.h"
#include "robot-cleaner/Instructions.h"

USING_CUM_NS

FIXTURE(RobotCleanerTest)
{
    RobotCleaner robot;

    TEST("at the beginning, the robot should be in at the initial position")
    {
        ASSERT_THAT(robot.getPosition(), is(Position(0, 0, NORTH)));
    }

    TEST("left instruction: 1-times")
    {
        robot.exec(left());
        ASSERT_THAT(robot.getPosition(), is(Position(0, 0, WEST)));
    }

    TEST("left instruction: 2-times")
    {
        robot.exec(left());
        robot.exec(left());
        ASSERT_THAT(robot.getPosition(), is(Position(0, 0, SOUTH)));
    }
};

函數(shù)提取

提取的相關(guān)子函數(shù)织鲸,可以直接放在Fixture的內(nèi)部舔腾,使得用例與其的距離最近,更加體現(xiàn)類作用域的概念搂擦。

#include "cut/cut.hpp"
#include "robot-cleaner/RobotCleaner.h"
#include "robot-cleaner/Position.h"
#include "robot-cleaner/Instructions.h"

USING_CUM_NS

FIXTURE(RobotCleanerTest)
{
    RobotCleaner robot;

    void WHEN_I_send_instruction(Instruction* instruction)
    {
        robot.exec(instruction);
    }

    void AND_I_send_instruction(Instruction* instruction)
    {
        WHEN_I_send_instruction(instruction);
    }

    void THEN_the_robot_cleaner_should_be_in(const Position& position)
    {
        ASSERT_THAT(robot.getPosition(), is(position));
    }

    TEST("at the beginning")
    {
        THEN_the_robot_cleaner_should_be_in(Position(0, 0, NORTH));
    }

    TEST("left instruction: 1-times")
    {
        WHEN_I_send_instruction(left());
        THEN_the_robot_cleaner_should_be_in(Position(0, 0, WEST));
    }

    TEST("left instruction: 2-times")
    {
        WHEN_I_send_instruction(repeat(left(), 2));
        THEN_the_robot_cleaner_should_be_in(Position(0, 0, SOUTH));
    }

    TEST("left instruction: 3-times")
    {
        WHEN_I_send_instruction(repeat(left(), 3));
        THEN_the_robot_cleaner_should_be_in(Position(0, 0, EAST));
    }

    TEST("left instruction: 4-times")
    {
        WHEN_I_send_instruction(repeat(left(), 4));
        THEN_the_robot_cleaner_should_be_in(Position(0, 0, NORTH));
    }
};

斷言

ASSERT_THAT

Cut只支持一種斷言原語:ASSERT_THAT, 從而避免用戶在選擇ASSERT_EQ/ASSERT_NE, ASSERT_TRUE/ASSERT_FALSE時(shí)的困擾稳诚,使其斷言更加具有統(tǒng)一性,一致性瀑踢。

此外扳还,ASSERT_THAT使得斷言更加具有表達(dá)力,它將實(shí)際值放在左邊橱夭,期望值放在右邊氨距,更加符合英語習(xí)慣。

#include <cut/cut.hpp>

FIXTURE(CloseToTest)
{
    TEST("close to double")
    {
        ASSERT_THAT(1.0, close_to(1.0, 0.5));
        ASSERT_THAT(0.5, close_to(1.0, 0.5));
        ASSERT_THAT(1.5, close_to(1.0, 0.5));
    }
};

Hamcrest

Hamcrest是Java社區(qū)一個(gè)輕量級(jí)的棘劣,可擴(kuò)展的Matcher框架俏让,曾被Kent Beck引入到JUnit框架中,用于增強(qiáng)斷言的機(jī)制。Cut引入了Hamcrest的設(shè)計(jì)首昔,實(shí)現(xiàn)了一個(gè)C\\+\\+移植版本的Hamcrest寡喝,使得Cut的斷言更加具有擴(kuò)展性和可讀性。

結(jié)構(gòu)
anything
匹配器 說明
anything 總是匹配
_ anything語法糖
#include <cut/cut.hpp>

USING_CUM_NS

FIXTURE(AnythingTest)
{
    TEST("should always be matched")
    {
        ASSERT_THAT(1, anything<int>());
        ASSERT_THAT(1u, anything<unsigned int>());
        ASSERT_THAT(1.0, anything<double>());
        ASSERT_THAT(1.0f, anything<float>());
        ASSERT_THAT(false, anything<bool>());
        ASSERT_THAT(true, anything<bool>());
        ASSERT_THAT(nullptr, anything<std::nullptr_t>());
    }

    TEST("should support _ as syntactic sugar")
    {
        ASSERT_THAT(1u, _(int));
        ASSERT_THAT(1.0f, _(float));
        ASSERT_THAT(false, _(int));
        ASSERT_THAT(nullptr, _(std::nullptr_t));
    }
};
比較器
匹配器 說明
eq 相等
ne 不相等
lt 小于
gt 大于
le 小于或等于
ge 大于或等于
#include <cut/cut.hpp>

USING_CUM_NS

FIXTURE(EqualToTest)
{
    TEST("should allow compare to integer")
    {
        ASSERT_THAT(0xFF, eq(0xFF));
        ASSERT_THAT(0xFF, is(eq(0xFF)));

        ASSERT_THAT(0xFF, is(0xFF));
        ASSERT_THAT(0xFF == 0xFF, is(true));
    }

    TEST("should allow compare to bool")
    {
        ASSERT_THAT(true, eq(true));
        ASSERT_THAT(false, eq(false));
    }

    TEST("should allow compare to string")
    {
        ASSERT_THAT("hello", eq("hello"));
        ASSERT_THAT("hello", eq(std::string("hello")));
        ASSERT_THAT(std::string("hello"), eq(std::string("hello")));
    }
};

FIXTURE(NotEqualToTest)
{
    TEST("should allow compare to integer")
    {
        ASSERT_THAT(0xFF, ne(0xEE));

        ASSERT_THAT(0xFF, is_not(0xEE));
        ASSERT_THAT(0xFF, is_not(eq(0xEE)));
        ASSERT_THAT(0xFF != 0xEE, is(true));
    }

    TEST("should allow compare to boolean")
    {
        ASSERT_THAT(true, ne(false));
        ASSERT_THAT(false, ne(true));
    }

    TEST("should allow compare to string")
    {
        ASSERT_THAT("hello", ne("world"));
        ASSERT_THAT("hello", ne(std::string("world")));
        ASSERT_THAT(std::string("hello"), ne(std::string("world")));
    }
};
修飾器
匹配器 說明
is 可讀性裝飾器
is_not 可讀性裝飾器
#include <cut/cut.hpp>

USING_CUM_NS

FIXTURE(IsNotTest)
{
    TEST("integer")
    {
        ASSERT_THAT(0xFF, is_not(0xEE));
        ASSERT_THAT(0xFF, is_not(eq(0xEE)));
    }

    TEST("string")
    {
        ASSERT_THAT("hello", is_not("world"));
        ASSERT_THAT("hello", is_not(eq("world")));

        ASSERT_THAT("hello", is_not(std::string("world")));
        ASSERT_THAT(std::string("hello"), is_not(std::string("world")));
    }
};
空指針
匹配器 說明
nil 空指針
#include <cut/cut.hpp>

USING_CUM_NS

FIXTURE(NilTest)
{
    TEST("equal_to")
    {
        ASSERT_THAT(nullptr, eq(nullptr));
        ASSERT_THAT(0, eq(NULL));
        ASSERT_THAT(NULL, eq(NULL));
        ASSERT_THAT(NULL, eq(0));
    }

    TEST("is")
    {
        ASSERT_THAT(nullptr, is(nullptr));
        ASSERT_THAT(nullptr, is(eq(nullptr)));

        ASSERT_THAT(0, is(0));
        ASSERT_THAT(NULL, is(NULL));
        ASSERT_THAT(0, is(NULL));
        ASSERT_THAT(NULL, is(0));
    }

    TEST("nil")
    {
        ASSERT_THAT((void*)NULL, nil());
        ASSERT_THAT((void*)0, nil());
        ASSERT_THAT(nullptr, nil());
    }
};
字符串
匹配器 說明
contains_string 斷言是否包含子串
contains_string_ignoring_case 忽略大小寫勒奇,斷言是否包含子
starts_with 斷言是否以該子串開頭
starts_with_ignoring_case 忽略大小寫预鬓,斷言是否以該子串開頭
ends_with 斷言是否以該子串結(jié)尾
ends_with_ignoring_case 忽略大小寫,斷言是否以該子串結(jié)尾
#include <cut/cut.hpp>

USING_CUM_NS

FIXTURE(StartsWithTest)
{
    TEST("case sensitive")
    {
        ASSERT_THAT("ruby-cpp", starts_with("ruby"));
        ASSERT_THAT("ruby-cpp", is(starts_with("ruby")));

        ASSERT_THAT(std::string("ruby-cpp"), starts_with("ruby"));
        ASSERT_THAT("ruby-cpp", starts_with(std::string("ruby")));
        ASSERT_THAT(std::string("ruby-cpp"), starts_with(std::string("ruby")));
    }

    TEST("ignoring case")
    {
        ASSERT_THAT("ruby-cpp", starts_with_ignoring_case("Ruby"));
        ASSERT_THAT("ruby-cpp", is(starts_with_ignoring_case("Ruby")));

        ASSERT_THAT(std::string("ruby-cpp"), starts_with_ignoring_case("RUBY"));
        ASSERT_THAT("Ruby-Cpp", starts_with_ignoring_case(std::string("rUBY")));
        ASSERT_THAT(std::string("RUBY-CPP"), starts_with_ignoring_case(std::string("ruby")));
    }
};
浮點(diǎn)數(shù)
匹配器 說明
close_to 斷言浮點(diǎn)數(shù)近似等于
nan 斷言浮點(diǎn)數(shù)不是一個(gè)數(shù)字
#include <cut/cut.hpp>
#include <math.h>

USING_CUM_NS

FIXTURE(IsNanTest)
{
    TEST("double")
    {
        ASSERT_THAT(sqrt(-1.0), nan());
        ASSERT_THAT(sqrt(-1.0), is(nan()));

        ASSERT_THAT(1.0/0.0,  is_not(nan()));
        ASSERT_THAT(-1.0/0.0, is_not(nan()));
    }
};

程序選項(xiàng)

TestOptions::TestOptions() : desc("cut")
{
    desc.add({
        {"help,     h",   "help message"},
        {"filter,   f",   "--filter=pattern"},
        {"color,    c",   "--color=[yes|no]"},
        {"xml,      x",   "print test result into XML file"},
        {"list,     l",   "list all tests without running them"},
        {"progress, p",   "print test result in progress bar"},
        {"verbose,  v",   "verbosely list tests processed"},
        {"repeat,   r",   "how many times to repeat each test"}
    });
    
    // default value
    options["color"]  = "yes";
    options["repeat"] = "1";
}

設(shè)計(jì)與實(shí)現(xiàn)

核心領(lǐng)域

Cut整體的結(jié)構(gòu)其實(shí)是一棵樹赊颠,用于用例的組織和管理格二。

struct TestResult;

DEFINE_ROLE(Test)
{
    ABSTRACT(const std::string& getName () const);
    ABSTRACT(int countTestCases() const);
    ABSTRACT(int countChildTests() const);
    ABSTRACT(void run(TestResult&));
};

適配

如何讓FIXTURE中一個(gè)普通的成員函數(shù)TEST在運(yùn)行時(shí)表現(xiàn)為一個(gè)TestCase呢?在C++的實(shí)現(xiàn)中巨税,似乎變得非常困難蟋定。Cut的設(shè)計(jì)非常簡(jiǎn)單,將TEST的元信息在編譯時(shí)注冊(cè)到框架草添,簡(jiǎn)單地使用了C++元編程的技術(shù),及其C++11的一些特性保證扼仲,從而解決了C++社區(qū)一直未解決此問題的關(guān)鍵远寸。

TEST的運(yùn)行時(shí)信息由TestMethod的概念表示,其代表FIXTURE中一個(gè)普通的成員函數(shù)TEST屠凶,它們都具有同樣的函數(shù)原型: void Fixture::*)(); TestMethod是一個(gè)泛型類驰后,泛型參數(shù)是Fixture;形式化地描述為:

template <typename Fixture>
struct TestMethod
{    
    using Method = void(Fixture::*)();
};

TestCaller也是一個(gè)泛型類矗愧,它將一個(gè)TestMethod適配為一個(gè)普通的TestCase灶芝。

template <typename Fixture>
struct TestCaller : TestCase
{    
    using Method = void(Fixture::*)();

    TestCaller(const std::string& name, Method method)
        : TestCase(name), fixture(0), method(method)
    {}

private:
    OVERRIDE(void setUp())
    {
        fixture = new Fixture;
        fixture->setUp();
    }

    OVERRIDE(void tearDown())
    {
        fixture->tearDown();        
        delete fixture;
        fixture = 0;
    }

    OVERRIDE(void runTest())
    {
        (fixture->*method)();
    }

private:
    Fixture* fixture;
    Method method;
};

裝飾

TestDecorator其實(shí)是對(duì)Cut核心領(lǐng)域的一個(gè)擴(kuò)展,從而保證核心領(lǐng)域的不變性唉韭,而使其具有最大的可擴(kuò)展性和靈活性夜涕。

工廠

在編譯時(shí)通過測(cè)試用例TEST的元信息的注冊(cè),使用TestFactory很自然地將這些用例自動(dòng)生成出來了属愤。因?yàn)?code>Magallan組織用例是一刻樹女器,TestFactory也被設(shè)計(jì)為一棵樹,從而使得其與框架核心領(lǐng)域保持高度的一致性住诸,更加自然驾胆、漂亮。

監(jiān)聽狀態(tài)

Cut通過TestListener對(duì)運(yùn)行時(shí)的狀態(tài)變化進(jìn)行監(jiān)控贱呐,從而實(shí)現(xiàn)了Cut不同格式報(bào)表打印的變化丧诺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奄薇,隨后出現(xiàn)的幾起案子驳阎,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搞隐,死亡現(xiàn)場(chǎng)離奇詭異驹愚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)劣纲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門逢捺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人癞季,你說我怎么就攤上這事劫瞳。” “怎么了绷柒?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵志于,是天一觀的道長。 經(jīng)常有香客問我废睦,道長伺绽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任嗜湃,我火速辦了婚禮奈应,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘购披。我一直安慰自己杖挣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布刚陡。 她就那樣靜靜地躺著惩妇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筐乳。 梳的紋絲不亂的頭發(fā)上歌殃,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音哥童,去河邊找鬼挺份。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贮懈,可吹牛的內(nèi)容都是我干的匀泊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朵你,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼各聘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抡医,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤躲因,失蹤者是張志新(化名)和其女友劉穎早敬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體大脉,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搞监,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镰矿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琐驴。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖秤标,靈堂內(nèi)的尸體忽然破棺而出绝淡,到底是詐尸還是另有隱情,我是刑警寧澤苍姜,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布牢酵,位于F島的核電站,受9級(jí)特大地震影響衙猪,放射性物質(zhì)發(fā)生泄漏馍乙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一屈嗤、第九天 我趴在偏房一處隱蔽的房頂上張望潘拨。 院中可真熱鬧,春花似錦饶号、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扭屁,卻和暖如春算谈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背料滥。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國打工然眼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人葵腹。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓高每,卻偏偏與公主長得像,于是被迫代替她去往敵國和親践宴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鲸匿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)阻肩,斷路器带欢,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 3,665評(píng)論 0 7
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法乔煞,繼承相關(guān)的語法吁朦,異常的語法,線程的語...
    子非魚_t_閱讀 31,632評(píng)論 18 399
  • 第一部分 準(zhǔn)入訓(xùn)練 第1章 進(jìn)入忍者世界 js開發(fā)人員通常使用js庫來實(shí)現(xiàn)通用和可重用的功能渡贾。這些庫需要簡(jiǎn)單易用逗宜,...
    如201608閱讀 1,352評(píng)論 1 2
  • 2017年8月12日 還有6分鐘shizhong就會(huì)定格到8月13日,聽說英仙座的流星雨今夜會(huì)劃破長空剥啤,留下千年的...
    臻臻的心閱讀 150評(píng)論 0 0