GTEST source code learning

5/17/2017 10:46:37 AM


Google Test Source Code Learning

<h2 id="example">Example</h2>

<span id="jump_example">SRC</span>
test case: BasicTest(测试用例), test:Negative(测试特例)

// negative numbers
TEST( BasicTest, Negative ) {
    EXPECT_EQ(1, -1);
    EXPECT_EQ(-1, -1);
}

// 0 numbers
TEST( BasicTest, Zero ) {
    EXPECT_EQ(1, 0);
    EXPECT_EQ(0.0, 0);
}

// positive numbers
TEST( BasicTest, Positive ) {
    EXPECT_EQ(1, 1);
    EXPECT_EQ(-1, 1);
}

// assert
TEST( AssertTest, Zero ) {
    ASSERT_TRUE( 0==0 );
    ASSERT_TRUE( 1==0 );
}

TEST( AssertTest, HELLOWORLD ) {
    ASSERT_STREQ( "hello world", "hell0 world" );
    ASSERT_STREQ( "hello world", "hello world" );
}

TEST( AssertTest, Near ) {
    ASSERT_NEAR( -1.0f, -1.1f, 0.2f );
    ASSERT_NEAR( -2.0f, -2.1f, 0.01f );
}

<span id="jump_output">Output</span>

[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from BasicTest
[ RUN      ] BasicTest.Negative
/home/meyan/workspace-gui-10/qtgui/gtest_learning/test_src.cpp:5: Failure
Value of: -1
Expected: 1
[  FAILED  ] BasicTest.Negative (0 ms)
[ RUN      ] BasicTest.Zero
/home/meyan/workspace-gui-10/qtgui/gtest_learning/test_src.cpp:11: Failure
Value of: 0
Expected: 1
[  FAILED  ] BasicTest.Zero (0 ms)
[ RUN      ] BasicTest.Positive
/home/meyan/workspace-gui-10/qtgui/gtest_learning/test_src.cpp:18: Failure
Value of: 1
Expected: -1
[  FAILED  ] BasicTest.Positive (0 ms)
[----------] 3 tests from BasicTest (0 ms total)

[----------] 3 tests from AssertTest
[ RUN      ] AssertTest.Zero
/home/meyan/workspace-gui-10/qtgui/gtest_learning/test_src.cpp:23: Failure
Value of: 1==0
Actual: false
Expected: true
[  FAILED  ] AssertTest.Zero (0 ms)
[ RUN      ] AssertTest.HELLOWORLD
/home/meyan/workspace-gui-10/qtgui/gtest_learning/test_src.cpp:27: Failure
Value of: "hell0 world"
Expected: "hello world"
[  FAILED  ] AssertTest.HELLOWORLD (1 ms)
[ RUN      ] AssertTest.Near
/home/meyan/workspace-gui-10/qtgui/gtest_learning/test_src.cpp:33: Failure
The difference between -2.0f and -2.1f is 0.0999999, which exceeds 0.01f, where
-2.0f evaluates to -2,
-2.1f evaluates to -2.1, and
0.01f evaluates to 0.01.
[  FAILED  ] AssertTest.Near (0 ms)
[----------] 3 tests from AssertTest (1 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 2 test cases ran. (1 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 6 tests, listed below:
[  FAILED  ] BasicTest.Negative
[  FAILED  ] BasicTest.Zero
[  FAILED  ] BasicTest.Positive
[  FAILED  ] AssertTest.Zero
[  FAILED  ] AssertTest.HELLOWORLD
[  FAILED  ] AssertTest.Near

Macro expansion

g++ main.cpp -I/usr/local/**/gmock/1.4.0/include/ -E >> main.i
g++ test_src.cpp -I/usr/local/**/gmock/1.4.0/include/ -E >> test_src.i

Tips:

诸如internal::HandleExceptionsInMethodIfSupported的函数都是为了支持跨平台测试, 这里除非显示调用,其余的这类函数或处理都将被忽略。

Questions:

  • How to run test?
  • How to store test?
  • How to print result?

<h2 id="1">1. 自动调度机制</h2>
gtest main function

#include <gtest/gtest.h>
int main( int argc, char **argv )
{
    testing::InitGoogleTest( &argc, argv ); // parameter, filter...
    return RUN_ALL_TESTS(); // run tests
}

<h3 id="1.1">1.1 RUN_ALL_TESTS</h3>

Expand RUN_ALL_TESTS():

return (::testing::UnitTest::GetInstance()->Run());

class UnitTest, 注意到class UnitTestImpl:

class UnitTest {
public:
    static UnitTest* GetInstance();
    int Run() GTEST_MUST_USE_RESULT_;
    ...
private:
    UnitTest::UnitTest() {
        impl_ = new internal::UnitTestImpl(this);
    }
    internal::UnitTestImpl* impl() { return impl_; }
    const internal::UnitTestImpl* impl() const { return impl_; }
    
    internal::UnitTestImpl* impl_;
    // We disallow copying UnitTest.
    GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest);
    ...
}

For UnitTest::RUN(),可以看到实际上调用了internal::UnitTestImpl::RunAllTests:

int UnitTest::Run() {
  return internal::HandleExceptionsInMethodIfSupported(
      impl(),
      &internal::UnitTestImpl::RunAllTests,
      "auxiliary test code (environments or event listeners)") ? 0 : 1;
}

For UnitTestImpl::RunAllTests(),实际调用了TestCase::Run(),也就是说UnitTestImpl中存了若干个TestCase(std::vector<TestCase> test_cases_):

bool UnitTestImpl::RunAllTests() {
    for (int test_index = 0; test_index < total_test_case_count(); test_index++) {
      GetMutableTestCase(test_index)->Run();
    }
}

TestCase *UnitTestImpl::GetMutableTestCase(int i) {
    const int index = GetElementOr(test_case_indices_, i, -1);
    return index < 0 ? NULL : test_cases_[index];
}

For class TestCase, 实际调用了TestInfo::Run(),即在TestCase中存储了若干的TestInfo:

void TestCase::Run() {
    for (int i = 0; i < total_test_count(); i++) {
    GetMutableTestInfo(i)->Run();
}

TestInfo* TestCase::GetMutableTestInfo(int i) {
    const int index = GetElementOr(test_indices_, i, -1);
    return index < 0 ? NULL : test_info_list_[index];
}

For class TestInfo,可以看到实际的test是由一个工厂类产生并进行测试:

void TestInfo::Run() {
    // Creates the test object.
    Test* const test = internal::HandleExceptionsInMethodIfSupported(
        factory_, &internal::TestFactoryBase::CreateTest,
        "the test fixture's constructor");
    test->Run();
}

Test* const test is create by factory_. test is built in runtime.

void Test::Run() {
    internal::HandleExceptionsInMethodIfSupported(
    this, &Test::TestBody, "the test body");
}

<span id="jump_flow">Call flow</span>


What are TestCase, TestInfo?
How to create test?
Where and when builds these tests?

<h3 id="1.2">1.2 How to add tests?</h3>
source code

无论是main还是source code, 都没有显式调用测试的位置,所以只能分析TEST宏。

# define TEST(test_case_name, test_name) \ 
    GTEST_TEST(test_case_name, test_name)

GTEST_TEST:

#define GTEST_TEST(test_case_name, test_name)\
  GTEST_TEST_(test_case_name, test_name, \
              ::testing::Test, ::testing::internal::GetTestTypeId())

GTEST_TEST_:

#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
 public:\
  GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
 private:\
  virtual void TestBody();\
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
  GTEST_DISALLOW_COPY_AND_ASSIGN_(\
      GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
};\
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
  ::test_info_ =\
    ::testing::internal::MakeAndRegisterTestInfo(\
        #test_case_name, #test_name, NULL, NULL, \
        ::testing::internal::CodeLocation(__FILE__, __LINE__), \
        (parent_id), \
        parent_class::SetUpTestCase, \
        parent_class::TearDownTestCase, \
        new ::testing::internal::TestFactoryImpl<\
            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()

For TEST( BasicTest, Negative ), macro expansion:

class BasicTest_Negative_Test : public ::testing::Test { 
    public: 
        BasicTest_Negative_Test() {} 
    private: 
        virtual void TestBody(); 

        static ::testing::TestInfo* const test_info_; 

        BasicTest_Negative_Test(const BasicTest_Negative_Test &); 
        void operator=(const BasicTest_Negative_Test &);
 };

 ::testing::TestInfo* const BasicTest_Negative_Test ::test_info_ = 
            ::testing::internal::MakeAndRegisterTestInfo( 
            "BasicTest", "Negative", "", "",
            (::testing::internal::GetTestTypeId()),
            ::testing::Test::SetUpTestCase, ::testing::Test::TearDownTestCase, 
            new ::testing::internal::TestFactoryImpl< BasicTest_Negative_Test>);

  void BasicTest_Negative_Test::TestBody() {
       EXPECT_EQ(1, -1);
       EXPECT_EQ(-1, -1);
  }

也就是说,EXPECT_EQ就是我们需要的实现。GTEST是通过TestBody()进行测试。那说明test_info就是保存我们测试特例的位置。MakeAndRegisterTestInfo:

TestInfo* MakeAndRegisterTestInfo(  
    const char* test_case_name,  
    const char* name,  
    const char* type_param,  
    const char* value_param,  
    CodeLocation code_location,  
    TypeId fixture_class_id,  
    SetUpTestCaseFunc set_up_tc,  
    TearDownTestCaseFunc tear_down_tc,  
    TestFactoryBase* factory) {  
  TestInfo* const test_info =  
    new TestInfo(test_case_name, name, type_param, value_param,  
                 code_location, fixture_class_id, factory);  
    GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);  
    return test_info;  
}

TestFactoryImpl工厂类, 用于创建出BasicTest_Negative_Test用于测试:

template <class TestClass>  
class TestFactoryImpl : public TestFactoryBase {  
 public:  
  virtual Test* CreateTest() { return new TestClass; }  
};

GetUnitTestImpl()->AddTestInfo():

void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc,
    Test::TearDownTestCaseFunc tear_down_tc,
    TestInfo* test_info) {
    GetTestCase(test_info->test_case_name(), test_info->type_param(), set_up_tc, tear_down_tc)->AddTestInfo(test_info);
  }

GetTestCase, 从UnitTestImpl中拿到对应的TestCase:

TestCase* UnitTestImpl::GetTestCase(const char* test_case_name,
                                const char* type_param,
                                Test::SetUpTestCaseFunc set_up_tc,
                                Test::TearDownTestCaseFunc tear_down_tc) {
    // Can we find a TestCase with the given name?
    const std::vector<TestCase*>::const_iterator test_case =
     std::find_if(test_cases_.begin(), test_cases_.end(),
               TestCaseNameIs(test_case_name));

    if (test_case != test_cases_.end())
        return *test_case;

    // No.  Let's create one.
    TestCase* const new_test_case =
        new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc);

    test_cases_.push_back(new_test_case);
    test_case_indices_.push_back(static_cast<int>(test_case_indices_.size()));
    return new_test_case;
}

AddTestInfo, 向TestCase添加TestInfo:

void TestCase::AddTestInfo(TestInfo * test_info) {
    test_info_list_.push_back(test_info);
    test_indices_.push_back(static_cast<int>(test_indices_.size()));
}

现在TestCase, TestInfo, TestBody()已经拿到了, 但是test怎么跑起来?测试用例,测试特例哪时候注册?
static ::testing::TestInfo* const test_info_, static变量是先于main()初始化的,所以在main()开始之前, UnitTest, UnitTestImpl, TestCase, TestInfo 都会被初始化, 这样保证了测试自动调度。call flow.

<h2 id="2">2. 结果统计机制</h2>
output

  • 有多少test_case
  • test_case有多少个test
  • test_case有多少个test成功
  • test_case有多少个test失败
  • 失败的原因、位置、期待与实际的结果

这个我们只关注如何统计结果,调用将在Listener机制中解释。
所有的统计方法都是调用UnitTest的方法实现, 实际上就是对UnitTestImpl接口的调用:

class UnitTestImpl {
// Gets the number of successful test cases.
  int successful_test_case_count() const;

  // Gets the number of failed test cases.
  int failed_test_case_count() const;

  // Gets the number of all test cases.
  int total_test_case_count() const;

  // Gets the number of all test cases that contain at least one test
  // that should run.
  int test_case_to_run_count() const;

  // Gets the number of successful tests.
  int successful_test_count() const;
  ....
};

只关注UnitTestImpl::failed_test_case_count():

int UnitTestImpl::failed_test_case_count() const {
    return CountIf(test_cases_, TestCaseFailed);
}

CountIf模板:

template <class Container, typename Predicate>
inline int CountIf(const Container& c, Predicate predicate) {
  int count = 0;
  for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) {
     if (predicate(*it))
         ++count;
  }
  return count;
}

