c++ 使用CppUnit参数化测试

daupos2t  于 2023-04-08  发布在  其他
关注(0)|答案(8)|浏览(136)

我的组织正在使用CppUnit,我正在尝试使用不同的参数运行相同的测试。在测试中运行循环不是一个好的选择,因为任何失败都会中止测试。我已经看过TestDecoratorTestCaller,但似乎都不适合。代码示例会有所帮助。

cnjp1d6j

cnjp1d6j1#

在CppUnit中,似乎不可能直接参数化测试用例(参见herehere)。但是,您确实有一些选项:

使用RepeatedTest

您可以巧妙地使用内置的RepeatedTest装饰器,这允许测试用例多次运行(尽管没有参数化)。
我承认我自己从来没有用过这个,但也许你可以让RepeatedTest驱动一些看门人函数,它会(也许使用一个类静态变量?)在每次运行时选择一个不同的输入。它会反过来调用你想用这个值作为输入测试的true函数。

使用TestCase子类

CppUnit的SourceForge页面上的One person声称已经编写了一个TestCase的子类,它将运行一个特定的测试任意次数,尽管与RepeatedTest类提供的方式略有不同。遗憾的是,帖子只描述了创建该类的动机,但没有提供源代码。然而,有一个联系个人以获取更多细节的提议。

使用简单的helper函数

最直接(但自动化程度最低)的方法是创建一个helper函数,它接受你想传递给“真实的”函数的参数,然后有很多单独的测试用例。每个测试用例都会用不同的值调用你的helper函数。
如果你选择上面列出的前两个选项中的任何一个,我很有兴趣听听你的经历。

liwlm1x9

liwlm1x92#

class members : public CppUnit::TestFixture
{
    int i;
    float f;
};

class some_values : public members
{
    void setUp()
    {
        // initialization here
    }
};

class different_values : public members
{
    void setUp()
    {
        // different initialization here
    }
};

tempalte<class F>
class my_test : public F
{
    CPPUNIT_TEST_SUITE(my_test<F>);
    CPPUNIT_TEST(foo);
    CPPUNIT_TEST_SUITE_END();

    foo() {}
};

CPPUNIT_TEST_SUITE_REGISTRATION(my_test<some_values>);
CPPUNIT_TEST_SUITE_REGISTRATION(my_test<different_values>);

我不知道这是否被认为是按照CppUnit的“首选做事方式”,但这是我现在采取的方法。

xienkqul

xienkqul3#

根据Marcin的建议,我实现了一些宏来帮助定义参数化的CppUnit测试。
使用此解决方案,您只需替换类头文件中的旧宏CPPUNIT_TEST_SUITE和CPPUNIT_TEST_SUITE_END:

CPPUNIT_PARAMETERIZED_TEST_SUITE(<TestSuiteClass>, <ParameterType>);

/*
 * put plain old tests here.
 */

CPPUNIT_PARAMETERIZED_TEST_SUITE_END();

在实现文件中,您需要将旧的CPPUNIT_TEST_SUITE_REGISTRATION宏替换为:

CPPUNIT_PARAMETERIZED_TEST_SUITE_REGISTRATION ( <TestSuiteClass>, <ParameterType> )

这些宏要求您实现以下方法:

static std::vector parameters();
void testWithParameter(ParameterType& parameter);
  • 参数():提供具有参数的向量。
  • testWithParameter(...):这是实现参数化测试的地方。

详细的解释可以在这里找到:http://brain-child.de/engineering/parameterizing-cppunit-tests
德语版本可以在这里找到:http://brain-child.de/engineering/parametrierbare-tests-cppunit

fnatzsnv

fnatzsnv4#

我不是一个C++程序员,但我可以帮助单元测试的概念:
测试用例应该独立运行,不依赖于外部参数。另外,你应该把测试用例的数量控制在最低限度,以覆盖你的大部分代码。(我已经处理过一些),其中一些测试看起来是一样的,只是一些小参数不同。最好的办法是编写一个 fixture,它接受您正在谈论的参数,然后为每个参数创建一个测试用例,用它调用fixture。下面是一个通用的例子:

