4 votes

Comment tester le même comportement pour plusieurs classes avec des modèles différents dans Google Test ?

Je m'exerce aux algorithmes de tri en C++ 17 et j'ai implémenté mes unittests comme suit (ce qui suit se compile et tous les tests sont verts) :

template <typename T>
class SortingmethodTest : public ::testing::Test
{
protected:   
    T sortingmethod;

    static constexpr int amount_test_data[7] = {0, 4, 8, 10, 256, 1000, 1234};
};

using sortingmethods = ::testing::Types<STLSort<int>,
                                         InsertionSort<int>,
                                         ShellSort<int>,
                                         MergeSort<int>,
                                         OptimizedMergeSort<int>,
                                         QuickSort<int>>;

TYPED_TEST_SUITE(SortingmethodTest, sortingmethods);

TYPED_TEST(SortingmethodTest, sort)
{
    for (const auto& amount : this->amount_test_data)
    {
        Sortvector<int> test(amount);
        test.vul_random_zonder_dubbels(); // Fills the vector

        this->sortingmethod(test); // operator() of the sortmethod used (STLSort, InsertionSort, ...) sorts the vector

        ASSERT_TRUE(test.is_range());
        ASSERT_TRUE(test.is_gesorteerd());
        ASSERT_TRUE(std::is_sorted(test.begin(), test.end()));
    }
}

TYPED_TEST(SortingmethodTest, sort_reverse)
{
    // ...
}

TYPED_TEST(SortingmethodTest, sort_already_sorted)
{
    // ...
}

TYPED_TEST(SortingmethodTest, sort_empty)
{
    // ...
}

Je voudrais répéter les mêmes tests pour d'autres types que les ints, par ex.

STLSort<int>,
InsertionSort<int>,
ShellSort<int>,
MergeSort<int>,
OptimizedMergeSort<int>,
QuickSort<int>

STLSort<double>,
InsertionSort<double>,
ShellSort<double>,
MergeSort<double>,
OptimizedMergeSort<double>,
QuickSort<double>

STLSort<CustomType>,
InsertionSort<CustomType>,
ShellSort<CustomType>,
MergeSort<CustomType>,
OptimizedMergeSort<CustomType>,
QuickSort<CustomType>

...

Comment puis-je faire cela en C++ avec google test de manière aussi propre et avec autant de réutilisation que possible ? Je me perds dans la jungle des tests typés et des tests paramétrés par type [1] : quand dois-je utiliser l'un ou l'autre ?

Avec mes salutations distinguées,

Marten

[1] https://github.com/google/googletest/blob/master/docs/advanced.md#type-parameterized-tests

2voto

Mike Kinghan Points 4567

Il est souvent frustrant de constater que l'API de googletest ne nous donne pas, jusqu'à présent, plus de possibilités de faire des l'effet de levier du C++ moderne pour rendre le code de test concis, en particulier pour le test des templates. modèles. Mais jusqu'à la v1.8.x (la série de versions actuelle à cette date), googletest s'est engagé à assurer la compatibilité avec C++98, et c'est en grande partie pour cela. Les prochaines versions 1.9.x passeront à la compatibilité C++11, et nous pouvons espérer une API plus puissante. une API plus puissante.

Néanmoins, il est possible d'écrire un code googletest raisonnablement concis et simple maintenant pour faire le genre de choses que vous voulez : c'est-à-dire, tester unitairement des modèles congruents pour des valeurs variables d'un seul paramètre du modèle.

Il y a plus d'une façon de le faire. Voici un exemple concret de l'une d'entre elles, en utilisant Tests paramétrés par type .

Nous aurons un ensemble de trois modèles

template<typename T> struct (AA|BB|CC) {...};

dont chacun fournit (au moins) l'interface :

Name::Name(T const & u);
Name::operator int() const;
Name Name::operator+(Name const & u) const;
Name & Name::operator+=(Name const & u);
Name Name::operator-(Name const & u) const;
Name & Name::operator-=(Name const & u);

pour Name = (AA|BB|CC) . Nous voulons tester unitairement cette interface, pour chacun des (AA|BB|CC) chacun instancié pour chacun des six types :

char, int, float, AA<char>, BB<int>, CC<float>

Cela fait donc 18 instanciations à tester :

AA<char>, AA<int>, AA<float>, AA<AA<char>>, AA<BB<int>>, AA<CC<float>>
BB<char>, BB<int>, BB<float>, BB<AA<char>>, BB<BB<int>>, BB<CC<float>>
CC<char>, CC<int>, CC<float>, CC<AA<char>>, CC<BB<int>>, CC<CC<float>>

Pour faire court, nous n'implémenterons que deux tests génériques. Pour les objets a , b y c de tout type de test instancié :

  • Après a = b + c; b += c alors a == b .
  • Étant donné que b != c après a = b - c; c -= b alors a != c .

