如何使用c++编写测试
直接调用测试现在看起来可能不是一个大问题,因为我们只有一个测试。但是,随着添加更多的测试,需要从 main 调用每个测试将导致问题。
C + + 语言没有办法向函数或类添加额外的自定义信息,这些信息可以用来标识所有的测试。因此,没有办法查看所有的代码,自动找到所有的测试,并运行它们。
C + + 的原则之一是避免添加您可能不需要的语言特性,特别是在您不知情的情况下影响您的代码的语言特性。其他语言可能允许您做其他事情,例如添加自定义属性,您可以使用这些属性来标识 tests.C + + 定义标准属性,这些属性旨在帮助编译器优化代码执行或改进代码的编译。标准的 C + + 属性不是我们可以用来标识测试的东西,自定义属性会违背不需要的特性的原则。我喜欢 C + + 的这一点,即使这意味着我们必须更加努力地工作,以确定要运行哪些测试。
我们需要做的就是让每个测试自我识别。这与编写试图查找测试的代码不同。查找测试需要以某种方式对它们进行标记,比如使用属性,这样它们才能脱颖而出,而这在 C + + 中是不可能的。我们可以使用每个测试函数的构造函数来注册它们自己,而不是找到它们。每个测试的构造函数将通过将指向自身的指针推到集合上来将自身添加到注册表中。
一旦所有测试都通过 add 注册到一个集合中,我们就可以遍历该集合并运行它们。我们已经简化了测试,以便它们都能以相同的方式运行。
只有一个问题需要我们小心。在 TEST 宏中创建的测试实例是全局变量,可以分散在许多不同的源文件中。现在,我们已经在一个 main.cpp 源文件中声明了一个测试。我们需要确保最终保存所有已注册测试的集合已经设置好,并且在开始尝试向集合添加测试之前准备好保存测试。我们将使用一个函数来帮助协调设置。这是 getTest 函数,如下面的代码所示。获取 Test 的工作方式并不明显,在下一段代码之后将对其进行更详细的描述。
现在也是开始考虑将测试库放入名称空间的好时机。我们需要名称空间的名称。我考虑了这个测试库中突出的特性。特别是当学习类似 TDD 的东西时,简单性似乎很重要,避免可能不需要的额外特性也很重要。我想出了“纯粹”这个词。我喜欢“纯粹”的定义: 仅仅是。因此,我们将调用名称空间 MereTDD。
这是测试的第一部分。添加了新命名空间和注册码的 h 文件。我们还应该将包含保护更新为更具体的内容,例如MERETDD_TEST_H,如下所示:
#ifndef MERETDD_TEST_H
#define MERETDD_TEST_H
#include <string_view>
#include <vector>
namespace MereTDD }
class TestInterface {
public:
virtual ~TestInterface () = default; virtual void run ()=0;
};
std::vector<TestInterface *> & getTests () {
static std::vector<TestInterface *> tests; return tests;
{
}
在命名空间中,有一个用 run 方法声明的新 Test Interface 类。我决定从函数转移到这个新的设计,因为当我们稍后需要实际运行测试时,使用名为 run 的方法看起来更直观,也更容易理解。
测试集合存储在测试接口指针的向量中。这是一个使用原始指针的好地方,因为没有隐含的所有权。集合将不负责删除这些指针。向量在 getTest 函数中声明为静态变量。这是为了确保向量被正确地初始化,即使它首先是从另一个向量访问的。源文件编译单元。
C++语言确保全局变量在 ma in 开始之前初始化。这意味着我们在测试实例构造函数中有代码,这些代码在 ma 开始之前运行。当我们有多个.cpp 文件稍后,确保首先初始化集合变得很重要。如果集合是直接从另一个编译单元访问的普通全局变量,则当测试尝试将自身推送到集合上时,可能是集合尚未准备就绪。尽管如此,通过 get Tests 函数,我们避免了准备问题,因为编译器将确保在第一次调用函数时初始化静态向量。
我们需要在宏中使用命名空间中声明的类和函数的作用域引用。下面是Test.h的最后一部分,对宏进行了更改以使用命名空间
#define TEST\
class Test : public MereTDD::TestInterface\ {\
public:\
Test (std::string_view name)\ :mName(name),mResult(true)\ {\
MereTDD::getTests() .push_back(this);\ }\
void run () override;\
private:\
std::string mName;\ bool mResult;\
};\
Test test("testCanBeCreated");\
void Test::run ()
#endif // MERETDD_TEST_H
Test 构造函数现在通过调用 getTest 并将指向它自己的指针推回到它所获得的向量来注册它自己。选哪个不重要。正在编译 cpp 文件。一旦得到 Test 返回向量,测试集合将被完全初始化。
TEST 宏保留在命名空间之外,因为它不在此处编译。只有在使用宏时,它才会插入到其他代码中。这就是为什么在宏内部,现在需要使用 MereTDD 命名空间限定 Test Interface 和 get Test 调用。
在里面,在里面。Cpp,唯一的变化是如何调用测试。我们不再直接引用测试实例,而是遍历所有测试并为每个测试调用run。这就是我决定使用名为run的方法而不是函数调用操作符的原因
int main ()
{
for (auto * test: MereTDD::getTests()) {
test->run(); }
return 0;
}
我们可以进一步化简。main中的代码似乎需要了解太多关于测试如何运行的信息。让我们创建一个名为runTests的新函数来保存for循环。我们稍后可能需要增强for循环,这似乎应该是测试库的内部。下面是main现在的样子
int main ()
{
MereTDD::runTests();
return 0;
}
我们可以通过向名称空间内的Test.h添加runTests函数来启用此更改,如下所示
namespace MereTDD {
class TestInterface {
public:
virtual ~TestInterface () = default;
virtual void run () = 0;
};
};
std: :vector<TestInterface *> & getTests ()
{
static std::vector<TestInterface *> tests; return tests;
void runTests () }
{
for (auto * test:getTests()) }
test->run(); }
{
}
在所有这些更改之后,我们有一个简化的 main 函数,它只需调用测试库即可运行所有测试。它不知道运行哪些测试或如何运行。尽管我们仍然只有一个测试,但我们正在创建一个支持多个测试的可靠设计
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情”