gpt4 book ai didi

c++ - 如何强烈分解代码单元测试一堆函数的所有参数

转载 作者:行者123 更新时间:2023-11-30 05:20:21 25 4
gpt4 key购买 nike

我有一堆函数,用来自定义一个算法参数。函数获得不同类型参数的不同计数(一些标准:doubleint,其他自定义类允许访问 doubleint 值在某些时候通过 getters)。

所有算法参数都必须在有效范围内 ([min,max])。我需要编写一个单元测试以确保每个函数的每个参数的绑定(bind)检查都正确编码(达到 100% 代码覆盖率)。

这是我的 MCVE:

待测代码:

class Object
{
public:
Object( double value ) : value( value ) {}
inline const double& getValue() const { return value; }
private:
double value;
};

static const double minA = 0;
static const double maxA = 100;
static const int minB = 10;
static const int maxB = 20;
static const Object minC = Object( 23.0 );
static const Object maxC = Object( 29.0 );

bool func1( double a )
{
if ( a < minA )
return false;
else if ( a > maxA )
return false;

// do something
return true;
}

bool func2( int b, const Object& c )
{
if ( b < minB )
return false;
else if ( b > maxB )
return false;
else if ( c.getValue() < minC.getValue() )
return false;
else if ( c.getValue() > maxC.getValue() )
return false;

// do something
return true;
}

必须分解的测试示例:

double getValidValue( const std::pair<double,double>& minmax ) { return minmax.first + (minmax.second-minmax.first)/2; }
int getValidValue( const std::pair<int,int>& minmax ) { return minmax.first + (minmax.second-minmax.first)/2; }
Object getValidValue( const std::pair<Object,Object>& minmax ) { return Object( minmax.first.getValue() + (minmax.second.getValue()-minmax.first.getValue())/2); }

double getInvalidLowerValue( const std::pair<double,double>& minmax ) { return minmax.first - 1; }
int getInvalidLowerValue( const std::pair<int,int>& minmax ) { return minmax.first - 1; }
Object getInvalidLowerValue( const std::pair<Object,Object>& minmax ) { return Object( minmax.first.getValue() - 1); }

double getInvalidUpperValue( const std::pair<double,double>& minmax ) { return minmax.second + 1; }
int getInvalidUpperValue( const std::pair<int,int>& minmax ) { return minmax.second + 1; }
Object getInvalidUpperValue( const std::pair<Object,Object>& minmax ) { return Object( minmax.second.getValue() + 1); }

int main ()
{
// valid cases:
assert( func1( getValidValue( std::make_pair(minA,maxA) ) ) );
assert( func2( getValidValue( std::make_pair(minB,maxB) ), getValidValue( std::make_pair(minC,maxC) ) ) );

// func1 out of bound cases:
assert( !func1( getInvalidLowerValue( std::make_pair(minA,maxA) ) ) );
assert( !func1( getInvalidUpperValue( std::make_pair(minA,maxA) ) ) );

// func2 out of bound cases:
// two tests won't offer a 100% code coverage!
//assert( !func2( getInvalidLowerValue( std::make_pair(minB,maxB) ), getInvalidLowerValue( std::make_pair(minC,maxC) ) ) );
//assert( !func2( getInvalidUpperValue( std::make_pair(minB,maxB) ), getInvalidUpperValue( std::make_pair(minC,maxC) ) ) );

// func2, first param out of bound cases
assert( !func2( getInvalidLowerValue( std::make_pair(minB,maxB) ), getValidValue( std::make_pair(minC,maxC) ) ) );
assert( !func2( getInvalidUpperValue( std::make_pair(minB,maxB) ), getValidValue( std::make_pair(minC,maxC) ) ) );
// func2, second param out of bound cases
assert( !func2( getValidValue( std::make_pair(minB,maxB) ), getInvalidLowerValue( std::make_pair(minC,maxC) ) ) );
assert( !func2( getValidValue( std::make_pair(minB,maxB) ), getInvalidUpperValue( std::make_pair(minC,maxC) ) ) );

return (0);
}