class MyTestCase

  # this is your fixture
  def check_special_condition(param)
    some
    complex
    tests
  end

  # these are your test-cases
  def test_1
    check_special_condition("value_1")
  end

  def test_2
    check_special_condition("value_2")
  end

end

否则你就不是在写真正的测试用例,因为它们应该是可重复的,而不需要执行它们的人有太多的知识。我想有一些参数都是测试的重要输入。那么为什么不把每个参数都明确地写在自己的测试用例中呢?这也是记录的最好方法。而不是编写一个单独的文档来指导程序员,程序员将在几年后阅读代码。

pexxcrt2

pexxcrt25#

这是一个很老的问题,但我只是需要做一些类似的事情,并提出了以下的解决方案。我不是100%满意,但它似乎做的工作相当不错
1.定义一组测试方法的输入参数。例如,假设这些是字符串,那么我们这样做:

std::vector<std::string> testParameters = { "string1", "string2" };
size_t testCounter = 0;

1.实现一个通用的测试器函数,每次调用时都会从测试数组中获取下一个参数,例如:

void Test::genericTester()
{
  const std::string &param = testParameters[testCounter++];

  // do something with param
}

1.在test addTestToSuite()方法声明(被CPPUNIT宏隐藏)中,不要使用CPPUNIT_TEST宏定义方法(或在其旁边),而是添加类似于以下内容的代码:

CPPUNIT_TEST_SUITE(StatementTest);

testCounter = 0;
for (size_t i = 0; i < testParameters.size(); i++) {
  CPPUNIT_TEST_SUITE_ADD_TEST(
    ( new CPPUNIT_NS::TestCaller<TestFixtureType>(
              // Here we use the parameter name as the unit test name.
              // Of course, you can make test parameters more complex, 
              // with test names as explicit fields for example.
              context.getTestNameFor( testParamaters[i] ),
              // Here we point to the generic tester function.
              &TestFixtureType::genericTester,
              context.makeFixture() ) ) );
}

CPPUNIT_TEST_SUITE_END();

通过这种方式,我们可以多次注册genericTester(),每个参数一次,并指定一个名称。
希望这对某人有帮助。

ekqde3dh

ekqde3dh6#

基于consumerwhore的回答,我最终得到了一个非常好的方法,可以使用一行注册宏创建多个测试,其中包含我想要的任意多个参数。
只需要定义一个参数类:

class Param
{
public:
    Param( int param1, std::string param2 ) :
        m_param1( param1 ),
        m_param2( param2 )
    {
    }

    int m_param1;
    std::string m_param2;
};

让你的测试fixture使用它作为“非类型模板参数”(我想这就是它的调用方式):

template <Param& T>
class my_test : public CPPUNIT_NS::TestFixture
{
    CPPUNIT_TEST_SUITE(my_test<T>);
    CPPUNIT_TEST( doProcessingTest );
    CPPUNIT_TEST_SUITE_END();

    void doProcessingTest()
    {
        std::cout << "Testing with " << T.m_param1 << " and " << T.m_param2 << std::endl;
    };
};

使用一个小宏创建一个参数并注册一个新的测试夹具:

#define REGISTER_TEST_WITH_PARAMS( name, param1, param2 ) \
    Param name( param1, param2 ); \
    CPPUNIT_TEST_SUITE_REGISTRATION(my_test<name>);

最后,添加你想要的测试:

REGISTER_TEST_WITH_PARAMS( test1, 1, "foo" );
REGISTER_TEST_WITH_PARAMS( test2, 3, "bar" );

执行此测试将为您提供:

my_test<class Param test1>::doProcessingTestTesting with 1 and foo : OK
my_test<class Param test2>::doProcessingTestTesting with 3 and bar : OK
OK (2)
Test completed, after 0 second(s). Press enter to exit
x4shl7ld

x4shl7ld7#