// instance
inline int CountIf(const std::vector<TestCase>& c, TestCaseFailed predicate) {
    for ( std::vector<TestCase>::const_iterator it = c.begin(); it != c.end(); ++it) {
     if (predicate(*it))
         ++count;
  }
  return count;
}

TestCaseFailed():

static bool TestCaseFailed(const TestCase* test_case) {
  return test_case->should_run() && test_case->Failed();
}

最终调用TestResult::Failed():

// Returns true if the test failed.
bool TestResult::Failed() const {
  for (int i = 0; i < total_part_count(); ++i) {
  if (GetTestPartResult(i).failed())
      return true;
  }
  return false;
}

现在的问题是TestResult哪里产生的? 将EXPECT_EQ展开:

#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)

#define GTEST_NONFATAL_FAILURE_(message) \
  GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure)

#define GTEST_MESSAGE_(message, result_type) \
  GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type)

#define GTEST_MESSAGE_AT_(file, line, message, result_type) \
  ::testing::internal::AssertHelper(result_type, file, line, message) \
     = ::testing::Message()

出现了class AssertHelper(A class that enables one to stream messages to assertion macros).

// Message assignment, for assertion streaming support.
void AssertHelper::operator=(const Message& message) const {
    UnitTest::GetInstance()->
      AddTestPartResult(data_->type, data_->file, data_->line,
              AppendUserMessage(data_->message, message),
              UnitTest::GetInstance()->impl()->CurrentOsStackTraceExceptTop(1)
              );
}

