代码之家  ›  专栏  ›  技术社区  ›  Harald Scheirich

使用cppunit参数化测试

  •  7
  • Harald Scheirich  · 技术社区  · 17 年前

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

    7 回复  |  直到 10 年前
        1
  •  8
  •   Matt Dillard    17 年前

    在cppunit中不可能直接参数化测试用例(请参见 here here )但是,您有几个选择:

    使用A RepeatedTest

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

    我承认我自己从来没有用过这个,但也许你可以 重复试验 驱动一些gatekeeper函数,它可能会(使用类静态变量?)每次运行时选择不同的输入。它将依次调用您想用该值作为输入进行测试的true函数。

    使用 TestCase 子类

    One person 在cppunit的sourceforge页面上,声明编写了 测试用例 它将以任意次数运行特定测试,尽管方式与 重复试验 班级优惠。遗憾的是,海报只是描述了创建类的动机,但没有提供源代码。不过,也有人提议与此人联系,了解更多细节。

    使用简单的助手函数

    要做到这一点,最直接(但自动化程度最低)的方法是创建一个helper函数,它接受要传递给“real”函数的参数,然后拥有许多单独的测试用例。每个测试用例都会用不同的值调用helper函数。


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

        2
  •  3
  •   consumerwhore    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的“首选做事方式”,但这是我现在采取的方法。

        3
  •  1
  •   user3354701    11 年前

    根据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
  •  0
  •   Rômulo Ceccon    17 年前

    我不是一个C++程序员,但我可以帮助实现单元测试概念:

    测试用例是独立运行的,不依赖于外部参数。另外,您应该将测试用例的数量保持在覆盖大部分代码的最小值。然而,有些情况下(我已经处理过一些),有些测试看起来是一样的,只是在一些小参数上有所不同。最好的办法是写一个 固定装置 它接受您正在讨论的参数,然后为每个参数都有一个测试用例,并用它调用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
  •  0
  •   Marcin Zukowski    12 年前

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

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

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

      void Test::genericTester()
      {
        const std::string &param = testParameters[testCounter++];
      
        // do something with param
      } 
      
    3. 在test addtesttosuite()方法声明(由cppunit宏隐藏)中,而不是(或旁边)使用cppunit_测试宏定义方法,添加类似于以下内容的代码:

      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
  •  0
  •   jpo38    10 年前

    基于consumerhore的答案,我得到了一个非常好的方法,在这里我可以使用一个单行注册宏创建多个测试,其中包含我想要的任意多个参数。

    只需定义一个参数类:

    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
  •  0
  •   Rupert Nash    9 年前

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