使用 CppUnit 参数化测试

发布于 2024-07-09 02:48:15 字数 158 浏览 8 评论 0原文

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

My organization is using CppUnit and I am trying to run the same test using different parameters. Running a loop inside the test is not a good option as any failure will abort the test. I have looked at TestDecorator and TestCaller but neither seems to really fit. Code samples would be helpful.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

桃扇骨 2024-07-16 02:48:15

在 CppUnit 中似乎不可能直接参数化测试用例(请参阅 此处此处 )。 但是,您确实有几个选择:

使用 RepeatedTest

您也许可以巧妙地利用内置的 RepeatedTest 装饰器。 这允许测试用例运行多次(尽管没有参数化)。

我承认我自己从未使用过这个,但也许您可以让 RepeatedTest 驱动一些看门人函数,这将(也许使用类静态变量?)在每次运行时选择不同的输入。 它会依次调用您想要测试的 true 函数,并使用该值作为输入。

使用 TestCase 子类

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

使用简单的辅助函数

最直接(但自动化程度最低)的方法是创建一个辅助函数,该函数接受您想要传递给“真实”函数的参数,然后进行大量单独的测试案例。 每个测试用例都会使用不同的值调用您的辅助函数。


如果您选择上面列出的前两个选项中的任何一个,我很想听听您的经历。

It does not appear possible in CppUnit to parameterize a test case directly (see here and here). However, you do have a few options:

Use a RepeatedTest

You may be able to make some clever use of the built-in RepeatedTest decorator. This allows a test case to be run multiple times (though without parameterization).

I'll admit to never having used this myself, but perhaps you could have the RepeatedTest drive some gatekeeper function, which would (using a class static variable, perhaps?) pick a different input with every run. It would in turn call the true function you'd like to test with that value as input.

Use a TestCase subclass

One person on CppUnit's SourceForge page claims to have written a subclass of TestCase that will run a particular test an arbitrary number of times, although in a slightly different manner than the RepeatedTest class offers. Sadly, the poster simply described the motivation for creating the class, but did not provide the source code. There was, however, an offer to contact the individual for more details.

Use a simple helper function

The most straight-forward (but least automated) way to do this is to create a helper function that takes the parameter you'd like to pass on to your "real" function, and then have lots of individual test cases. Each test case would call your helper function with a different value.


If you choose either of the first two options listed above, I'd be interested in hearing about your experience.

安静被遗忘 2024-07-16 02:48:15
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 的“首选做事方式”,但这就是我现在正在采取的方法。

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>);

I don't know if that's considered kosher as per CppUnit's "preferred way of doing things" but that's the approach I'm taking now.

一念一轮回 2024-07-16 02:48:15

根据 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);
  • parameters():提供向量与参数。
  • testWithParameter(...):为每个参数调用。 这是您实施参数化测试的地方。

详细说明可以在这里找到:http://brain-child.de/engineering/parameterizing -cppunit-tests

德语版本可以在这里找到:http://brain -child.de/engineering/parametrierbare-tests-cppunit

Upon of the suggestion of Marcin i've implemented some macros aiding to define parameterized CppUnit tests.

With this solution you just need to replace the old macros CPPUNIT_TEST_SUITE and CPPUNIT_TEST_SUITE_END within the class's header file:

CPPUNIT_PARAMETERIZED_TEST_SUITE(<TestSuiteClass>, <ParameterType>);

/*
 * put plain old tests here.
 */

CPPUNIT_PARAMETERIZED_TEST_SUITE_END();

In the implementation file you need to replace the old CPPUNIT_TEST_SUITE_REGISTRATION macro with:

CPPUNIT_PARAMETERIZED_TEST_SUITE_REGISTRATION ( <TestSuiteClass>, <ParameterType> )

These macros require you to implement the methods:

static std::vector parameters();
void testWithParameter(ParameterType& parameter);
  • parameters(): Provides a vector with the parameters.
  • testWithParameter(...): Is called for each parameter. This is where you implement your parameterized test.

A detailed explanation can be found here: http://brain-child.de/engineering/parameterizing-cppunit-tests

The german version can be found here: http://brain-child.de/engineering/parametrierbare-tests-cppunit

阳光的暖冬 2024-07-16 02:48:15

我不是 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

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

I'm not a C++ programmer but I can help with the unit-test concept:

Test-cases are meant to run isolated and with no dependency on external parameters. Additionally you should keep the number of test-cases down to the minimum which covers most of your code. There are cases, however (and I have already dealt with some), where some tests look the same, differing only by some minor parameters. The best bet then is to write a fixture which takes the parameter you're talking about, and then have one test-case for each of the parameters, calling the fixture with it. A generic example follows:

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

Otherwise you're not writing true test-cases, because they're supposed to be reproducible without much knowledge from the one who is executing them. I imagine there are a handful of parameters which are all important as input to the tests. Then why not make each one explicit inside its own test-case? That's also the best way to document then, instead of writing a separate document to guide the programmer which will read the code years later.

生生漫 2024-07-16 02:48:15

