{
// Read tests from test/data/tx_valid.json
// Format is an array of arrays
// Inner arrays are either [ "comment" ]
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, verifyFlags
// ... where all scripts are stringified scripts.
//
// verifyFlags is a comma separated list of script verification flags to apply, or "NONE"
UniValue tests = read_json(std::string(json_tests::tx_valid, json_tests::tx_valid + sizeof(json_tests::tx_valid)));
ScriptError err;
for (unsigned int idx = 0; idx < tests.size(); idx++) {
UniValue test = tests[idx];
std::string strTest = test.write();
if (test[0].isArray())
{
if (test.size() != 3 || !test[1].isStr() || !test[2].isStr())
{
BOOST_ERROR("Bad test: " << strTest);
continue;
}
std::map<COutPoint, CScript> mapprevOutScriptPubKeys;
std::map<COutPoint, int64_t> mapprevOutValues;
UniValue inputs = test[0].get_array();
bool fValid = true;
for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) {
const UniValue& input = inputs[inpIdx];
if (!input.isArray())
{
fValid = false;
break;
}
UniValue vinput = input.get_array();
if (vinput.size() < 3 || vinput.size() > 4)
{
fValid = false;
break;
}
COutPoint outpoint(uint256S(vinput[0].get_str()), vinput[1].get_int());
mapprevOutScriptPubKeys[outpoint] = ParseScript(vinput[2].get_str());
if (vinput.size() >= 4)
{
mapprevOutValues[outpoint] = vinput[3].get_int64();
}
}
if (!fValid)
{
BOOST_ERROR("Bad test: " << strTest);
continue;
}
std::string transaction = test[1].get_str();
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
CTransaction tx(deserialize, stream);
CValidationState state;
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state), strTest);
BOOST_CHECK(state.IsValid());
PrecomputedTransactionData txdata(tx);
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
{
BOOST_ERROR("Bad test: " << strTest);
break;
}
CAmount amount = 0;
if (mapprevOutValues.count(tx.vin[i].prevout)) {
amount = mapprevOutValues[tx.vin[i].prevout];
}
unsigned int verify_flags = ParseScriptFlags(test[2].get_str());
const CScriptWitness *witness = &tx.vin[i].scriptWitness;
BOOST_CHECK_MESSAGE(VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout],
witness, verify_flags, TransactionSignatureChecker(&tx, i, amount, txdata), &err),
strTest);
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
}
}
}
}
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
1.常用的C++单元测试框架
测试驱动开发(TDD)已经是一种非常流行的开发方式了,在Java和.Net中都提供了非常好的单元测试框架,最近研究C++下面的单元测试,发现其实在C++中还是有很多选择:
- CPPUnit:著名的XUnit系列产品之一,熟悉JUnit、NUnit的开发人员可以很快上手。
- CXXTest:需要进行预处理,需要安装Perl或Python。
- Boost Test:功能强大,提供了自动注册和手动注册两种方式,更重要的是来自千锤百炼的Boost库。
- Google Test:Google在去年开源的测试框架,据说其内部上千个项目均采用该框架进行单元测试。
2.Boost Test起步
先来看一个简单的Boost Test示例(来自Boost文档):
- 1#define BOOST_TEST_MODULE example
- 2
- 3#include <boost/test/unit_test.hpp>
- 4
- 5
- 6
- 7int add(int i, int j);
- 8
- 9
- 10
- 11BOOST_AUTO_TEST_SUITE(minimal_test)
- 12
- 13
- 14
- 15BOOST_AUTO_TEST_CASE(my_test)
- 16
- 17{
- 18
- 19 BOOST_CHECK(add(2, 2) == 5);
- 20
- 21 BOOST_REQUIRE(add(2, 2) == 4);
- 22
- 23 if(add(2, 2) != 4)
- 24
- 25 BOOST_ERROR("oops!");
- 26
- 27 if(add(2, 2) != 4)
- 28
- 29 BOOST_FAIL("oops!");
- 30
- 31 if(add(2, 2) != 4)
- 32
- 33 throw "oops!";
- 34
- 35 BOOST_CHECK_MESSAGE(add(2, 2) == 4, "add(..) result: " << add(2, 2));
- 36
- 37 BOOST_CHECK_EQUAL(add(2, 2), 4);
- 38
- 39}
- 40
- 41
- 42
- 43BOOST_AUTO_TEST_SUITE_END()
- 44
- 45
首先,需要定义#define BOOST_TEST_MODULE example或者#define BOOST_AUTO_TEST_MAIN,否则测试模块初始化函数需要手动实现;然后宏“BOOST_AUTO_TEST_SUITE(minimal_test)”将创建一个名为minimal_test的测试套件,并将其加入到测试模块中。宏“BOOST_AUTO_TEST_CASE(my_test)”将创建一个名为“my_test”的测试用例,并将其加入到测试套件minimal_test中。
关于测试模块常犯的一个错误是在不同的测试文件中定义不同的“#define BOOST_TEST_MODULE example”,最后将导致“multiple definition of ‘init_unit_test_suite(int, char**)’”错误,原因是在一个测试程序中只允许存在一个定义一个测试模块。测试模块中存在一个主测试套件,所有未明确包含到测试套件中的测试用例将被包含到主测试套件中。
下面是具体的测试过程了,在程序中展示了七种不同的方式来对函数add进行测试:
-
BOOST_CHECK :这种方法将检查到的错误保存起来,测试结束时由测试框架自动显示;
-
BOOST_REQUIRE :同样是检查错误,与BOOST_CHECK 不同之处在于如果发生错误时将抛出一个异常,因此后续的测试将不会继续进行;
-
BOOST_ERROR :可以用来对错误进行独立描述,测试结束时由测试框架自动显示;
-
BOOST_FAIL :同样可以用来对错误进行独立描述,调用时将抛出一个异常;
-
抛出异常方式:测试框架将捕获该异常并打印出错误信息;
-
BOOST_CHECK_MESSAGE :与BOOST_CHECK 类似,可以在第二个参数中加入错误描述信息;
-
BOOST_CHECK_EQUAL :与BOOST_CHECKL 类似,用来检查两个参数是否相等。
3.单参数测试用例
有时候需要对一个测试用例使用不同的参数运行测试,前面提到的BOOST_AUTO_TEST_CASE不能满足需求,此时需要对测试用例进行手动注册(来自Boost文档):
- #include <boost/test/included/unit_test.hpp>
- #include <boost/test/parameterized_test.hpp>
- using namespace boost::unit_test;
- void free_test_function( int i )
- {
- BOOST_CHECK( i < 4 /* test assertion */ );
- }
- test_suite* init_unit_test_suite( int argc, char* argv[] )
- {
- int params[] = { 1, 2, 3, 4, 5 };
- framework::master_test_suite().
- add( BOOST_PARAM_TEST_CASE( &free_test_function, params, params+5 ) );
- return 0;
- }
示例代码将会分别以参数1、2、3、4、5运行测试free_test_function五次。手动注册需要定义测试函数,此处为free_test_function,然后定义init_unit_test_suite函数,并在该函数中将测试用例加入到主测试套件中。BOOST_PARAM_TEST_CASE有三个参数:第一个参数为测试函数指针,第二个与第三个为输入参数迭代器。
4.夹具(Fixture)
如果在多个测试用例中需要使用数据库连接,这时候要用到夹具来自动执行安装、清理过程。Boost Test采用RAII技术来实现夹具:
1 struct <fixture-name> {2
3 <fixture-name>();// 安装
4
5 ~<fixture-name>();// 拆卸
6
7};
- #define BOOST_TEST_MODULE example
- 2
- 3#include <boost/test/included/unit_test.hpp>
- 4
- 5
- 6
- 7struct F
- 8
- 9{
- 10
- 11 F() : i(0)
- 12
- 13 {
- 14
- 15 //std::cout << ("创建夹具") << std::endl;
- 16
- 17 }
- 18
- 19 ~F()
- 20
- 21 {
- 22
- 23 //std::cout << "销毁夹具" << std::endl;
- 24
- 25 }
- 26
- 27
- 28
- 29 int i;
- 30
- 31};
- 32
- 33
- 34
- 35BOOST_FIXTURE_TEST_SUITE(const_string_test, F)
- 36
- 37
- 38
- 39BOOST_FIXTURE_TEST_CASE( test_case1, F )
- 40
- 41{
- 42
- 43 BOOST_CHECK( i == 1 );
- 44
- 45 ++i;
- 46
- 47}
- 48
- 49
- 50
- 51BOOST_FIXTURE_TEST_CASE( test_case2 )
- 52
- 53{
- 54
- 55 BOOST_CHECK_EQUAL( i, 1 );
- 56
- 57}
- 58
- 59
- 60
- 61BOOST_AUTO_TEST_CASE( test_case3 )
- 62
- 63{
- 64
- 65 BOOST_CHECK( true );
- 66
- 67}
- 68
- 69
- 70
- 71BOOST_AUTO_TEST_SUITE_END()
- 72
- 73
使用宏BOOST_FIXTURE_TEST_SUITE在第二个参数中指定夹具来代替宏BOOST_AUTO_TEST_SUITE以建立测试套件,夹具将在该测试套件的所有测试用例中可用。使用宏BOOST_FIXTURE_TEST_CASE代替宏BOOST_AUTO_TEST_CASE建立测试用例,可以在该测试用例中使用夹具。需要注意的是在每一个测试用例中都将会执行夹具的安装和卸载过程。
5.测试输出
Boost Test中包括了十个级别的日志信息:
- 成功信息
- 测试树往返移动通知
- 通用信息
- 警告信息
- 非致命错误信息
- 未捕获C++异常
- 致命系统错误
- 所有信息
- 无信息
以上级别以由低到高的级别排列,可以在运行时参数log_level中设置,还可以使用参数log_format来制定输出格式。
可以向测试程序传递参数来定制测试结果,如“test.exe –build_info=yes –log_level=all”下面是常用的测试参数:
-
build_infoa :设置为yes 将在开始运行测试前打印当前的操作系统版本、编译器版本等信息;
-
log_level :对应于十个日志级别,包括all 、success 、test_suite 、message 、warning 、error 、cpp_exception 、system_error 、fatal_error 、nothing 。
-
output_format :定义日志的输出格式。目前支持两种格式,HRF (可读格式)和XML 格式;
-
run_test :指定要运行的测试单元,包括测试套件与测试用例,可以使用通配符“* ”来制定运行符合特定条件的测试单元,如“test.exe –run_test=*/test1 ”将运行所有名为test1 并位于主套件直接子套件的测试单元;
-
show_progress :设置为yes 将在运行测试时显示当前的进度。
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