(Du moins, ces propriétés devraient se maintenir tant que les opérations ne débordent pas ou que perdre de la précision, ce que je vais éviter).

Nous nous attendons donc à voir 36 tests en tout.

Pour cette illustration, je ne me soucie pas de ce que AA , BB y CC sont, en dehors de leur interface commune, donc je vais simplement les dériver de manière identique à partir d'un seul modèle, comme ça :

certains_types.h

#pragma once

#include <type_traits>

namespace detail {
    template<typename T>
    struct bottom_type {
        using type = T;
    };

    template<template<typename ...> class C, typename ...Ts>
    struct bottom_type<C<Ts...>> {
        using type = typename C<Ts...>::type;
    };
}

template<typename T>
using bottom_t = typename detail::bottom_type<T>::type;

template<
    typename T,
    typename Enable = std::enable_if_t<std::is_arithmetic_v<bottom_t<T>>>
>
struct model
{
    using type = bottom_t<T>;

    model() = default;
    model(model const &) = default;
    model(T const & t)
    : _t{t}{}

    operator type() const { return _t; }

    auto operator+(model const & u) const {
        return _t + u;
    }

    auto & operator+=(model const & u) {
        _t += u;
        return *this;
    }

    auto operator-(model const & u ) const {
        return _t - u;
    }

    auto & operator-=(model const & u ) {
        _t -= u;
        return *this;
    }

protected:
    type _t = 0;
};

template<typename T> struct AA : model<T>{ using model<T>::model; };
template<typename T> struct BB : model<T>{ using model<T>::model; };
template<typename T> struct CC : model<T>{ using model<T>::model; };

Maintenant, voici mon code googletest :

main.cpp

#include <array>
#include <algorithm>
#include <random>
#include <type_traits>
#include <limits>
#include <gtest/gtest.h>
#include "some_types.h"

template<typename T>
struct fixture : public ::testing::Test
{
protected:

    template<typename U>
    static auto const & test_data() {
        using type = bottom_t<U>;
        static std::array<type,1000> data;
        static bool called;
        if (!called) {
            std::default_random_engine gen;
            auto low = std::numeric_limits<type>::min() / 2;
            auto high = std::numeric_limits<type>::max() / 2;
            auto dist = [&low,&high](){
                if constexpr (std::is_floating_point_v<type>) {
                    return std::uniform_real_distribution<type>(low,high);
                } else {
                    return std::uniform_int_distribution<type>(low,high);
                }
            }();
            std::generate(
                data.begin(),data.end(),[&dist,&gen](){ return dist(gen); });
            called = true;
        }
        return data;
    }
};

template<template<typename> class C, typename ...Ts>
using test_types = ::testing::Types<C<Ts>...>;

using AA_test_types = test_types<AA,char,int,float,AA<char>,BB<int>,CC<float>>;
using BB_test_types = test_types<BB,char,int,float,AA<char>,BB<int>,CC<float>>;
using CC_test_types = test_types<CC,char,int,float,AA<char>,BB<int>,CC<float>>;

TYPED_TEST_SUITE_P(fixture);

TYPED_TEST_P(fixture, addition)
{
    using wrapped_type = typename TypeParam::type;
    auto const & data = this->template test_data<wrapped_type>();
    auto fi = data.begin(); auto ri = data.rbegin();
    for ( ; fi != ri.base(); ++fi, ++ri)
    {
        TypeParam lhs{*fi}, rhs{*ri};
        auto sum = lhs + rhs;
        lhs += rhs;
        ASSERT_EQ(lhs,sum);
    }
}

TYPED_TEST_P(fixture, subtraction)
{
    using wrapped_type = typename TypeParam::type;
    auto const & data = this->template test_data<wrapped_type>();
    auto fi = data.begin(); auto ri = data.rbegin();
    for ( ; fi != ri.base(); ++fi, ++ri) {
        TypeParam lhs{*fi}, rhs{*ri};
        if (lhs != rhs) {
            auto diff = lhs - rhs;
            rhs -= lhs;
            ASSERT_NE(rhs,diff);
        }
    }
}

REGISTER_TYPED_TEST_SUITE_P(fixture,addition,subtraction);
INSTANTIATE_TYPED_TEST_SUITE_P(AA_tests, fixture, AA_test_types);
INSTANTIATE_TYPED_TEST_SUITE_P(BB_tests, fixture, BB_test_types);
INSTANTIATE_TYPED_TEST_SUITE_P(CC_tests, fixture, CC_test_types);

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Passons en revue les points d'intérêt:-.

template<template<typename> class C, typename ...Ts>
using test_types = ::testing::Types<C<Ts>...>;

Ici, je fais test_types un modèle-alias pour un ::testing::Types<SomeType...> liste où SomeType sera une instanciation de l'un des modèles testés. Comme se passe, mes modèles AA , BB , CC (comme le vôtre) sont tous de la forme :

template<typename T> class;

donc je veux test_types pour être un :