void UnitTest::AddTestPartResult(
    TestPartResult::Type result_type,
    const char* file_name,
    int line_number,
    const std::string& message,
    const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_) {
    ...
    const TestPartResult result = TestPartResult(result_type, file_name, line_number, msg.GetString().c_str());
    impl_->GetTestPartResultReporterForCurrentThread()->ReportTestPartResult(result);
    ...
}

void DefaultGlobalTestPartResultReporter::ReportTestPartResult(
    const TestPartResult& result) {
    unit_test_->current_test_result()->AddTestPartResult(result);
    unit_test_->listeners()->repeater()->OnTestPartResult(result);
}

// Adds a test part result to the list.
void TestResult::AddTestPartResult(const TestPartResult& test_part_result) {
    test_part_results_.push_back(test_part_result);
}

<h2 id="3">3. Listener机制</h2>
<h3 id="3.1">3.1 Listener的执行</h3>
首先关注UnitTestImpl的构造函数:

listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter);

listerners()返回TestEventListeners,供开发者监听执行的过程。

class TestEventListeners {
  // The actual list of listeners.
  internal::TestEventRepeater* repeater_;
  // Listener responsible for the standard result output.
  TestEventListener* default_result_printer_;
  // Listener responsible for the creation of the XML output file.
  TestEventListener* default_xml_generator_;
}