下面的类/辅助宏对适用于我目前的用例。在TestFixture子类中,只需定义一个接受一个参数的方法,然后使用PARAMETERISED_TEST(method_name, argument_type, argument_value)添加测试。

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/ui/text/TestRunner.h>

template <class FixtureT, class ArgT>
class ParameterisedTest : public CppUnit::TestCase {
public:
  typedef void (FixtureT::*TestMethod)(ArgT);
  ParameterisedTest(std::string name, FixtureT* fix, TestMethod f, ArgT a) :
    CppUnit::TestCase(name), fixture(fix), func(f), arg(a) {
  }
  ParameterisedTest(const ParameterisedTest* other) = delete;
  ParameterisedTest& operator=(const ParameterisedTest& other) = delete;

  void runTest() {
    (fixture->*func)(arg);
  }
  void setUp() { 
    fixture->setUp(); 
  }
  void tearDown() { 
    fixture->tearDown(); 
  }
private:
  FixtureT* fixture;
  TestMethod func;
  ArgT arg;
};

#define PARAMETERISED_TEST(Method, ParamT, Param)           \
  CPPUNIT_TEST_SUITE_ADD_TEST((new ParameterisedTest<TestFixtureType, ParamT>(context.getTestNameFor(#Method #Param), \
                                          context.makeFixture(), \
                                          &TestFixtureType::Method, \
                                              Param)))

class FooTests : public CppUnit::TestFixture {
  CPPUNIT_TEST_SUITE(FooTests);
  PARAMETERISED_TEST(ParamTest, int, 0);
  PARAMETERISED_TEST(ParamTest, int, 1);
  PARAMETERISED_TEST(ParamTest, int, 2);
  CPPUNIT_TEST_SUITE_END();
public:
  void ParamTest(int i) {
    CPPUNIT_ASSERT(i > 0);
  }
};
CPPUNIT_TEST_SUITE_REGISTRATION(FooTests);

int main( int argc, char **argv)
{
  CppUnit::TextUi::TestRunner runner;
  CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
  runner.addTest( registry.makeTest() );
  bool wasSuccessful = runner.run( "", false );
  return wasSuccessful;
}
sulc1iza

sulc1iza8#

我也看了一下这个主题。使用旧的代码库并添加额外的测试。
我的方法是前面描述的一些想法的混合。其中一个目标是要编写的实际测试代码简单易读。
1.创建宏TEST_METHOD_ARG(f,mark,...)注意mark用于扩展f的基本测试函数名。
1.创建基本测试f基本测试根据测试详细信息接受多个参数。
1.使用marcro TEST_METHOD_ARG(f,mark,...)创建一个测试序列。这将导致许多测试都以基本测试名称开头,每个测试都将调用带有参数的基本测试。
编译器:VS2015,应该与任何VS版本一起工作。

#define TEST_METHOD_ARG(f, mark, ...) TEST_METHOD(f##_##mark) { f(__VA_ARGS__); }

void BaseTest2digits(int arg1, int arg2)
{
    // just a simple test as example.
    Assert::AreEqual(arg1, arg2);
}

TEST_METHOD_ARG(BaseTest2digits, v1, 0, 0)
TEST_METHOD_ARG(BaseTest2digits, v2, 0, 1)
TEST_METHOD_ARG(BaseTest2digits, v3, 1, 0)
TEST_METHOD_ARG(BaseTest2digits, v4, 1, 1)

void BaseTest3digits(int arg1, int arg2, int arg3)
{
    // just a simple test as example.
    Assert::AreEqual(arg1, arg2);
    Assert::AreEqual(arg1, arg3);
}

TEST_METHOD_ARG(BaseTest3digits, v1, 0, 0, 0)
TEST_METHOD_ARG(BaseTest3digits, v2, 0, 0, 1)
TEST_METHOD_ARG(BaseTest3digits, v3, 0, 1, 0)
TEST_METHOD_ARG(BaseTest3digits, v4, 1, 1, 1)

相关问题