GTEST入门介绍


原文链接:Google C++单元测试框架---Gtest框架简介(译文) - 超超boy - 博客园

一、引入头文件,简单测试

使用TEST()宏来定义和命名测试函数,这些是不返回值的普通C++函数。在此函数中,连同要包括的任何有效的C++语句,使用各种Google Test断言来检查值。测试的结果由断言确定; 如果测试中的任何断言失败(致命或非致命),或者如果测试崩溃,则整个测试失败,否则成功。

TEST(test_case_name, test_name) {
 ... test body ...
}

TEST()参数从一般到特定。 第一个参数是测试用例的名称,第二个参数是测试用例中的测试名称。 这两个名称必须是有效的C ++标识符,并且它们不应包含下划线(_)。 测试的全名由其包含的测试用例及其个人名称组成。来自不同测试用例的测试可以具有相同的个人名称。

例如,让我们使用一个简单的整数函数:

int Factorial(int n); // Returns the factorial of n;n!

 此函数的测试用例可能如下所示:

/ Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(1, Factorial(0));
}
 
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
} 

 Google Test通过测试用例对测试结果进行分组,因此逻辑相关的测试应该在同一测试用例中; 换句话说,它们的TEST()的第一个参数应该是相同的。 在上面的例子中,我们有两个测试,HandlesZeroInput和HandlesPositiveInput,属于同一个测试用例FactorialTest。

二、测试夹具(Test Fixtures)

如果你发现自己写了两个或更多的测试来操作类似的数据,你可以使用测试夹具。它允许您为几个不同的测试重复使用相同的对象配置。要创建夹具,对多个测试使用相同的数据配置,只需:

  1. 从:: testing :: Test派生一个类。 使用protected:或public:开始它的主体,因为我们想从子类     访问fixture成员。
  2. 在类中,声明你打算使用的任何对象。
  3. 如果需要,可以编写默认构造函数或SetUp()函数来为每个测试准备对象。 一个常见的错误是     拼写SetUp()为Setup()与一个小u -- 不要让这种情况发生在你身上。
  4. 如果需要,写一个析构函数或TearDown()函数来释放你在SetUp()中分配的任何资源。 要     学习什么时候应该使用构造函数/析构函数,当你应该使用SetUp()/ TearDown()时,请阅读这个 FAQ entry.。
  5. 如果需要,定义要分享的测试的子程序。

当使用夹具时,使用TEST_F()而不是TEST(),因为它允许您访问测试夹具中的对象和子程序:

TEST_F(test_case_name, test_name) {
 ... test body ...
}

和TEST()一样,第一个参数是测试用例名,但是对于TEST_F()第一个参数必须是测试夹具类的名称。 你可能猜到了:_F是夹具。不幸的是,C ++宏系统不允许我们创建一个可以处理两种类型的测试的宏。 使用错误的宏会导致编译器错误。

另外,在TEST_F()中使用它之前,你必须首先定义一个测试夹具类,否则将得到编译器错误“virtual outside class declaration”。

对于使用TEST_F()定义的每个测试,Google Test将:

  1. 在运行时创建一个新的测试夹具
  2. 立即通过SetUp()初始化
  3. 运行测试
  4. 通过调用TearDown()清除
  5. 删除测试夹具。 请注意,同一测试用例中的不同测试具有不同的测试夹具对象,Google测试始终会删除测试夹具,然后再创建下一个测试夹具。 Google测试不会为多个测试重复使用相同的测试夹具。一个测试对夹具的任何更改不会影响其他测试

例如,让我们为名为Queue的FIFO队列类编写测试,它有以下接口:

template  // E is the element type.
class Queue {
 public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // Returns NULL if the queue is empty.
  size_t size() const;
  ...
};

首先定义一个夹具类。按照惯例,你应该给它名称QueneTest,其中Quene是被测试的类

class QueueTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }
 
  // virtual void TearDown() {}
 
  Queue q0_;
  Queue q1_;
  Queue q2_;
};

在这种情况下,不需要TearDown(),因为我们不必在每次测试后清理,除了析构函数已经做了什么。现在我们将使用TEST_F()和这个夹具编写测试。QueneTest就是测试夹具,也就是上面定义的夹具类。夹具类把一些基础基本通用工作做好,仅在TEST_F()中做测试即可,

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(0, q0_.size());
}
 
TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(NULL, n);
 
  n = q1_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0, q1_.size());
  delete n;
 
  n = q2_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1, q2_.size());
  delete n;
}

上面使用ASSERT_ *和EXPECT_ *断言。 经验法则( The rule of thumb )是当你希望测试在断言失败后继续显示更多错误时使用EXPECT_ *,或是在失败后继续使用ASSERT_ *没有意义。 例如,Dequeue测试中的第二个断言是ASSERT_TRUE(n!= NULL),因为我们需要稍后解引用指针n,这将导致n为NULL时的segfault。

