unit-testing - unitarias - unit test c# visual studio 2015
¿Cómo escribir pruebas ejecutables de static_assert? (1)
Como parezco ser un chiflado solitario por mi interés en esta pregunta, he obtenido una respuesta para mí, con un archivo de encabezado esencialmente como este:
exceptionalized_static_assert.h
#ifndef TEST__EXCEPTIONALIZE_STATIC_ASSERT_H
#define TEST__EXCEPTIONALIZE_STATIC_ASSERT_H
/* Conditionally compilable apparatus for replacing `static_assert`
with a runtime exception of type `exceptionalized_static_assert`
within (portions of) a test suite.
*/
#if TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1
#include <string>
#include <stdexcept>
namespace test {
struct exceptionalized_static_assert : std::logic_error
{
exceptionalized_static_assert(char const *what)
: std::logic_error(what){};
virtual ~exceptionalized_static_assert() noexcept {}
};
template<bool Cond>
struct exceptionalize_static_assert;
template<>
struct exceptionalize_static_assert<true>
{
explicit exceptionalize_static_assert(char const * reason) {
(void)reason;
}
};
template<>
struct exceptionalize_static_assert<false>
{
explicit exceptionalize_static_assert(char const * reason) {
std::string s("static_assert would fail with reason: ");
s += reason;
throw exceptionalized_static_assert(s.c_str());
}
};
} // namespace test
// A macro redefinition of `static_assert`
#define static_assert(cond,gripe) /
struct _1_test /
: test::exceptionalize_static_assert<cond> /
{ _1_test() : /
test::exceptionalize_static_assert<cond>(gripe){}; /
}; /
_1_test _2_test
#endif // TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1
#endif // EOF
Este encabezado solo debe incluirse en un banco de pruebas, y luego hará visible la redefinición de macro de static_assert
visible solo cuando el banco de pruebas esté construido con
`-DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1`
El uso de este aparato se puede esbozar con una biblioteca de plantillas de juguetes:
my_template.h
#ifndef MY_TEMPLATE_H
#define MY_TEMPLATE_H
#include <type_traits>
template<typename T>
struct my_template
{
static_assert(std::is_pod<T>::value,"T must be POD in my_template<T>");
explicit my_template(T const & t = T())
: _t(t){}
// ...
template<int U>
static int increase(int i) {
static_assert(U != 0,"I cannot be 0 in my_template<T>::increase<I>");
return i + U;
}
template<int U>
static constexpr int decrease(int i) {
static_assert(U != 0,"I cannot be 0 in my_template<T>::decrease<I>");
return i - U;
}
// ...
T _t;
// ...
};
#endif // EOF
Trate de imaginar que el código es lo suficientemente grande y complejo como para que no pueda simplemente examinarlo y seleccionar el static_assert
y asegurarse de que sabe por qué están allí y que cumplen sus propósitos de diseño. Usted confía en las pruebas de regresión.
Aquí hay un conjunto de prueba de regresión de juguetes para my_template.h
:
test.cpp
#include "exceptionalized_static_assert.h"
#include "my_template.h"
#include <iostream>
template<typename T, int I>
struct a_test_template
{
a_test_template(){};
my_template<T> _specimen;
//...
bool pass = true;
};
template<typename T, int I>
struct another_test_template
{
another_test_template(int i) {
my_template<T> specimen;
auto j = specimen.template increase<I>(i);
//...
(void)j;
}
bool pass = true;
};
template<typename T, int I>
struct yet_another_test_template
{
yet_another_test_template(int i) {
my_template<T> specimen;
auto j = specimen.template decrease<I>(i);
//...
(void)j;
}
bool pass = true;
};
using namespace std;
int main()
{
unsigned tests = 0;
unsigned passes = 0;
cout << "Test: " << ++tests << endl;
a_test_template<int,0> t0;
passes += t0.pass;
cout << "Test: " << ++tests << endl;
another_test_template<int,1> t1(1);
passes += t1.pass;
cout << "Test: " << ++tests << endl;
yet_another_test_template<int,1> t2(1);
passes += t2.pass;
#if TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1
try {
// Cannot instantiate my_template<T> with non-POD T
using type = a_test_template<int,0>;
cout << "Test: " << ++tests << endl;
a_test_template<type,0> specimen;
}
catch(test::exceptionalized_static_assert const & esa) {
++passes;
cout << esa.what() << endl;
}
try {
// Cannot call my_template<T>::increase<I> with I == 0
cout << "Test: " << ++tests << endl;
another_test_template<int,0>(1);
}
catch(test::exceptionalized_static_assert const & esa) {
++passes;
cout << esa.what() << endl;
}
try {
// Cannot call my_template<T>::decrease<I> with I == 0
cout << "Test: " << ++tests << endl;
yet_another_test_template<int,0>(1);
}
catch(test::exceptionalized_static_assert const & esa) {
++passes;
cout << esa.what() << endl;
}
#endif // TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1
cout << "Passed " << passes << " out of " << tests << " tests" << endl;
cout << (passes == tests ? "*** Success :)" : "*** Failure :(") << endl;
return 0;
}
// EOF
Puede compilar test.cpp
con al menos gcc 6.1, clang 3.8 y option -std=c++14
, o VC ++ 19.10.24631.0 y option /std:c++latest
. Hazlo primero sin definir TEST__EXCEPTIONALIZE_STATIC_ASSERT
(o definiéndolo = 0). Luego ejecute y la salida debería ser:
Test: 1
Test: 2
Test: 3
Passed 3 out of 3 tests
*** Success :)
Si luego repite, pero compile con -DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1
,
Test: 1
Test: 2
Test: 3
Test: 4
static_assert would fail with reason: T must be POD in my_template<T>
Test: 5
static_assert would fail with reason: I cannot be 0 in my_template<T>::increase<I>
Test: 6
static_assert would fail with reason: I cannot be 0 in my_template<T>::decrease<I>
Passed 6 out of 6 tests
*** Success :)
Claramente, la codificación repetitiva de bloques de try/catch
en los casos de prueba de afirmación estática es tediosa, pero en el marco de un marco de prueba de unidad real y respetable uno esperaría que empaquetara aparatos de prueba de excepción para generar tales cosas fuera de su vista . En googletest, por ejemplo, puede escribir el me gusta de:
TYPED_TEST(t_my_template,insist_non_zero_increase)
{
ASSERT_THROW(TypeParam::template increase<0>(1),
exceptionalized_static_assert);
}
Ahora puedo volver a mis cálculos de la fecha de Armageddon :)
Estoy escribiendo un conjunto de pruebas de unidad para una biblioteca de código fuente que contiene static_assert
s. Quiero ofrecer seguridad de que estos static_assert
s no hagan más ni menos de lo que se desea hacer, en términos de diseño. Entonces me gustaría poder probarlos.
Por supuesto, podría agregar pruebas unitarias no disponibles de la interfaz que causan que las afirmaciones static assert
sean violadas por una amplia variedad de medios, y comente o #if 0
todas ellas, con mi seguridad personal para el usuario de que si alguna de ellas es sin comentar, observarán que la biblioteca no compila.
Pero eso sería bastante ridículo. En su lugar, me gustaría tener algunos aparatos que, en el contexto del conjunto de pruebas unitarias, reemplacen un static_assert
con una excepción de tiempo de ejecución provocada de manera equivalente, que el marco de prueba podría detectar e informar en vigencia: Este código tendría static_assert
ed en una construcción real.
¿Estoy pasando por alto alguna razón evidente por la cual esta sería una idea tonta?
Si no, ¿cómo podría hacerse? El aparato macro es un enfoque obvio y no lo descarto. Pero tal vez también, y preferiblemente, con una plantilla de especialización o enfoque SFINAE?