[英]gtest assertions in non-test code
I'm working on a C++ library, and I'm using gtest for unit testing. 我正在使用C ++库,并且正在使用gtest进行单元测试。 I want to add ASSERT_* statements to the library code itself, not just the unit test code.
我想将ASSERT_ *语句添加到库代码本身,而不仅仅是单元测试代码。 I want these ASSERTions to cause a unit test to fail if the code is run under a unit test, or turn into regular asserts if the code is not running under a unite test.
我希望这些断言在代码在单元测试下运行时导致单元测试失败,或者在代码不在单元测试下运行时变为常规断言。
Something like: 就像是:
if(gtest::is_running)
ASSERT_TRUE(...);
else
assert(...);
How can I achieve that? 我该如何实现?
Even if this were technically possible (I don't think it is), I really don't believe that making your production code depend on the test framework is a good idea. 即使这在技术上是可行的(我认为不是),但我真的不相信使生产代码依赖于测试框架是一个好主意。
The main reasons are robustness, separation of concerns, and decoupling: introducing test-specific conditionals in production code makes the code unnecessarily harder to understand, and may reduce the trustworthiness of your test suite (after all, your tests won't stress the exact same paths your production code will go through). 主要原因是健壮性,关注点分离和去耦:在生产代码中引入特定于测试的条件会使代码变得不必要地难以理解,并可能降低测试套件的可信度(毕竟,您的测试不会强调精确性)生产代码将使用的相同路径)。
Also, one day you may want to change something in the testing environment (eg the version of the unit test framework, or the unit test framework itself), and this dependency might force you to modify the production code accordingly, at the risk of introducing new bugs. 同样,有一天您可能想在测试环境中进行某些更改(例如,单元测试框架的版本或单元测试框架本身),并且这种依赖性可能会迫使您相应地修改生产代码,但有引入风险。新的错误。
If what you want to verify is that your assertions actually fire when the client violates the function's preconditions (ie if you want to test that preconditions are correctly validated by your assertions), then this proposal may be relevant to you as well as the library which inspired it, Bloomberg's BDE . 如果您要验证的是断言在客户端违反函数的前提条件时实际上触发了(例如,如果您要测试前提条件已正确验证了前提条件),那么此建议可能与您以及与之相关的库有关。启发了它, 彭博的BDE 。
If this is not a viable technology for your project, perhaps you may consider adopting a strategy based on Dependency Inversion. 如果这不是您项目的可行技术,则也许您可以考虑采用基于依赖倒置的策略。 The simplest possible approach is to:
最简单的方法是:
Verifier
with an abstract member function verify()
taking a bool
; Verifier
与一个抽象的成员函数verify()
取bool
; AssertingVerifier
class from it (to be used in production code) that overrides verify()
and forwards its argument to assert()
. AssertingVerifier
类(将在生产代码中使用),该类重写verify()
并将其参数转发给assert()
。 Both Verifier
and AssertVerifier
would live in your production code; Verifier
和AssertVerifier
都将存在于您的生产代码中。 GracefulTestVerifier
, that overrides verify()
and forwards its argument to ASSERT_TRUE()
- or by doing whatever you think is most appropriate; GracefulTestVerifier
,该类将覆盖verify()
并将其参数转发给ASSERT_TRUE()
-或通过执行您认为最合适的操作; Verifier
into your production code - several possibilities exist, but telling which one fits best requires detailed knowledge of your design. Verifier
注入生产代码的最佳方法-存在几种可能性,但要确定哪种方法最合适则需要您对设计有详细的了解。 You would then inject an AssertVerifier
in a regular execution environment, and a GracefulTestVerifier
in a testing environment. AssertVerifier
,并在测试环境中注入GracefulTestVerifier
。 This way, execution may flow from the production code to the test framework without your production code being physically dependent on the test framework itself. 这样,执行可能会从生产代码流向测试框架,而您的生产代码实际上并不依赖于测试框架本身。
What about approaching this from the alternate direction? 从替代方向解决这个问题呢? Instead of changing your gtest behavior, change your assert's behavior.
无需更改gtest行为,而是更改断言的行为。
Boost.Assert , for example, provides a BOOST_ASSERT
macro that, by default, behaves identically to assert
. 例如, Boost.Assert提供了
BOOST_ASSERT
宏,默认情况下其行为与assert
相同。 However, if BOOST_ENABLE_ASSERT_HANDLER
is defined, then it instead looks for a ::boost::assertion_failed
function, which you must provide. 但是,如果定义了
BOOST_ENABLE_ASSERT_HANDLER
,则它将查找必须提供的::boost::assertion_failed
函数。 You could design your library code to build with standard assertion behavior outside of the test suite and with a ::boost::assertion_failed
that calls gtest's FAIL()
inside of a test suite. 您可以将您的库代码设计为使用测试套件外部的标准断言行为以及在测试套件内部调用gtest的
FAIL()
的::boost::assertion_failed
构建。
If you don't want to use Boost, it would be trivial to implement something similar yourself. 如果您不想使用Boost,那么自己实现类似的东西将是微不足道的。
This would require building your library twice (once for the test suite, once for regular use), which may not fit well with your overall goals. 这将需要两次构建您的库(一次用于测试套件,一次用于常规使用),这可能与您的总体目标不太吻合。
You could use preprocessor directives. 您可以使用预处理程序指令。
When compiling with gtest, tell your compiler to define something like "GTEST_ON", then in your code: 使用gtest进行编译时,请告诉您的编译器定义类似“ GTEST_ON”的内容,然后在您的代码中进行定义:
#ifdef GTEST_ON
ASSERT_TRUE(...);
#else
assert(...);
#endif
Here is what I ended up doing, following @Josh Kelley's advice: 这是我根据@Josh Kelley的建议最终做的事情:
I've switched from assert
to BOOST_ASSERT
. 我已经从
assert
切换到BOOST_ASSERT
。 Instead of including boost/assert.hpp
I've added my own assert.hpp
file that includes the Boost file, defines BOOST_ENABLE_ASSERT_HANDLER
and a BOOST_ASSERT_HANDLER
function pointer (to the exact same type as the Boost assert handler). 我没有添加
boost/assert.hpp
而是添加了自己的包含Boost文件的assert.hpp
文件,它定义了BOOST_ENABLE_ASSERT_HANDLER
和BOOST_ASSERT_HANDLER
函数指针(与Boost断言处理程序的类型完全相同)。
I've also included my own Boost assert handler ( ::boost::assertion_failed
) that outputs the assertion information to std::cerr
and calls the function pointed to by BOOST_ASSERT_HANDLER
if one exists. 我还包括了我自己的Boost断言处理程序(
::boost::assertion_failed
),该处理程序将断言信息输出到std::cerr
并调用BOOST_ASSERT_HANDLER
指向的函数(如果存在)。 If there isn't one, it just assert(false)
s. 如果不存在,
assert(false)
。
In my test main, I point BOOST_ASSERT_HANDLER to a function that simply calls EXPECT_FALSE(true)
. 在我的测试主体中,我将BOOST_ASSERT_HANDLER指向一个简单调用
EXPECT_FALSE(true)
的函数。
And that's it. 就是这样。 Now I can have ordinary asserts when not running under gtest, and gtest-integrated asserts when running under gtest.
现在,当我不在gtest下运行时,我可以拥有普通的断言,而在gtest下运行时,可以拥有gtest集成的断言。
I've basically use these set of sources. 我基本上已经使用了这些资源。 Their are pretty standalone.
他们是非常独立的。
To make it works you have to do these steps: 要使其正常工作,您必须执行以下步骤:
cmake
). cmake
这样做很容易)。 UNIT_TESTS
definition has been defined for unit tests and has not for benchmark tests. UNIT_TESTS
定义的独立项目中是针对单元测试而不是针对基准测试定义的。 utility/assert.hpp
somethere in a main header of both unit tests and main project BEFORE the gtest/gtest.hpp
header inclusion. gtest/gtest.hpp
标头包含之前,将utility/assert.hpp
/assert.hpp包含在单元测试和主项目的主标头中。 ASSERT_TRUE
/ ASSERT_EQ
/etc instead of assert
macro ASSERT_TRUE
/ ASSERT_EQ
/ etc代替assert
宏 NOTE : In case of benchmark tests you should not define the UNIT_TESTS
definition, otherwise the assert definitions does slow down the execution. 注意 :如果进行基准测试,则不应定义
UNIT_TESTS
定义,否则assert定义的确会减慢执行速度。
utility/assert.hpp 实用/ assert.hpp
UPD1 UPD1
' “
#pragma once
#include "debug.hpp"
#ifdef UNIT_TESTS
#include <gtest/gtest.h>
#endif
#include <cassert>
#define ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(exp, precondition) \
if (!(precondition)); else if(!!(exp)); else ::utility::debug_break()
#ifdef GTEST_FAIL
#ifdef _MSC_VER
#if _MSC_VER < 1600 // < MSVC++ 10 (Visual Studio 2010)
#error lambda is not supported
#endif
#else
#if __cplusplus < 201103L
#error lambda is not supported
#endif
#endif
// TIPS:
// * all lambdas captured by reference because of the error in the MSVC 2015:
// `error C3493 : '...' cannot be implicitly captured because no default capture mode has been specified`
// * if debugger is attached but `::testing::GTEST_FLAG(break_on_failure)` has not been setted,
// then an assertion does a post break.
// gtest asserts rebind with the `void` error workaround (C++11 and higher is required)
#undef ASSERT_TRUE
#define ASSERT_TRUE(condition) [&]() -> void { \
const bool is_success = ::utility::is_true(condition); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_TEST_BOOLEAN_(is_success, #condition, false, true, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_TEST_BOOLEAN_(is_success, #condition, false, true, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(is_success, !break_on_failure); \
}()
#undef ASSERT_FALSE
#define ASSERT_FALSE(condition) [&]() -> void { \
const bool is_success = ::utility::is_false(condition); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_TEST_BOOLEAN_(is_success, #condition, true, false, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_TEST_BOOLEAN_(is_success, #condition, true, false, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(is_success, !break_on_failure); \
}()
#if !GTEST_DONT_DEFINE_ASSERT_EQ
#undef ASSERT_EQ
#define ASSERT_EQ(val1, val2) [&]() -> void { \
const ::testing::AssertionResult exp_value = ::testing::internal::EqHelper<GTEST_IS_NULL_LITERAL_(val1)>::Compare(#val1, #val2, val1, val2); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_ASSERT_(exp_value, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_ASSERT_(exp_value, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(exp_value, !break_on_failure); \
}()
#endif
#if !GTEST_DONT_DEFINE_ASSERT_NE
#undef ASSERT_NE
#define ASSERT_NE(val1, val2) [&]() -> void { \
const ::testing::AssertionResult exp_value = ::testing::internal::CmpHelperNE(#val1, #val2, val1, val2); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_ASSERT_(exp_value, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_ASSERT_(exp_value, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(exp_value, !break_on_failure); \
}()
#endif
#if !GTEST_DONT_DEFINE_ASSERT_LE
#undef ASSERT_LE
#define ASSERT_LE(val1, val2) [&]() -> void { \
const ::testing::AssertionResult exp_value = ::testing::internal::CmpHelperLE(#val1, #val2, val1, val2); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_ASSERT_(exp_value, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_ASSERT_(exp_value, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(exp_value, !break_on_failure); \
}()
#endif
#if !GTEST_DONT_DEFINE_ASSERT_LT
#undef ASSERT_LT
#define ASSERT_LT(val1, val2) [&]() -> void { \
const ::testing::AssertionResult exp_value = ::testing::internal::CmpHelperLT(#val1, #val2, val1, val2); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_ASSERT_(exp_value, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_ASSERT_(exp_value, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(exp_value, !break_on_failure); \
}()
#endif
#if !GTEST_DONT_DEFINE_ASSERT_GE
#undef ASSERT_GE
#define ASSERT_GE(val1, val2) [&]() -> void { \
const ::testing::AssertionResult exp_value = ::testing::internal::CmpHelperGE(#val1, #val2, val1, val2); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_ASSERT_(exp_value, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_ASSERT_(exp_value, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(exp_value, !break_on_failure); \
}()
#endif
#if !GTEST_DONT_DEFINE_ASSERT_GT
#undef ASSERT_GT
#define ASSERT_GT(val1, val2) [&]() -> void { \
const ::testing::AssertionResult exp_value = ::testing::internal::CmpHelperGT(#val1, #val2, val1, val2); \
const bool break_on_failure = ::testing::GTEST_FLAG(break_on_failure); \
if (break_on_failure) { \
GTEST_ASSERT_(exp_value, GTEST_FATAL_FAILURE_); \
} else { \
GTEST_ASSERT_(exp_value, GTEST_NONFATAL_FAILURE_); \
} \
ASSERT_FAIL_BREAK_ON_ATTACHED_DEBUGGER(exp_value, !break_on_failure); \
}()
#endif
#define ASSERT(x) ASSERT_TRUE(x)
#else
#ifndef ASSERT_IMPL
#define ASSERT_IMPL(exp) assert(exp)
#endif
#ifdef _DEBUG
#define ASSERT_TRUE(exp) ASSERT_IMPL(exp)
#define ASSERT_FALSE(exp) ASSERT_IMPL(!(exp))
#define ASSERT_EQ(v1, v2) ASSERT_IMPL((v1) == (v2))
#define ASSERT_NE(v1, v2) ASSERT_IMPL((v1) != (v2)))
#define ASSERT_LE(v1, v2) ASSERT_IMPL((v1) <= (v2))
#define ASSERT_LT(v1, v2) ASSERT_IMPL((v1) < (v2))
#define ASSERT_GE(v1, v2) ASSERT_IMPL((v1) >= (v2))
#define ASSERT_GT(v1, v2) ASSERT_IMPL((v1) > (v2))
#define ASSERT(exp) ASSERT_IMPL(exp)
#else
#define ASSERT_TRUE(exp) (::utility::is_true(exp), (void)0)
#define ASSERT_FALSE(exp) (::utility::is_false(exp), (void)0))
#define ASSERT_EQ(v1, v2) (::utility::is_equal(v1, v2), (void)0)
#define ASSERT_NE(v1, v2) (::utility::is_not_equal(v1, v2), (void)0)
#define ASSERT_LE(v1, v2) (::utility::is_less_or_equal(v1, v2), (void)0)
#define ASSERT_LT(v1, v2) (::utility::is_less(v1, v2), (void)0)
#define ASSERT_GE(v1, v2) (::utility::is_greater_or_equal(v1, v2), (void)0)
#define ASSERT_GT(v1, v2) (::utility::is_greater(v1, v2), (void)0)
#define ASSERT(exp) ::utility::is_true(exp)
#endif
#endif
namespace utility
{
// TIPS:
// * to capture parameters by reference in macro definitions for single evaluation
// * to suppress `unused variable` warnings like: `warning C4101: '...': unreferenced local variable`
template<typename T>
inline bool is_true(const T & v)
{
return !!v; // to avoid warnings of truncation to bool
}
template<typename T>
inline bool is_false(const T & v)
{
return !v; // to avoid warnings of truncation to bool
}
template<typename T1, typename T2>
inline bool is_equal(const T1 & v1, const T2 & v2)
{
return v1 == v2;
}
template<typename T1, typename T2>
inline bool is_not_equal(const T1 & v1, const T2 & v2)
{
return v1 != v2;
}
template<typename T1, typename T2>
inline bool is_less_or_equal(const T1 & v1, const T2 & v2)
{
return v1 <= v2;
}
template<typename T1, typename T2>
inline bool is_less(const T1 & v1, const T2 & v2)
{
return v1 < v2;
}
template<typename T1, typename T2>
inline bool is_greater_or_equal(const T1 & v1, const T2 & v2)
{
return v1 >= v2;
}
template<typename T1, typename T2>
inline bool is_greater(const T1 & v1, const T2 & v2)
{
return v1 > v2;
}
}
utility/debug.hpp 实用/ debug.hpp
#pragma once
namespace utility
{
void debug_break(bool breakCondition = true);
bool is_under_debugger();
}
utility/debug.cpp 实用/ debug.cpp
#include "debug.hpp"
#include "platform.hpp"
#if defined(UTILITY_PLATFORM_WINDOWS)
#include <windows.h>
#include <intrin.h>
#elif defined(UTILITY_PLATFORM_POSIX)
#include <sys/ptrace.h>
#include <signal.h>
static void signal_handler(int) { }
#else
#error is_under_debugger is not supported for this platform
#endif
namespace utility {
void debug_break(bool breakCondition)
{
// avoid signal if not under debugger
if (breakCondition && is_under_debugger()) {
#if defined(UTILITY_COMPILER_CXX_MSC)
__debugbreak(); // won't require debug symbols to show the call stack, when the DebugBreak() will require system debug symbols to show the call stack correctly
#elif defined(UTILITY_PLATFORM_POSIX)
signal(SIGTRAP, signal_handler);
#else
#error debug_break is not supported for this platform
#endif
}
}
bool is_under_debugger()
{
#if defined(UTILITY_PLATFORM_WINDOWS)
return !!::IsDebuggerPresent();
#elif defined(UTILITY_PLATFORM_POSIX)
return ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1;
#endif
}
}
utility/platform.hpp 实用/ platform.hpp
#pragma once
// linux, also other platforms (Hurd etc) that use GLIBC, should these really have their own config headers though?
#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__)
# define UTILITY_PLATFORM_LINUX
# define UTILITY_PLATFORM_POSIX
# if defined(__mcbc__)
# define UTILITY_PLATFORM_MCBC
# define UTILITY_PLATFORM_SHORT_NAME "MCBC"
# elif defined( __astra_linux__ )
# define UTILITY_PLATFORM_ASTRA_LINUX
# define UTILITY_PLATFORM_SHORT_NAME "Astra Linux"
# else
# define UTILITY_PLATFORM_SHORT_NAME "Linux"
# endif
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) // BSD:
# define UTILITY_PLATFORM_BSD
# define UTILITY_PLATFORM_POSIX
# define UTILITY_PLATFORM_SHORT_NAME "BSD"
#elif defined(sun) || defined(__sun) // solaris:
# define UTILITY_PLATFORM_SOLARIS
# define UTILITY_PLATFORM_POSIX
# define UTILITY_PLATFORM_SHORT_NAME "Solaris"
#elif defined(__CYGWIN__) // cygwin is not win32:
# define UTILITY_PLATFORM_CYGWIN
# define UTILITY_PLATFORM_POSIX
# define UTILITY_PLATFORM_SHORT_NAME "Cygwin"
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) // win32:
# define UTILITY_PLATFORM_WINDOWS
# define UTILITY_PLATFORM_SHORT_NAME "Windows"
# if defined(__MINGW32__) // Get the information about the MinGW runtime, i.e. __MINGW32_*VERSION.
# include <_mingw.h>
# endif
#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) // MacOS
# define UTILITY_PLATFORM_APPLE
# define UTILITY_PLATFORM_POSIX
# define UTILITY_PLATFORM_SHORT_NAME "MacOS"
#elif defined(__QNXNTO__) // QNX:
# define UTILITY_PLATFORM_QNIX
# define UTILITY_PLATFORM_POSIX
# define UTILITY_PLATFORM_SHORT_NAME "QNX"
#elif defined(unix) || defined(__unix) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)
# define UTILITY_PLATFORM_UNIX
# define UTILITY_PLATFORM_POSIX
# define UTILITY_PLATFORM_SHORT_NAME "Unix"
#else
# error Unknown platform
#endif
#if defined(__GNUC__)
# define UTILITY_COMPILER_CXX_GCC
# define UTILITY_COMPILER_CXX "gcc"
# define UTILITY_COMPILER_CXX_VERSION __GNUC__
# if __GNUC__ < 4
# error "Unsuported gcc version"
# endif
#elif defined(_MSC_VER)
# define UTILITY_COMPILER_CXX_MSC
# define UTILITY_COMPILER_CXX "MS VisualC"
# define UTILITY_COMPILER_CXX_VERSION _MSC_VER
#else
# error "Unknown compiler"
#endif
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.