class TestEventRepeater : public TestEventListener {  
public:  
    virtual void OnTestProgramStart(const UnitTest& unit_test);  
    virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);  
    virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);  
    virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test);  
    virtual void OnTestCaseStart(const TestCase& test_case);  
    virtual void OnTestStart(const TestInfo& test_info);  
    virtual void OnTestPartResult(const TestPartResult& result);  
    virtual void OnTestEnd(const TestInfo& test_info);  
    virtual void OnTestCaseEnd(const TestCase& test_case);  
    virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);  
    virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test);  
    virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);  
    virtual void OnTestProgramEnd(const UnitTest& unit_test);  

private:
    std::vector<TestEventListener*> listeners_;
};

执行顺序也是start正序,end逆序。


<h3 id="3.2">3.2 定制自己的Listener</h3>
如果要自定义Listener,需要实现一个继承于testing::TestEventListener或者testing::EmptyTestEventListener的类,后者更简便一些。如果想了解更多的实现,可以分析class PrettyUnitTestResultPrinter. 我们这里仅对test进行定制:

void SimplePrinter::OnTestStart( const testing::TestInfo &test_info ) {
    printf( ".....Test starting.....\n" );
    printf( "Test Case: %s\n", test_info.test_case_name() );
    printf( "Test Name: %s\n", test_info.name() );
}