::testing::Types<C<Ts>...>

Ensuite, je définis 3 alias de type concret :

using AA_test_types = test_types<AA,char,int,float,AA<char>,BB<int>,CC<float>>;
using BB_test_types = test_types<BB,char,int,float,AA<char>,BB<int>,CC<float>>;
using CC_test_types = test_types<CC,char,int,float,AA<char>,BB<int>,CC<float>>;

qui sont respectivement équivalents à :

::testing::Types<AA<char>, AA<int>, AA<float>, AA<AA<char>>, AA<BB<int>>, AA<CC<float>>>;
::testing::Types<BB<char>, BB<int>, BB<float>, BB<AA<char>>, BB<BB<int>>, BB<CC<float>>>;
::testing::Types<CC<char>, CC<int>, CC<float>, CC<AA<char>>, CC<BB<int>>, CC<CC<float>>>;

Ensuite, je définis une suite de test paramétrée par type avec la fixation du modèle. fixture .

TYPED_TEST_SUITE_P(fixture);

Ensuite, je définis mes deux modèles de test paramétrés par type.

TYPED_TEST_P(fixture, addition)
{
    using wrapped_type = typename TypeParam::type;
    auto const & data = this->template test_data<wrapped_type>();
    auto fi = data.begin(); auto ri = data.rbegin();
    for ( ; fi != ri.base(); ++fi, ++ri)
    {
        TypeParam lhs{*fi}, rhs{*ri};
        auto sum = lhs + rhs;
        lhs += rhs;
        ASSERT_EQ(lhs,sum);
    }
}

TYPED_TEST_P(fixture, subtraction)
{
    using wrapped_type = typename TypeParam::type;
    auto const & data = this->template test_data<wrapped_type>();
    auto fi = data.begin(); auto ri = data.rbegin();
    for ( ; fi != ri.base(); ++fi, ++ri) {
        TypeParam lhs{*fi}, rhs{*ri};
        if (lhs != rhs) {
            auto diff = lhs - rhs;
            rhs -= lhs;
            ASSERT_NE(rhs,diff);
        }
    }
}

Ensuite, j'enregistre ces deux motifs pour l'instanciation avec chaque instanciation de fixture :

REGISTER_TYPED_TEST_SUITE_P(fixture,addition,subtraction);

Ensuite, je crée 3 instanciations appelées (AA|BB|CC)_tests de fixture pour le les listes de types de test (AA|BB|CC)_test_types respectivement :

INSTANTIATE_TYPED_TEST_SUITE_P(AA_tests, fixture, AA_test_types);
INSTANTIATE_TYPED_TEST_SUITE_P(BB_tests, fixture, BB_test_types);
INSTANTIATE_TYPED_TEST_SUITE_P(CC_tests, fixture, CC_test_types);

Et c'est tout. Compilez et reliez :

$ g++ -std=c++17 -Wall -Wextra -pedantic -o gtester main.cpp -lgtest -pthread

Cours :

./gtester
[==========] Running 36 tests from 18 test suites.
[----------] Global test environment set-up.
[----------] 2 tests from AA_tests/fixture/0, where TypeParam = AA<char>
[ RUN      ] AA_tests/fixture/0.addition
[       OK ] AA_tests/fixture/0.addition (0 ms)
[ RUN      ] AA_tests/fixture/0.subtraction
[       OK ] AA_tests/fixture/0.subtraction (1 ms)
[----------] 2 tests from AA_tests/fixture/0 (1 ms total)

[----------] 2 tests from AA_tests/fixture/1, where TypeParam = AA<int>
[ RUN      ] AA_tests/fixture/1.addition
[       OK ] AA_tests/fixture/1.addition (0 ms)
[ RUN      ] AA_tests/fixture/1.subtraction
[       OK ] AA_tests/fixture/1.subtraction (0 ms)
[----------] 2 tests from AA_tests/fixture/1 (0 ms total)
...
...
...
[----------] 2 tests from CC_tests/fixture/4, where TypeParam = CC<BB<int> >
[ RUN      ] CC_tests/fixture/4.addition
[       OK ] CC_tests/fixture/4.addition (0 ms)
[ RUN      ] CC_tests/fixture/4.subtraction
[       OK ] CC_tests/fixture/4.subtraction (0 ms)
[----------] 2 tests from CC_tests/fixture/4 (0 ms total)

[----------] 2 tests from CC_tests/fixture/5, where TypeParam = CC<CC<float> >
[ RUN      ] CC_tests/fixture/5.addition
[       OK ] CC_tests/fixture/5.addition (0 ms)
[ RUN      ] CC_tests/fixture/5.subtraction
[       OK ] CC_tests/fixture/5.subtraction (0 ms)
[----------] 2 tests from CC_tests/fixture/5 (0 ms total)

[----------] Global test environment tear-down
[==========] 36 tests from 18 test suites ran. (4 ms total)
[  PASSED  ] 36 tests.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X