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.