【问题标题】:Parameterizing a test using CppUnit使用 CppUnit 参数化测试
【发布时间】:2010-09-22 08:25:13
【问题描述】:

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

【问题讨论】:

    标签: c++ unit-testing cppunit


    【解决方案1】:

    在 CppUnit 中似乎无法直接参数化测试用例(请参阅 herehere)。但是,您确实有几个选择:

    使用RepeatedTest

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

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

    使用TestCase 子类

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

    使用简单的辅助函数

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


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

    【讨论】:

      【解决方案2】:
      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 的“首选做事方式”的要求,但这是我现在采用的方法。

      【讨论】:

      • 我正在尝试这种方法,但无法弄清楚 my_test(在我的情况下为 TEST),我收到一个错误:“TEST”未在此范围内声明
      【解决方案3】:

      根据 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

      【讨论】:

      • 两个链接都失效了
      【解决方案4】:

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

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

      【讨论】:

      • 虽然它并没有真正回答我的问题,但还是谢谢
      【解决方案5】:

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

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

        std::vector<std::string> testParameters = { "string1", "string2" };
        size_t testCounter = 0;
        
      2. 实现一个通用测试器函数,每次调用都会从测试数组中获取下一个参数,例如:

        void Test::genericTester()
        {
          const std::string &param = testParameters[testCounter++];
        
          // do something with param
        } 
        
      3. 在测试 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(),每个参数一个,并指定一个名称。这似乎对我很有效。

      希望这对某人有所帮助。

      【讨论】:

        【解决方案6】:

        根据 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
        

        【讨论】:

          【解决方案7】:

          以下类/帮助宏对适用于我当前的用例。在您的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;
          }
          

          【讨论】:

            猜你喜欢
            • 2013-04-09
            • 2010-11-04
            • 1970-01-01
            • 1970-01-01
            • 2011-12-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多