生成测试报告
到目前为止,我们的单个测试只是在运行时打印其硬编码名称。早期有一些想法,除了测试名称之外,我们可能需要一个结果。这实际上是一个很好的示例,用于向代码中添加不需要或使用的内容。好的,一个 mninor 示例,因为我们需要一些东西来跟踪测试是通过还是失败,但它仍然是一个很好的超越我们自己的例子,因为我们实际上从未使用过 mResult 数据成员。现在打算用一种更好的方法来跟踪运行测试的结果来解决这个问题。
我们假设测试成功,除非发生导致测试失败的事情。会发生什么?最终会有很多方法导致测试失败。现在,我们只考虑例外情况。这可能是测试在检测到错误时故意抛出的异常,也可能是引发的意外异常。
我们不希望任何异常阻止测试运行。从一个测试引发的异常不应成为停止运行其他测试的理由。我们仍然只有一个测试,但我们可以确保异常不会停止整个测试过程。我们想要的是将 run
函数调用包装在一个 try
块中,以便任何异常都将被视为失败,如下所示:
inline void runTests ()
{
for (auto * test: getTests()) {
try {
test->run(); }
catch(...) {
test->setFailed("Unexpected exception thrown."); }
} }
当捕获异常时,我们要做两件事。首先是将测试标记为失败。第二种是设置消息,以便可以报告结果。问题是我们在测试接口类上没有一个名为set Failed
的方法。实际上,首先按照我们希望的方式编写代码是件好事。
事实上,测试接口的想法是让它成为一组像接口一样的纯虚拟方法。我们可以添加一个名为set Failed
的新方法,但是实现需要在派生类中编写。这似乎是测试的基本部分,能够保存结果和消息。
因此,让我们重构设计并将测试接口更改为更多的基类,并将其称为TestBase
。我们还可以从 TEST 宏中声明的类中移动数据成员,并将它们放入 Test Base
类中:
class TestBase
{
public:
TestBase (std::string_view name)
: mName(name), mPassed(true)
{ }
virtual ~TestBase ()=default;
virtual void run () = 0;
std::string_view name () const
{
return mName;
}
bool passed () const
{
return mPassed;
}
std::string_view reason () const
{
return mReason;
}
void setFailed (std::string_view reason)
{
mPassed = false;
mReason =reason;
}
}
private:
std::string mName;
bool mPassed;
std::string mReason; };
使用新的setFailed
方法,拥有 mResult
数据成员不再有意义。相反,有一个 mPass
成员和 mName
成员;两者都来自 TEST 宏。添加一些getter
方法似乎也是一个好主意,特别是现在还有一个mReason数据成员。总之,每个测试现在可以存储其名称,记住它是否通过,以及失败的原因,如果它失败。
只需要在getTests函数中稍作更改即可引用TestBase类:
inline std::vector<TestBase *> & getTests ()
{
static std::vector<TestBase *> tests;
return tests;
{
其余的更改简化了 TEST 宏,如下所示,以删除现在位于基类中的数据成员,并从 TestBase 继承:
#define TEST\
class Test : public MereTDD::TestBase\ { \
public:\
Test (std::string_view name)\
:TestBase(name)\
{ \
MereTDD::getTests() .push_back(this);\
}\
void run () override;\ };\
Test test("testCanBeCreated"); \
void Test::run ()
检查以确保所有内容再次构建和运行,表明我们返回到正在运行的程序,其结果与以前相同。您经常会在重构中看到这种技术。在重构时,最好将任何功能更改保持在最低限度,并且主要专注于恢复与以前相同的行为。
现在,我们可以进行一些更改,这些更改将影响可观察的行为。我们希望报告测试运行时发生的情况。现在,我们只将输出发送到 std::cout。
第一个更改是在 Test.h 中包含 iostream:
#define MERETDD_TEST_H
#include <iostream>
#include <string_view>
#include <vector>
inline void runTests ()
{
for (auto * test: getTests())
{
std::cout <<
\n" << test->name()
<< std::endl;
try
{
test->run();
}
catch(...)
{
test->setFailed("Unexpected exception thrown.");
}
if (test->passed())
{
std::cout << "Passed"
<< std::endl;
}
else {
std::cout << "Failed\n"
<< test->reason()
<< std::endl; }
}
}
原始的try/catch
保持不变。我们所要做的就是为分隔符和测试的名称打印一些破折号。立即将这一行刷新到输出可能是一个好主意。在以后发生的情况下,至少会记录测试的名称。测试运行后,将检查测试是否通过,并显示相应的消息。
我们还将更改 Creat ion 中的测试。CPP 扔一些东西以确保我们失败。我们不再需要包含iostream,因为显示测试本身的任何内容通常不是一个好主意。如果需要,可以显示测试的输出,但测试本身中的任何输出都倾向于弄乱测试结果的报告。当我有时需要显示来自 wvithin 测试的输出时,它通常是暂时的。
以下是修改为抛出 int 的测试:
#include"../Test.h"
TEST
{
throw 1;
}
通常,你会编写抛出简单 int 值以外的代码,但在这一点上,我们只想展示当某些东西被抛出时会发生什么。
现在,构建并运行它会显示由于意外异常而导致的预期故障:
我们可以从测试中删除 throw 语句,以便主体完全为空,测试现在将通过:
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情”