这是一个非常古老的问题,但我只需要做类似的事情并提出以下解决方案。 我对它不是 100% 满意,但它似乎很好地完成了这项工作

  1. 为测试方法定义一组输入参数。 例如,假设这些是字符串,那么让我们这样做:

    std::vector;   测试参数 = { "字符串1", "字符串2" }; 
      size_t 测试计数器 = 0; 
      
  2. 实现一个通用测试器函数,每次调用都会从测试数组中获取下一个参数,例如:

    void Test::genericTester() 
      { 
        const std::string ¶m = testParameters[testCounter++]; 
    
        // 用参数做一些事情 
      }  
      
  3. 在测试中 addTestToSuite() 方法声明(由 CPPUNIT 宏隐藏)而不是(或在旁边)使用 CPPUNIT_TEST 宏定义方法,添加与此类似的代码:

    CPPUNIT_TEST_SUITE(StatementTest); 
    
      测试计数器 = 0; 
      for (size_t i = 0; i < testParameters.size(); i++) { 
        CPPUNIT_TEST_SUITE_ADD_TEST( 
          ( 新 CPPUNIT_NS::TestCaller( 
                    // 这里我们使用参数名称作为单元测试名称。 
                    // 当然,你可以让测试参数更复杂,  
                    // 例如,将测试名称作为显式字段。 
                    context.getTestNameFor( testParamaters[i] ), 
                    // 这里我们指向通用测试器函数。 
                    &TestFixtureType::genericTester, 
                    context.makeFixture() ) ) ); 
      } 
    
      CPPUNIT_TEST_SUITE_END(); 
      

这样我们就可以注册 genericTester()多次,每个参数一次,并指定名称。 这似乎对我来说很有效。

希望这对某人有帮助。

This is a very old question, but I just needed to do something similar and came up with the following solution. I'm not 100% happy with it, but it seems to do the job quite well

  1. Define a set of input parameters to a testing method. For example, let's say these are strings, so let's do:

    std::vector<std::string> testParameters = { "string1", "string2" };
    size_t testCounter = 0;
    
  2. Implement a generic tester function, which with each invocation will take the next parameter from the test array, e.g.:

    void Test::genericTester()
    {
      const std::string ¶m = testParameters[testCounter++];
    
      // do something with param
    } 
    
  3. In the test addTestToSuite() method declaration (hidden by the CPPUNIT macros) instead of (or next to) defining methods with the CPPUNIT_TEST macros, add code similar to this:

    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();
    

This way we register genericTester() multiple times, one for each parameter, with a name specified. This seems to work for me quite well.

Hope this helps someone.

酒与心事 2024-07-16 02:48:15

根据consumerwhore的回答,我最终得到了一种非常好的方法,我可以使用单行注册宏和我想要的尽可能多的参数来创建多个测试。

只需定义一个参数类:

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

    int m_param1;
    std::string m_param2;
};

让您的测试装置将其用作“非类型模板参数”(我认为这就是它的名称):

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

Based on consumerwhore answer, I ended up with a very nice approach where I can create multiple tests using a one-line registration macro with as many parameters I want.

Just define a parameter class:

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

    int m_param1;
    std::string m_param2;
};

Make your test fixture use it as "non-type template parameter" (I think that's how it's called):

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;
    };
};

Have a small macro creating a parameter and registering a new test fixture:

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

Finally, add as many tests you want like that:

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

Executing this test will give you:

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
离去的眼神 2024-07-16 02:48:15

以下类/帮助器宏对适用于我当前的用例。 在您的 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 ®istry = CppUnit::TestFactoryRegistry::getRegistry();
  runner.addTest( registry.makeTest() );
  bool wasSuccessful = runner.run( "", false );
  return wasSuccessful;
}

The following class/helper macro pair works for my current use-cases. In your TestFixture subclass, just define a method which accepts one parameter and then add the test with 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 ®istry = CppUnit::TestFactoryRegistry::getRegistry();
  runner.addTest( registry.makeTest() );
  bool wasSuccessful = runner.run( "", false );
  return wasSuccessful;
}
冷…雨湿花 2024-07-16 02:48:15

我也看过这个主题。
使用旧的代码库并添加额外的测试。

我的方法是之前描述的一些想法的混合。
目标之一是要编写的实际测试代码简单且易于阅读。

  1. 创建宏 TEST_METHOD_ARG(f, mark, ...)
    请注意,mark 用于扩展 f 的基本测试函数名称。

  2. 创建一个基本测试f
    基本测试接受多个参数,具体取决于测试详细信息。

  3. 使用宏 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)

I had a look at this subject too.
Working with an old code base and adding extra tests.

My approach is a mix of some of the ideas described before.
One of the goals is that the actual test code to write is simple and easy to read.

  1. create a macro TEST_METHOD_ARG(f, mark, ...)
    Note that mark is used to extend the base test function name of f.

  2. Create a base test f
    The base test accepts a number of arguments depending on test details.

  3. Create a sequence of tests using marcro TEST_METHOD_ARG(f, mark, ...)
    This will result in a number of tests all beginning with the base test name
    each one will call the base test with the arguments for it.

Compiler: VS2015, should work with any VS version from this one on.

#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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文