Как проверить многопараметрическую формулу - PullRequest
8 голосов
/ 23 января 2012

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

Этот конкретный фрагмент кода является формулой, котораяпринимает 3 параметра и возвращает значение.У меня даже есть несколько таблиц данных с ожидаемыми результатами для разных входных данных, поэтому теоретически я мог бы просто набрать миллион тестов, просто изменив входные параметры и сравнив результаты с соответствующим ожидаемым значением.

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

Итак, теперь я знаю, как автоматически создавать тесты для различных входных данных.
Нокак мне получить соответствующий ожидаемый результат, чтобы сравнить его с моим вычисленным?

Единственное, что я смог придумать, - это статическая таблица поиска и статический член в текстовом фиксаторе, который являетсяиндекс для таблицы поиска и увеличивается при каждом запуске.Примерно так:

#include "gtest/gtest.h"

double MyFormula(double A, double B, double C)
{
    return A*B - C*C;   // Example. The real one is much more complex
}

class MyTest:public ::testing::TestWithParam<std::tr1::tuple<double, double, double>>
{
protected:

    MyTest(){ Index++; }
    virtual void SetUp()
    {
        m_C = std::tr1::get<0>(GetParam());
        m_A = std::tr1::get<1>(GetParam());
        m_B = std::tr1::get<2>(GetParam());
    }

    double m_A;
    double m_B;
    double m_C;

    static double ExpectedRes[];
    static int Index;

};

int MyTest::Index = -1;

double MyTest::ExpectedRes[] =
{
//               C = 1
//      B:   1     2     3     4     5     6     7     8     9    10
/*A =  1*/  0.0,  1.0,  2.0,  3.0,  4.0,  5.0,  6.0,  7.0,  8.0,  9.0, 
/*A =  2*/  1.0,  3.0,  5.0,  7.0,  9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 
/*A =  3*/  2.0,  5.0,  8.0, 11.0, 14.0, 17.0, 20.0, 23.0, 26.0, 29.0, 

//               C = 2
//      B:     1     2     3     4     5     6     7     8     9    10
/*A =  1*/   -3.0, -2.0, -1.0,  0.0,  1.0,  2.0,  3.0,  4.0,  5.0,  6.0, 
/*A =  2*/   -2.0,  0.0,  2.0,  4.0,  6.0,  8.0, 10.0, 12.0, 14.0, 16.0, 
/*A =  3*/   -1.0,  2.0,  5.0,  8.0, 11.0, 14.0, 17.0, 20.0, 23.0, 26.0, 
};

TEST_P(MyTest, TestFormula)
{
    double res = MyFormula(m_A, m_B, m_C);
    ASSERT_EQ(ExpectedRes[Index], res);
}

INSTANTIATE_TEST_CASE_P(TestWithParameters,  
                        MyTest,  
                        testing::Combine( testing::Range(1.0, 3.0), // C
                                          testing::Range(1.0, 4.0), // A 
                                          testing::Range(1.0, 11.0) // B
                                          ));  

Это хороший подход или есть какой-нибудь лучший способ получить правильный ожидаемый результат для каждого прогона?

Ответы [ 3 ]

10 голосов
/ 25 января 2012

Включите ожидаемый результат вместе с входными данными.Вместо тройки входных значений, сделайте ваш тестовый параметр четырехзначным.

class MyTest: public ::testing::TestWithParam<
  std::tr1::tuple<double, double, double, double>>
{ };

TEST_P(MyTest, TestFormula)
{
  double const C = std::tr1::get<0>(GetParam());
  double const A = std::tr1::get<1>(GetParam());
  double const B = std::tr1::get<2>(GetParam());
  double const result = std::tr1::get<3>(GetParam());

  ASSERT_EQ(result, MyFormula(A, B, C));
}

Недостатком является то, что вы не сможете сохранить свои тестовые параметры краткими с testing::Combine.Вместо этого вы можете использовать testing::Values, чтобы определить каждый отдельный 4-кортеж, который вы хотите протестировать.Вы можете достичь предела количества аргументов для Values, чтобы вы могли разделить свои экземпляры, например, поместив все случаи C = 1 в один и все C = 2 случаи в другом.

INSTANTIATE_TEST_CASE_P(
  TestWithParametersC1, MyTest, testing::Values(
    //           C     A     B
    make_tuple( 1.0,  1.0,  1.0,  0.0),
    make_tuple( 1.0,  1.0,  2.0,  1.0),
    make_tuple( 1.0,  1.0,  3.0,  2.0),
    // ...
  ));  

INSTANTIATE_TEST_CASE_P(
  TestWithParametersC2, MyTest, testing::Values(
    //           C     A     B
    make_tuple( 2.0,  1.0,  1.0, -3.0),
    make_tuple( 2.0,  1.0,  2.0, -2.0),
    make_tuple( 2.0,  1.0,  3.0, -1.0),
    // ...
  ));

Или вы можете поместить все значения в массив отдельно от вашего экземпляра и затем использовать testing::ValuesIn:

std::tr1::tuple<double, double, double, double> const FormulaTable[] = {
  //           C     A     B
  make_tuple( 1.0,  1.0,  1.0,  0.0),
  make_tuple( 1.0,  1.0,  2.0,  1.0),
  make_tuple( 1.0,  1.0,  3.0,  2.0),
  // ...
  make_tuple( 2.0,  1.0,  1.0, -3.0),
  make_tuple( 2.0,  1.0,  2.0, -2.0),
  make_tuple( 2.0,  1.0,  3.0, -1.0),
  // ...
};

INSTANTIATE_TEST_CASE_P(
  TestWithParameters, MyTest, ::testing::ValuesIn(FormulaTable));
1 голос
/ 23 января 2012

См. Жесткое кодирование ожидаемого результата, как будто вы снова ограничиваете количество тестовых случаев. Если вы хотите получить полную модель, управляемую данными, я бы скорее предложил вам прочитать входные данные, ожидаемый результат из простого файла / xml / xls.

0 голосов
/ 23 января 2012

У меня нет большого опыта работы с юнит-тестированием, но, как математик, я думаю, что вы не могли бы сделать больше.

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

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

...