Параметризация теста с использованием CppUnit - PullRequest
7 голосов
/ 14 ноября 2008

Моя организация использует CppUnit, и я пытаюсь запустить один и тот же тест, используя разные параметры. Выполнение цикла внутри теста не является хорошим вариантом, так как любой сбой приведет к прерыванию теста. Я посмотрел на TestDecorator и TestCaller, но ни один из них не подходит. Примеры кода будут полезны.

Ответы [ 7 ]

8 голосов
/ 15 ноября 2008

В CppUnit не представляется возможным напрямую параметризовать тестовый пример (см. здесь и здесь ). Однако у вас есть несколько вариантов:

Используйте RepeatedTest

Возможно, вам удастся использовать встроенный декоратор RepeatedTest. Это позволяет запускать тестовый набор несколько раз (хотя и без параметризации).

Я признаю, что никогда не использовал это сам, но, возможно, вы могли бы заставить RepeatedTest управлять некоторой функцией привратника, которая бы (используя статическую переменную класса, возможно?) Выбирала разные входные данные при каждом запуске. Это, в свою очередь, вызовет истинную функцию, которую вы хотите проверить с этим значением в качестве ввода.

Использовать TestCase подкласс

Один человек на странице SourceForge в CppUnit утверждает, что написал подкласс TestCase, который будет запускать определенный тест произвольное количество раз, хотя и несколько иначе, чем класс RepeatedTest. , К сожалению, автор просто описал мотивы создания класса, но не предоставил исходный код. Было, однако, предложение связаться с человеком для получения более подробной информации.

Используйте простую вспомогательную функцию

Самый простой (но наименее автоматизированный) способ сделать это - создать вспомогательную функцию, которая принимает параметр, который вы хотите передать своей «реальной» функции, и затем иметь множество отдельных тестовых случаев. Каждый тестовый пример будет вызывать вашу вспомогательную функцию с другим значением.


Если вы выберете один из первых двух вариантов, перечисленных выше, мне было бы интересно узнать о вашем опыте.

3 голосов
/ 18 мая 2010
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, но сейчас я использую такой подход.

1 голос
/ 26 февраля 2014

По предложению 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 / parametertrierbare-tests-cppunit

0 голосов
/ 14 июня 2016

Следующая пара макросов класс / помощник работает для моих текущих сценариев использования. В вашем подклассе 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;
}
0 голосов
/ 08 января 2016

Основываясь на ответе потребителя, где я получил, я получил очень хороший подход, в котором я могу создать несколько тестов, используя макрос регистрации в одну строку с таким количеством параметров, которое я хочу.

Просто определите класс параметров:

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
0 голосов
/ 18 апреля 2013

Это очень старый вопрос, но мне просто нужно было сделать что-то подобное и придумал следующее решение. Я не на 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 () несколько раз, по одному для каждого параметра, с указанным именем. Это, кажется, работает для меня довольно хорошо.

Надеюсь, это кому-нибудь поможет.

0 голосов
/ 14 ноября 2008

Я не программист на C ++, но могу помочь с концепцией модульного теста:

Тест-кейсы предназначены для работы изолированно и без зависимости от внешних параметров. Кроме того, вы должны свести количество тестов к минимуму, который покрывает большую часть вашего кода. Однако есть случаи (и я уже имел дело с некоторыми), когда некоторые тесты выглядят одинаково, отличаясь лишь некоторыми незначительными параметрами. Лучше всего тогда написать прибор , который будет принимать параметр, о котором вы говорите, а затем иметь один тест-кейс для каждого из параметров, вызывая прибор с ним. Вот общий пример:

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

В противном случае вы не пишете настоящие тест-кейсы, потому что они должны воспроизводиться без особого знания того, кто их выполняет. Я предполагаю, что есть несколько параметров, которые важны как входные данные для тестов. Тогда почему бы не сделать каждый из них явным внутри собственного тестового примера? Тогда это также лучший способ документировать, а не писать отдельный документ для руководства программиста, который будет читать код годами позже.

...