使用C++ catch框架验证assert语句
Use C++ catch framework to verify assert statement
是否可以使用 C++ CATCH
框架来验证 assert
语句是否正确识别无效前提条件?
// Source code
void loadDataFile(FILE* input) {
assert(input != NULL);
...
}
// Test code
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
loadDataFile(NULL)
// Now what do I look for?
}
您可能对 google 测试框架感兴趣。它有能力捕捉异常程序终止:
ASSERT_DEATH(statement, regex);
ASSERT_DEATH_IF_SUPPORTED(statement, regex);
ASSERT_EXIT(statement, predicate, regex);
EXPECT_DEATH(statement, regex);
EXPECT_DEATH_IF_SUPPORTED(statement, regex);
EXPECT_EXIT(statement, predicate, regex);
regex
匹配 stderr
上的文本
predicate
匹配程序的退出代码。
我怀疑这可以通过在断言之前分叉测试程序来实现。
文档在这里:
https://github.com/google/googletest/blob/master/googletest/docs/advanced.md
假设您的示例的第一部分是被测源代码,第二部分是单元测试,那么您需要选择如何处理:
一些开源框架,例如 BDE and Boost have their own ASSERT macro which can be configured at application startup to behave different than C assert. For example, you can specify that a failed ASSERT throws an exception - and then you could use Catch's REQUIRE_THROWS() 断言,用于验证您的代码是否强制执行非 NULL FILE 描述符契约。
BDE 示例
#include <bsls_assert.h>
void loadDataFile(FILE* input) {
BSLS_ASSERT_OPT(input != NULL);
...
}
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
// Opt-in to the 'throw exception on assert failure' handler
// just for this test case.
bsls::AssertFailureHandlerGuard guard(&bsls::Assert::failThrow);
REQUIRE_THROWS_AS(loadDataFile(NULL), bsls::AssertFailedException);
}
提升示例
#include <boost/assert.hpp>
void loadDataFile(FILE* input) {
BOOST_ASSERT(input != NULL);
...
}
namespace boost {
void assertion_failed(char const * expr, char const * function, char const * file, long line) {
throw std::runtime_error("Assertion Failed"); // TODO: use expr, function, file, line
}
}
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
REQUIRE_THROWS(loadDataFile(NULL));
// Now what do I look for?
}
您可以推出自己的 assert() 宏。这是重新发明轮子 - 请参阅上面的示例。
您可以更改代码以抛出 std::invalid_argument() 异常:
void loadDataFile(FILE* input) {
if (input == NULL) {
throw std::invalid_argument("input file descriptor cannot be NULL");
}
...
}
您可以测试您的代码是否强制执行与以下合同的合同:
REQUIRE_THROWS_AS(loadDataFile(NULL), std::invalid_argument);
这是在您的代码中引入了异常(以及处理它们的需要),这是一个比您的客户可能满意的更大的变化 - 一些公司有无例外规则,一些平台(例如嵌入式)有不支持例外。
最后,如果您真的想要,您可以可以改变您的代码接口以暴露合约失败:
enum LoadDataFile_Result {
LDF_Success,
LDF_InputIsNull,
...
};
LoadDataFile_Result loadDataFile(FILE* input) {
if (input == NULL) {
// bail out early for contract failure
return LDF_InputIsNull;
}
// input is non-NULL
...
return LDF_Success;
}
...但这具有固有的风险,即客户不检查 return 值,这是许多错误的原因,感觉又像 C。
如果可以模拟 C 函数,则可以暂时将断言失败更改为异常。
例如,使用 Hippomocks,您的测试用例将类似于:
#ifndef NDEBUG
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
MockRepository mocks;
mocks.OnCallFunc(__assert_fail).Throw(nullptr);
CHECK_THROWS_AS(loadDataFile(NULL), std::nullptr_t);
}
#endif
可移植性可能是个问题,我只在 Linux 上用 glibc 和 musl 测试过这个。
是否可以使用 C++ CATCH
框架来验证 assert
语句是否正确识别无效前提条件?
// Source code
void loadDataFile(FILE* input) {
assert(input != NULL);
...
}
// Test code
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
loadDataFile(NULL)
// Now what do I look for?
}
您可能对 google 测试框架感兴趣。它有能力捕捉异常程序终止:
ASSERT_DEATH(statement, regex);
ASSERT_DEATH_IF_SUPPORTED(statement, regex);
ASSERT_EXIT(statement, predicate, regex);
EXPECT_DEATH(statement, regex);
EXPECT_DEATH_IF_SUPPORTED(statement, regex);
EXPECT_EXIT(statement, predicate, regex);
regex
匹配 stderr
上的文本
predicate
匹配程序的退出代码。
我怀疑这可以通过在断言之前分叉测试程序来实现。
文档在这里:
https://github.com/google/googletest/blob/master/googletest/docs/advanced.md
假设您的示例的第一部分是被测源代码,第二部分是单元测试,那么您需要选择如何处理:
一些开源框架,例如 BDE and Boost have their own ASSERT macro which can be configured at application startup to behave different than C assert. For example, you can specify that a failed ASSERT throws an exception - and then you could use Catch's REQUIRE_THROWS() 断言,用于验证您的代码是否强制执行非 NULL FILE 描述符契约。
BDE 示例
#include <bsls_assert.h>
void loadDataFile(FILE* input) {
BSLS_ASSERT_OPT(input != NULL);
...
}
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
// Opt-in to the 'throw exception on assert failure' handler
// just for this test case.
bsls::AssertFailureHandlerGuard guard(&bsls::Assert::failThrow);
REQUIRE_THROWS_AS(loadDataFile(NULL), bsls::AssertFailedException);
}
提升示例
#include <boost/assert.hpp>
void loadDataFile(FILE* input) {
BOOST_ASSERT(input != NULL);
...
}
namespace boost {
void assertion_failed(char const * expr, char const * function, char const * file, long line) {
throw std::runtime_error("Assertion Failed"); // TODO: use expr, function, file, line
}
}
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
REQUIRE_THROWS(loadDataFile(NULL));
// Now what do I look for?
}
您可以推出自己的 assert() 宏。这是重新发明轮子 - 请参阅上面的示例。
您可以更改代码以抛出 std::invalid_argument() 异常:
void loadDataFile(FILE* input) {
if (input == NULL) {
throw std::invalid_argument("input file descriptor cannot be NULL");
}
...
}
您可以测试您的代码是否强制执行与以下合同的合同:
REQUIRE_THROWS_AS(loadDataFile(NULL), std::invalid_argument);
这是在您的代码中引入了异常(以及处理它们的需要),这是一个比您的客户可能满意的更大的变化 - 一些公司有无例外规则,一些平台(例如嵌入式)有不支持例外。
最后,如果您真的想要,您可以可以改变您的代码接口以暴露合约失败:
enum LoadDataFile_Result {
LDF_Success,
LDF_InputIsNull,
...
};
LoadDataFile_Result loadDataFile(FILE* input) {
if (input == NULL) {
// bail out early for contract failure
return LDF_InputIsNull;
}
// input is non-NULL
...
return LDF_Success;
}
...但这具有固有的风险,即客户不检查 return 值,这是许多错误的原因,感觉又像 C。
如果可以模拟 C 函数,则可以暂时将断言失败更改为异常。
例如,使用 Hippomocks,您的测试用例将类似于:
#ifndef NDEBUG
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
MockRepository mocks;
mocks.OnCallFunc(__assert_fail).Throw(nullptr);
CHECK_THROWS_AS(loadDataFile(NULL), std::nullptr_t);
}
#endif
可移植性可能是个问题,我只在 Linux 上用 glibc 和 musl 测试过这个。