[译]ShortCUT-一个简短的C ++单元测试框架
By robot-v1.0
本文链接 https://www.kyfws.com/applications/shortcut-a-short-c-unit-testing-framework-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 10 分钟阅读 - 4786 个词 阅读量 0[译]ShortCUT-一个简短的C ++单元测试框架
原文地址:https://www.codeproject.com/Articles/17670/ShortCUT-A-Short-C-Unit-Testing-Framework
原文作者:Todd-Lucas
译文由本站 robot-v1.0 翻译
前言
A very simple, customizable unit-testing framework for C++ developers
适用于C ++开发人员的非常简单,可定制的单元测试框架
介绍(Introduction)
这个单元测试框架在一个标头中包含约125行代码.它旨在成为开始为C ++开发人员编写单元测试的最简单方法.简单,定制也很容易.许多单元测试框架需要链接到单独的库,或者需要跳过几个箍才可以开始.这会使开始编写测试更加困难.(This unit testing framework consists of about 125 lines of code in a single header. It aims to be the simplest way to get started writing unit tests for C++ developers. Being simple, it is also easy to customize. Many unit testing frameworks require linking to a separate library or require jumping through several hoops just to get started. This can make it more difficult to begin writing tests.)
开发人员着手编写单元测试时经常会遇到的一种情况是,他们已经在开发项目.如果项目没有分解成独立的库,则编写独立的单元测试可能会很困难.此外,程序的许多核心功能根本无法分解为单独的测试可执行文件.理想情况下,开发人员应该能够编写一组单元测试,并在程序本身内的单个函数调用中包含它.(One of the situations developers often face when they set out to write unit tests is that they’re already working on a project. If the project isn’t broken out into independent libraries, it can be difficult to write stand-alone unit tests. In addition, many of the core functions of a program simply can’t be broken out into a separate test executable. Ideally, a developer should be able to write a set of unit tests, include it with a single function call within the program itself, and easily) #define
稍后进行测试.(the test out later on.)
这似乎不是软件工程的强大方法.但是,通常认为,尽早进行测试通常比替代方法更好.通过最大程度地减少开始编写测试所需的精力,可以更轻松地实现早期测试的目标.随着测试套件的增长和项目的进展,可以在时间允许的情况下将测试分解为一个单独的库或可执行文件.(This may not seem like a strong approach to software engineering. However, it is generally acknowledged that testing early and often is better than the alternative. By minimizing the effort required to get started writing tests, the goal of testing early can be more easily accomplished. As the test suite grows and the project moves forward, the tests can be factored into a separate library or executable as time permits.)
背景(Background)
我开始寻找现有的解决方案.单元测试社区中的xUnit框架引起了很多兴趣和活动.它起源于Smalltalk的SUnit框架,该框架启发了JUnit的开发人员.反过来,这启发了NUnit,CppUnit和其他几个类似框架的创建者.(I started my quest by looking for existing solutions. There is a lot of interest and activity around the xUnit frameworks within the unit testing community. It originated with Smalltalk’s SUnit framework, which inspired the developers of JUnit. This, in turn, inspired the creators of NUnit, CppUnit, and several other similar frameworks.)
这些框架中大多数的共同点之一是它们是使用支持某种反射功能的语言构建的.这样可以更轻松地将属性分配给测试功能或设置功能,并将其自动包含在测试运行中. C ++开发人员并不那么幸运. C ++开发人员必须手动执行更多操作(或使用模板或宏).不过,这没什么大不了的.这就是我们的期望.(One of the things that most of these frameworks have in common is that they are built using languages that support some reflection capability. This makes it easier to assign attributes to a test function or a setup function and have it automatically included in a test run. C++ developers are not so fortunate. C++ developers must do things more manually (or resort to templates or macros). This isn’t a big deal, though. It’s what we expect.)
xUnit社区和各种可用库中有许多关于单元测试的文章.但是,令人惊讶的是,几乎没有关于C ++开发人员的选项的文章.一个这样的(Many articles have been written on unit testing in the xUnit community and the various libraries available. However, there is surprisingly little written about options for C++ developers. One such) 文章(article) 在(at) 内部游戏(Games from Within) 提供了一些可用框架的调查.(offers a survey of some of the frameworks available.)
有关一般的单元测试的介绍,请参阅以下内容之一.(For an introduction to unit testing in general, have a look at one of the) 综合文章(comprehensive articles) 在代码项目中.(here at The Code Project.)
设计(Design)
在评估了几个框架之后,我认为它们都不符合我的非常简单易用和修改的基本标准.我决定看看编写一个适合单个标头并包含尽可能少的代码行的框架会有多困难.这是我的基本设计准则的列表:(After evaluating several frameworks, I decided that none of them met my basic criteria of being extremely simple to use and modify. I decided to see how hard it would be to write a framework that would fit into a single header and would consist of as few lines of code as possible. Here is a list of my basic design criteria:)
- 适合单个头文件(无源模块或库)(Fits into a single header file (no source modules or libraries))
- 不超过几百行代码(No longer than a couple of hundred lines of code)
- 易于修改和扩展(Easy to modify and extend)
- 可重新路由的消息输出(Re-routable message output)
- 可选宏(Optional macros)
- 没有模板(No templates)
- 没有动态内存分配(No dynamic memory allocation)
- 在嵌入式环境中可用(Usable in an embedded environment)
- 在低级C ++编译器上可用(没有高级C ++功能)(Usable on down-level C++ compilers (no fancy C++ features))
它不应该动态分配任何内存的设计约束将允许在堆栈上创建测试.这样可以很容易地编写一个简单的(The design constraint that it should not dynamically allocate any memory would allow tests to be created on the stack. This would make it easy to write a simple)
main
程序入口点,只需一次声明并运行测试,而不必担心清理或内存泄漏.(program entry point and just declare and run the tests all at once without worrying about cleanup or memory leaks.)
这些约束的结果是每个测试都需要一个类.使用函数指针的另一种选择是不允许在不分配内存的情况下在套件内链接测试.另外,使用函数指针似乎并不符合C ++的精神.(A consequence of these constraints was that a class would be required for each test. The alternative of using a function pointer wouldn’t allow for chaining tests within a suite without allocating memory. Also, it didn’t seem in the spirit of C++ to use function pointers.)
使用代码(Using the Code)
要启动测试并运行,需要做三件事:(To get a test up and running, three things are necessary:)
- 必须编写一个测试用例(A test case must be written)
- 可以编写测试套件(A test suite may be written)
- 测试套件和测试用例必须添加到运行器类中,然后调用(The test suite and test case must be added to a runner class and then called) 我们将在插图中遵循此顺序.用于下载的示例有所不同.(We’ll follow this sequence in the illustration. The sample included for download is different.)
编写测试用例(Writing a Test Case)
在此示例中,我们从(In this example, we derive our test case from the) TestCase
基类.(base class.) TestCase
与框架的其他组件一样,是(, like the other components of the framework, is a) struct
.这可以帮助我们避免很多(. This helps us avoid a lot of) public
访问说明符.(access specifiers.)
测试代码已添加到单个(Test code is added to the single) test
方法.的(method. The) TestSuite
类包含在测试用例之间共享的任何数据,并将其传递给每个(class contains any data that is shared across test cases and it is passed to every) test
呼叫.的(call. The) name
方法用于在测试用例失败的情况下提供有意义的输出.(method is used to provide meaningful output in the event of a test case failure.)
struct TestAccountWithdrawal : TestCase
{
const char* name() { return "Account withdrawal test"; }
void test(TestSuite* suite)
{
TestAccountSuite* data = (TestAccountSuite*)suite;
data->account->Deposit(10);
bool succeeded = data->account->Withdraw(11);
T_ASSERT(succeeded == false);
T_ASSERT(data->account->Balance() == 10);
}
};
添加测试套件(Adding a Test Suite)
测试套件包含一组相关测试.它可以在其他一些单元测试框架中同时满足测试套件和测试夹具的目的.(The test suite contains a group of related tests. It serves the purpose of both the test suite and test fixture in some other unit testing frameworks.)
两种关键方法(均为可选)是(The two key methods (both optional) are) setup
和(and) teardown
.每个对测试用例的调用都与此调用对一起构成.(. Each call to a test case is framed with this call pair.)
struct TestAccountSuite : TestSuite
{
const char* name() { return "Account suite"; }
void setup()
{
account = new Account();
}
void teardown()
{
delete account;
}
Account* account;
};
放在一起(Putting It All Together)
一旦编写了测试套件和至少一个测试用例,就可以将它们添加到运行器并执行.(Once a test suite and at least one test case have been written, they may be added to a runner and executed.)
#include <span class="code-keyword"><stdio.h></span>
#include <span class="code-string">"shortcut.h"</span>
#include <span class="code-string">"tests/account.h"</span>
int main(int argc, char* argv[])
{
TestRunner runner;
TestAccountSuite accountSuite;
TestAccountWithdrawal accountWithdrawalTest;
accountSuite.AddTest(&accountWithdrawalTest);
runner.AddSuite(&accountSuite);
runner.RunTests();
return 0;
}
这种轻量级系统的好处之一是所有测试代码都可以保存在标头中.这样可以避免在单独的类声明和实现之间进行某些重复.由于单元测试框架是在单个标头中实现的,因此仅单个驱动程序模块(包含(One of the benefits of this lightweight system is that all test code may be kept in headers. This avoids some duplication between a separate class declaration and implementation. Because the unit test framework is implemented in a single header, only a single driver module (containing) main
(例如).(, for example) is required.)
该系统还使添加测试到现有应用程序变得容易.例如,可以在程序启动时在(This system also makes it easy to add tests to an existing application. For example, the tests could be called at program startup within a) #ifdef DEBUG
部分.在发布模式下,应用程序二进制文件中不存在测试的痕迹.链接到其他单元测试库时,情况可能并非如此.(section. In release mode, no trace of the tests would exist in the application binary. This may not be the case when linking to other unit testing libraries.)
显然,这不是一个长期的解决方案.不过,这是入门的好方法.开发人员可以立即开始编写测试,并且在时间允许的情况下,可以将测试分解为单独的可执行文件.(Obviously, this is not a long-term solution. It is a good way to get started, though. Developers can start writing tests immediately and the tests can be factored into a separate executable when time permits.)
内部构造(Internals)
本节可能被跳过.它解释了(很少)移动部件的工作原理.(This section may be skipped. It explains a little bit about how the (very few) moving pieces work.)
基类(Base Classes)
所有测试用例都来自一个非常基础的类,称为(All test cases derive from a very basic class, called) TestCase
.(.)
struct TestCase
{
TestCase() : next(0) {}
virtual void test(TestSuite* suite) {}
virtual const char* name() { return "?"; }
TestCase* next;
};
该班有一个(The class has a) name
访问器方法,用于记录错误.测试套件使用(accessor method, which is used for logging errors. The test suite uses the) next
将测试用例链接到链接列表的指针.测试本身是在虚拟环境中实现的(pointer to chain the test cases into a linked list. The test itself is implemented in the virtual) test
方法.(method.)
测试套件具有相同的结构,除了它包含测试列表并且具有不同的方法来覆盖:(The test suite has the same structure, except that it contains a list of tests and has different methods to override:) setup
和(and) teardown
.(.)
struct TestSuite
{
TestSuite() : next(0), tests(0) {}
virtual void setup() {}
virtual void teardown() {}
virtual const char* name() { return "?"; }
void AddTest(TestCase* tc)
{
tc->next = tests;
tests = tc;
}
TestSuite* next;
TestCase* tests;
};
如前所述,测试套件类与其他框架中的测试套件和测试夹具类具有相同的作用.在这样的框架中,套件通常扮演测试分组构造的角色,而夹具提供设置/拆卸机制.由于ShortCUT是这样一个简单的框架,因此无需创建这种附加级别的复杂性.如果开发团队需要此功能,则可以轻松地将其作为自定义添加.(As stated earlier, the test suite class plays the same role as the test suite and test fixture classes in other frameworks. In such frameworks, suites often play the role of a test grouping construct while the fixture provides a setup/teardown mechanism. Since ShortCUT is such a simple framework, there was no need to create this additional level of complexity. If a development team needs this feature, it may be easily added as a customization.)
跑步者(The Runner)
测试运行程序是系统的心脏.它也非常简单.主要程序(The test runner is the heart of the system. It, too, is very straightforward. The main routine,) RunTests
来电(calls) RunSuite
每个套房.(for each suite.)
struct TestRunner
{
...
void RunSuite(TestSuite* suite, int& testCount, int& passCount)
{
TestCase* test = suite->tests;
while (test)
{
try
{
suite->setup();
test->test(suite);
passCount++;
}
catch (TestException& te)
{
log->write("FAILED '%s': %s\n", test->name(), te.text());
}
catch (...)
{
log->write("FAILED '%s': unknown exception\n", test->name());
}
try
{
suite->teardown();
}
catch (...)
{
log->write("FAILED: teardown error in suite '%s'\n", suite->name());
}
test = test->next;
testCount++;
}
}
...
}
这里要注意的关键点是,首先,(The key points to note here are that, first, the) log
类可以在框架之外实现和设置.这样可以轻松地将结果显示到另一个输出目标,例如窗口.(class can be implemented and set outside of the framework. This makes it easy to display results to another output target, such as a window.)
第二点可能是令人烦恼的是,测试套件和测试用例在单个链接列表中链接在一起.这意味着它们将按照LIFO顺序遍历并执行.这与添加顺序相反.(The second point, which can be an annoyance, is that the test suites and test cases are chained together in singly-linked lists. This means that they are traversed and executed in LIFO order. This is the reverse from the order in which they were added.)
定制框架以解决此类烦恼将是一件简单的事情.我选择不这样做,因为目标是使框架尽可能简单.(It would be a simple matter to customize the framework to fix annoyances like this. I chose not to, since the goal was to make the framework as simple as possible.)
定制和结论(Customization and Conclusion)
该框架的主要目标是在设计要求和约束范围内拥有绝对最简单的系统.每行代码的价值都经过仔细检查.在某些情况下,例如(The main goal of the framework was to have the absolute simplest system possible, within the design requirements and constraints. Every line of code was scrutinized for its value. In some cases, such as with the) TestLog
课堂上,添加了几行,因为它们有助于满足设计要求.即使该框架本来会更简单,但却会失去基本的灵活性.(class, a few lines were added because they helped to meet a design requirement. Even though the framework would have been simpler, it would have lost basic flexibility.)
标头是大约200行代码.实际上,四分之一的代码是不必要的.它作为一个示例说明如何通过异常实现自定义断言功能,以及如何实现几个帮助程序宏以避免重复代码.(The header is about 200 lines of code. A quarter of the code is actually unnecessary. It was included as an example of how to implement custom assert functionality through exceptions and how to implement a couple of helper macros to avoid repetitive code.)
该框架以其基本形式可用.希望它将成为针对使用它们的开发人员的需求量身定制的系统的基础(而不是相反).它应提供足够的实用程序来快速上手,其基本结构应使其易于修改,自定义和扩展.(The framework is useable in its basic form. It is hoped that it will form the basis of systems that are tailored to the needs of the developers who use them (instead of the other way around). It should provide enough utility to get going quickly, and its basic structure should make it easy to modify, customize, and extend going forward.)
历史(History)
- 15(15)日(th)2007年2月:原始文章(February, 2007: Original article)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C++ Windows VS2005 Visual-Studio VS.NET2003 Dev QA Architect 新闻 翻译