1. CppUnit是xUnit系列中的c++实现版本,它是从JUnit移植过来的,第一个移植版本由Michael Feathers完成,安装cppunit,你可以在此
下载cppunit的最新版本,最新版本是CppUnit release 1.12.0,安装方法,现解压,然后到文件夹下找到INSTALL-WIN32.txt(windows平台)
2.打开\examples下的examples.dsw,编译链接即可完成。
3.分析所要测试的类class
class Money
{
public:
Money( double amount, std::string currency )
: m_amount( amount )
, m_currency( currency )
{
}
double getAmount() const
{
return m_amount;
}
std::string getCurrency() const
{
return m_currency;
}
bool ōperator ==( const Money &other ) const
{
return m_amount == other.m_amount &&
m_currency == other.m_currency;
}
bool operator !=( const Money &other ) const
{
return !(*this == other);
}
Money &operator +=( const Money &other )
{
if ( m_currency != other.m_currency )
throw IncompatibleMoneyError();
m_amount += other.m_amount;
return *this;
}
private:
double m_amount;
std::string m_currency;
};
4. 所要测试的有哪些接口呢?
我们分析一下这个类的公开的属性和方法。这些都是我们要测试的接口。
构造函数
Money( double amount, std::string currency )
接口函数有
double getAmount() const
std::string getCurrency() const
bool ōperator ==( const Money &other ) const
bool operator !=( const Money &other ) const
Money &operator +=( const Money &other )
添加一个测试集test suite
测试集包含一组相关的测试用例,他的目的像其他的框架中的test suite和test fixture共同的功能。
有两个关键的方法(都是可选的)是setup和teardown,它们总是成对出现。
struct TestAccountSuite : TestSuite
{
const char* name() { return "Account suite"; }
void setup()
{
account = new Account();
}
void teardown()
{
delete account;
}
Account* account;
};
把它们组织到一起
一旦一个测试集和至少一个测试用例完成之后我们可以将它们放到一个runner中执行。
#include <stdio.h>
#include "shortcut.h"
#include "tests/account.h"
int main(int argc, char* argv[])
{
TestRunner runner;
TestAccountSuite accountSuite;
TestAccountWithdrawal accountWithdrawalTest;
accountSuite.AddTest(&accountWithdrawalTest);
runner.AddSuite(&accountSuite);
runner.RunTests();
return 0;
}
这个很小的系统的好处是能够都在一个头文件中。这就防止不同类的声明定义重复。因为之用一个头文件,所以只需要要一个驱动(driver),比如在main函数中。
这个系统可以很容易添加一个测试用例到现有的程序中。比如,一些用例被#ifdef DEBUG宏控制块,在Release版本就不会输出到二进制文件中,不会连接到其他单元测试库上。
显然这不是长效的解决方法,尽管是一个好的方法的开始。开发人员能够在开发时间内分离测试和code才是
基类
所有的测试用例派生与一个基类TestCase.
struct TestCase
{
TestCase() : next(0) {}
virtual void test(TestSuite* suite) {}
virtual const char* name() { return "?"; }
TestCase* next;
};
有个名字的方法用来记录错误的。用一个指针指向测试用例的列表。它本身是个虚函数能够派生。
测试集test suite具有相同的结构,除了包含一系列case和两个重载函数setup和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;
};
像开始提及的一样这个test suite类扮演着其他框架中的 test suite和test fixture类的角色. 这在写框架中,当fixture提供setup/teardown机制时,suite常常扮演测试组织的角色,因为 ShortCUT是一个简单的框架,就不需要创建这些复杂的类了。当开发需要一个类似的功能是很容易定制的加上
执行Runner
一个测试的runner是测试的主体,当然也是一个非常直接的他的主线是调用测试的方法,run每个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++;
}
}
...
}
这个关键点要注意,首先记录类执行在框架之外。这就容易让结果输出到另外一个目标中,像一个窗体。第二点,烦恼的是测试集合和测试用例像个链子一样,这就像一个LIFO的规则一样,反向与添加时候的顺序。
这就会有一个简单的方式能够修复这个问题,但是为了框架的简单性我删除了它。
这个框架的主要目标是是个尽量简单的解决方案。像TestLog类可以加到上面来帮助满足需求,尽管框架是简单的,但也不会失去基本的机动性。
头文件有200行代码,四分之一是不需要的,希望能能够组建一个能够剪裁的系统、能够很容易使用修改定制的系统。