当这些测试运行时,会发生以下情况:

  1.Google Test构造了一个QueueTest对象(我们称之为t1)。 
  2.t1.SetUp()初始化t1。 
  3.第一个测试(IsEmptyInitially)在t1上运行。 
  4.t1.TearDown()在测试完成后清理。 
  5.t1被析构。 
  6.以上步骤在另一个QueueTest对象上重复,这次运行DequeueWorks测试。

三、调用测试

TEST()和TEST_F()用Google Test隐式注册他们的测试。 因此,与许多其他C ++测试框架不同,您不必重新列出所有定义的测试以运行它们。

定义测试后,可以使用RUN_ALL_TESTS()运行它们,如果所有测试成功则返回0,否则返回1。 请注意,RUN_ALL_TESTS()运行链接单元中的所有测试 - 它们可以来自不同的测试用例,甚至是不同的源文件。

调用时,RUN_ALL_TESTS()宏:

  1. 保存所有Google测试标记的状态。
  2. 为第一个测试创建测试夹具对象。
  3. 通过SetUp()初始化它。
  4. 在fixture对象上运行测试。
  5. 通过TearDown()清除夹具。
  6. 删除夹具。
  7. 恢复所有Google测试标志的状态。
  8. 重复上述步骤进行下一个测试,直到所有测试运行结束。

此外,如果测试夹具的构造函数在步骤2中产生致命故障,则步骤3-5没有意义,因此它们被跳过。 类似地,如果步骤3产生致命故障,则将跳过步骤4。

重要:您不能忽略RUN_ALL_TESTS()的返回值,否则gcc将给您一个编译器错误。 此设计的基本原理是自动测试服务基于其退出代码而不是其stdout / stderr输出来确定测试是否已通过; 因此您的main()函数必须返回RUN_ALL_TESTS()的值

此外,您应该只调用一次RUN_ALL_TESTS()。 多次调用它会与一些高级Google测试功能(例如线程安全死亡测试)冲突,因此不受支持。

四、Environment类

#include 
#include 

useing name space testing;
class TestEnv:public::testing::Environment{
public:
    ~TestEnv() override = default;
    void Setup override{
        Environment::SetUp();

    }
    void TearDown override{
        Environment::TearDown();

    }
};

TestEnv *g_env;

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  g_env = dynamic_cast(AddGoogleTestEnvironment(new TestEnv()));
  return RUN_ALL_TESTS();
}

五、完整举例

首先在编译脚本添加如下命令

NAME = test ;
TYPE = exe ;
LIBS += m ;

#if $(ENABLE_GTEST) = 1
{
    DEFINES += DGETST ;
}
#include "this/package/foo.h"
#include 
#include 

#ifdef DGETST 
#define private public
#define protect public
#endif
 
namespace {
// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
 protected:
  // You can remove any or all of the following functions if its body
  // is empty.
 
  FooTest() {
    // You can do set-up work for each test here.
  }
 
  virtual ~FooTest() {
    // You can do clean-up work that doesn't throw exceptions here.
  }
 
  // If the constructor and destructor are not enough for setting up
  // and cleaning up each test, you can define the following methods:
 
  virtual void SetUp() {
    // Code here will be called immediately after the constructor (right
    // before each test).
  }
 
  virtual void TearDown() {
    // Code here will be called immediately after each test (right
    // before the destructor).
  }
 
  // Objects declared here can be used by all tests in the test case for Foo.
};
 
// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
  const string input_filepath = "this/package/testdata/myinputfile.dat";
  const string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}
 
// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
  // Exercises the Xyz feature of Foo.
}
 
}  // namespace
 
int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

:: testing :: InitGoogleTest()函数解析Google测试标志的命令行,并删除所有已识别的标志。 这允许用户通过各种标志控制测试程序的行为,我们将在AdvancedGuide中介绍。 在调用RUN_ALL_TESTS()之前必须调用此函数,否则标志将无法正确初始化。

在Windows上,InitGoogleTest()也适用于宽字符串,因此它也可以在以UNICODE模式编译的程序中使用。

但也许你认为编写所有这些main()函数是太多的工作? 我们完全同意你的看法,这就是为什么Google Test提供了main()的基本实现。 如果它适合你的需要,然后只是链接你的测试与gtest_main库。

六、Visual C ++用户的重要说明

如果你把你的测试放入一个库,你的main()函数在不同的库或在你的 .exe文件中,这些测试将不会运行。 原因是Visual C ++中的一个错误。 当您定义测试时,Google测试会创建一些静态对象来注册它们。 这些对象没有从其他地方引用,但它们的构造函数仍然应该运行。 当Visual C ++链接器发现库中没有从其他地方引用时,它会抛出该库。 您必须通过主程序中的测试来引用您的库,以防止链接器丢弃它。 这里是如何做到的。 在你的库代码中声明一个函数:

​用宏定义定义出常用函数
#define RESULT_CHECK_GOTO(function,point)\
do{\
     int retVal=function;\
     if(true != retVal)\
     {\
       printf("%s return value %d",#function,(int)retVal);\
       goto point;\
     }\
}while(0)

demo:
RESULT_CHECK_GOTO(testFunction(a,b),step1);

C/C++怎么做好单元测试 - 简书

你可能感兴趣的:(windows,测试用例)