void SimplePrinter::OnTestPartResult( const testing::TestPartResult &test_part_result ) {
    printf("%s in %s:%d\n%s\n",
       test_part_result.failed() ? "*** Failure" : "Success",
       test_part_result.file_name(),
       test_part_result.line_number(),
       test_part_result.summary());
}

void SimplePrinter::OnTestEnd( const testing::TestInfo &test_info ) {
    printf( ".....Test ending.....\n" );
}

Also:

::testing::TestEventListeners &listerns = ::testing::UnitTest::GetInstance()->listeners();
delete listerns.Release( listerns.default_result_printer() ); // Release vector then delete
listerns.Append( new SimplePrinter );

对比之前的output和现在的output:

.....Test starting.....
Test Case: AssertTest
Test Name: Near
*** Failure in /home/meyan/workspace-gui-10/qtgui/gtest_learning/test_src.cpp:33
The difference between -2.0f and -2.1f is 0.0999999, which exceeds 0.01f, where
-2.0f evaluates to -2,
-2.1f evaluates to -2.1, and
0.01f evaluates to 0.01.
.....Test ending.....

<h2 id="4">4. Others</h2>
<h3 id="4.1">4.1 Assertions</h3>
example, output
GTest中有两个系列:

  1. ASSERT_*
  2. EXPECT_*

EXPECT_TRUEASSERT_TRUE为例:

#define EXPECT_TRUE(condition) \  
  GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \  
                      GTEST_NONFATAL_FAILURE_)  

#define ASSERT_TRUE(condition) \  
  GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \  
                      GTEST_FATAL_FAILURE_)  

两者的区别就是在是出错时调用了GTEST_NONFATAL_FAILURE_还是GTEST_FATAL_FAILURE_, 一个返回另一个继续执行:

#define GTEST_FATAL_FAILURE_(message) \  
  return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure)  
  
#define GTEST_NONFATAL_FAILURE_(message) \  
  GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure)

<h3 id="4.2">4.2 Test Fixtures</h3>
Test Fixtures建立一个固定/一直的环境状态以确保测试可重复并且按照预期方式运行, 并且保证在不同的test中数据恒定。

TEST_F(TestFixtures, First) {  
    EXPECT_EQ(data, 0);  
    data =  1;  
    EXPECT_EQ(data, 1);  
}  

这里将TEST_FTEST同时展开可以发现二者的区别就是声明的测试特例类继承于不同的父类:

#define TEST_F(test_fixture, test_name)\  
  GTEST_TEST_(test_fixture, test_name, test_fixture, \  
      ::testing::internal::GetTypeId<test_fixture>())  

#define GTEST_TEST(test_case_name, test_name)\  
  GTEST_TEST_(test_case_name, test_name, \  
      ::testing::Test, ::testing::internal::GetTestTypeId())  

即:

class TestFixtures_First_Test : public TestFixtures {

其他的预处理如:

// test case
class TestFixtures : public ::testing::Test {
    static void SetUpTestCase()
    static void TearDownTestCase()
}

// global
class EnvironmentTest : public ::testing::Environment;

<h3 id="4.3">4.3 private code test</h3>
实际就是通过friend实现,侵入式的方法,谨慎使用:

class Foo {
public:
    Foo() {}

private:
    int Zero() const { return 0; }
    // Declare the friend tests
    FRIEND_TEST( FRIEND_TEST_Test, TEST );
};

TEST( FRIEND_TEST_Test, TEST ) {
    EXPECT_EQ( 0, Foo().Zero() );
}

macro expansion:

class Foo {
public:
    Foo() {}

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

推荐阅读更多精彩内容

  • Startup 单元测试的核心价值在于两点: 更加精确地定义某段代码的作用,从而使代码的耦合性更低 避免程序员写出...
    wuwenxiang阅读 10,090评论 1 27
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 已经同步到gitbook,想阅读的请转到gitbook: Django 1.10 中文文档 Writing you...
    leyu阅读 646评论 0 1
  • 因为unittest支持的html报告在作为邮件附加时耗时较长,故将报告扩展支持为unishark框架。 基于un...
    五娃儿阅读 521评论 0 0
  • 英语是我这一年到大三努力的一大目标。 专业课也是。 同时数学不能犹豫,加油! 少玩手机,多学习! 不要改变自己的初...
    冰鱼儿阅读 493评论 0 0