注意:

  • 我使用 assert 来简化 MCVE(我实际上使用 CPPUnit 库和 CPPUNIT_ASSERT 宏)。
  • 调用一个所有参数都无效的函数不会达到 100% 的代码覆盖率:由于 func2 在不同的 bool 值评估中检查所有参数,因此两个参数都无效将不会达到代码检查第二个参数。具有 n 个参数的函数需要 1+2*n 次调用才能得到全面测试。
  • 修改函数检查输入参数的方式(通过一个且只有一个 if 语句)以使其更容易达到 100% 的代码覆盖率是 Not Acceptable (该算法用于医疗设备,我们的目标是成为用户无论代码如何编写,每个参数的每个界限都会被测试。

由于我们有很多函数(约 20 个),所有函数都有很多参数(从 1 到 5),我希望最终得到一个完全分解的解决方案,其中核心测试代码为:

int main()
{
testFunc( &func1, /* give parameter bounds min/max for every parameter of func1 */ );
testFunc( &func2, /* give parameter bounds min/max for every parameter of func2 */ );
}

我尝试在这里使用可变参数模板(认为它会有所帮助),但我不确定这是否有效并且无法找到如何编写 testFunc 函数(特别是如何迭代参数以及如何使用变量 1+2*n 函数调用语句,n 是参数的数量...)。

这是我目前所拥有的(不多......而且它不编译),如果有人想以此作为开始的话。但是用完全不同的方法回答是完全可以接受的。

template <typename ...Args> void testFunc( bool (*func)( Args ... ), const std::pair<Args...,Args...>& args )
{
assert( (*func)( /* all getValidValue( args ) ... */ );
for ( arg : args )
{
assert( !(*func)( /* all getValidValue but one getInvalidLowerValue */ );
assert( !(*func)( /* all getValidValue but one getInvalidUpperValue */ );
}
}

int main()
{
testFunc( &func1, std::make_pair( minA, maxA ) );
testFunc( &func2, std::make_pair( minB, maxB ), std::make_pair( minC, maxC ) );
}

注意:使用boost 的解决方案是可以接受的

最佳答案

C++11 实现如下所示:

#include <tuple>
#include <utility>
#include <type_traits>
#include <cassert>
#include <iostream>

// CODE TO BE TESTED:

class Object
{
public:
Object( double value ) : value( value ) {}
inline const double& getValue() const { return value; }
private:
double value;
};

static const double minA = 0;
static const double maxA = 100;
static const int minB = 10;
static const int maxB = 20;
static const Object minC = Object( 23.0 );
static const Object maxC = Object( 29.0 );

bool func1( double a )
{
std::cout << "Calling func1(" << a << ")" << std::endl;
if ( a < minA )
return false;
else if ( a > maxA )
return false;

// do something
return true;
}

bool func2( int b, const Object& c )
{
std::cout << "Calling func2(" << b << "," << c.getValue() << ")" << std::endl;
if ( b < minB )
return false;
else if ( b > maxB )
return false;
else if ( c.getValue() < minC.getValue() )
return false;
else if ( c.getValue() > maxC.getValue() )
return false;

// do something
return true;
}

// TESTING CODE:

// integer_sequence implementation

template <class T, T... Vs>
struct integer_sequence { };

template <class T, class, class, class = integer_sequence<T>, class = integer_sequence<T, 0>, class = void>
struct make_integer_sequence_impl;

template <class T, T ICV1, T... Res, T... Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, ICV1>, std::integral_constant<T, 0>, integer_sequence<T, Res...>, integer_sequence<T, Pow...>, typename std::enable_if<(ICV1 > 0)>::type>: make_integer_sequence_impl<T, std::integral_constant<T, ICV1/2>, std::integral_constant<T, ICV1%2>, integer_sequence<T, Res...>, integer_sequence<T, Pow..., (Pow + sizeof...(Pow))...>> { };

template <class T, T ICV1, T... Res, T... Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, ICV1>, std::integral_constant<T, 1>, integer_sequence<T, Res...>, integer_sequence<T, Pow...>, void>: make_integer_sequence_impl<T, std::integral_constant<T, ICV1/2>, std::integral_constant<T, ICV1%2>, integer_sequence<T, Pow..., (Res + sizeof...(Pow))...>, integer_sequence<T, Pow..., (Pow + sizeof...(Pow))...>> { };

template <class T, class Res, class Pow>
struct make_integer_sequence_impl<T, std::integral_constant<T, 0>, std::integral_constant<T, 0>, Res, Pow, void> {
using type = Res;
};

template <class T, T V>
using make_integer_sequence = typename make_integer_sequence_impl<T, std::integral_constant<T, V/2>, std::integral_constant<T, V%2>>::type;

template <size_t V>
using make_index_sequence = make_integer_sequence<size_t, V>;

template <size_t... V>
using index_sequence = integer_sequence<size_t, V...>;

// end of integer_sequence implementation

// helper functions to generate valid/invalid inputs:

// TODO: possibly return values randomly offseted?
double getValidValue( const std::pair<double,double>& minmax ) { return minmax.first + (minmax.second-minmax.first)/2; }
int getValidValue( const std::pair<int,int>& minmax ) { return minmax.first + (minmax.second-minmax.first)/2; }
Object getValidValue( const std::pair<Object,Object>& minmax ) { return Object( minmax.first.getValue() + (minmax.second.getValue()-minmax.first.getValue())/2); }

double getInvalidLowerValue( const std::pair<double,double>& minmax ) { return minmax.first - 1; }
int getInvalidLowerValue( const std::pair<int,int>& minmax ) { return minmax.first - 1; }
Object getInvalidLowerValue( const std::pair<Object,Object>& minmax ) { return Object( minmax.first.getValue() - 1); }

double getInvalidUpperValue( const std::pair<double,double>& minmax ) { return minmax.second + 1; }
int getInvalidUpperValue( const std::pair<int,int>& minmax ) { return minmax.second + 1; }
Object getInvalidUpperValue( const std::pair<Object,Object>& minmax ) { return Object( minmax.second.getValue() + 1); }

// end of helper functions to generate valid/invalid inputs:

template <std::size_t N, std::size_t, class = make_index_sequence<N>>
struct TestFuncImplInnerLoop;

template <std::size_t N, std::size_t J, std::size_t... Is>
struct TestFuncImplInnerLoop<N, J, index_sequence<Is...>> {
template <class Func, class Tup>
int operator()(Func func, const std::string& funcName, Tup &tup) {
std::cout << "Calling " << funcName << " with argument #" << J+1 << " lower than lower bound:" << std::endl;
assert(!(*func)((J == Is)?getInvalidLowerValue(std::get<Is>(tup)):getValidValue(std::get<Is>(tup))...));
std::cout << "Calling " << funcName << " with argument #" << J+1 << " greater than upper bound:" << std::endl;
assert(!(*func)((J == Is)?getInvalidUpperValue(std::get<Is>(tup)):getValidValue(std::get<Is>(tup))...));
return 0;
}
};

template <std::size_t N, class = make_index_sequence<N>>
struct TestFuncImpl;

template <std::size_t N, std::size_t... Is>
struct TestFuncImpl<N, index_sequence<Is...>> {
template<class Func, class Tup>
void operator()(Func func, const std::string& funcName, Tup &tup) {
std::cout << "Calling " << funcName << " with valid arguments:" << std::endl;
assert((*func)(getValidValue(std::get<Is>(tup))...));
int falseAsserts[sizeof...(Is)] = { TestFuncImplInnerLoop<N, Is>{}(func, funcName, tup)... };
(void)falseAsserts;
}
};

template <class... Args>
void testFunc(bool (*func)(Args...), const std::string& funcName, std::pair<Args, Args>&&... args) {
auto argsTup = std::make_tuple(args...);
std::cout << std::endl << "Testing " << funcName << ":" << std::endl;
TestFuncImpl<sizeof...(Args)>{}(func, funcName, argsTup);
}

// wrapper needed because testFunc can't call function taking const ref as arguments, they need to pass parameters by copy
bool func2Wrapper( int b, Object c )
{
return func2( b, c );
}

int main() {
testFunc( &func1, "func1", std::make_pair( minA, maxA ) );
testFunc( &func2Wrapper, "func2", std::make_pair( minB, maxB ), std::make_pair( minC, maxC ) );
}

这就像一个魅力(OP 编辑​​)和输出:

Testing func1:
Calling func1 with valid arguments:
Calling func1(50)
Calling func1 with argument #1 lower than lower bound:
Calling func1(-1)
Calling func1 with argument #1 greater than upper bound:
Calling func1(101)

Testing func2:
Calling func2 with valid arguments:
Calling func2(15,26)
Calling func2 with argument #1 lower than lower bound:
Calling func2(9,26)
Calling func2 with argument #1 greater than upper bound:
Calling func2(21,26)
Calling func2 with argument #2 lower than lower bound:
Calling func2(15,22)
Calling func2 with argument #2 greater than upper bound:
Calling func2(15,30)

[live demo]

代码需要重构以在对象上应用常量引用,因为现在它需要按值使用对象...

关于c++ - 如何强烈分解代码单元测试一堆函数的所有参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40693777/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com