Estoy escribiendo un analizador sintético y, como parte de eso, tengo una Expander
clase que "expande" un enunciado complejo simple en múltiples enunciados simples. Por ejemplo, expandiría esto:
x = 2 + 3 * a
dentro:
tmp1 = 3 * a
x = 2 + tmp1
Ahora estoy pensando en cómo evaluar esta clase, específicamente cómo organizar las pruebas. Podría crear manualmente el árbol de sintaxis de entrada:
var input = new AssignStatement(
new Variable("x"),
new BinaryExpression(
new Constant(2),
BinaryOperator.Plus,
new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));
O podría escribirlo como una cadena y analizarlo:
var input = new Parser().ParseStatement("x = 2 + 3 * a");
La segunda opción es mucho más simple, más corta y legible. Pero también introduce una dependencia Parser
, lo que significa que un error Parser
podría fallar en esta prueba. Entonces, la prueba dejaría de ser una prueba unitaria de Expander
, y supongo que técnicamente se convierte en una prueba de integración de Parser
y Expander
.
Mi pregunta es: ¿está bien confiar principalmente (o completamente) en este tipo de pruebas de integración para probar esta Expander
clase?
Parser
pueda fallar en alguna otra prueba no es un problema si habitualmente comete solo cero fallas, por el contrario, significa que tiene más coberturaParser
. De lo que preferiría preocuparme es que un errorParser
podría hacer que esta prueba tenga éxito cuando debería haber fallado . Las pruebas unitarias están ahí para encontrar errores, después de todo, una prueba se rompe cuando no es así, pero debería haberlo hecho.Respuestas:
Te encontrarás escribiendo muchas más pruebas, de un comportamiento mucho más complicado, interesante y útil, si puedes hacerlo simplemente. Entonces la opción que involucra
Es bastante válido. Depende de otro componente. Pero todo depende de docenas de otros componentes. Si te burlas de algo a menos de una pulgada de su vida útil, probablemente dependas de muchas características de burla y accesorios de prueba.
Los desarrolladores a veces se centran demasiado en la pureza de sus pruebas unitarias , o desarrollan pruebas unitarias y pruebas unitarias únicamente , sin ningún módulo, integración, estrés u otro tipo de pruebas. Todos esos formularios son válidos y útiles, y todos son responsabilidad de los desarrolladores, no solo preguntas y respuestas o del personal de operaciones más adelante.
Un enfoque que he usado es comenzar con estas ejecuciones de nivel superior, luego usar los datos producidos a partir de ellas para construir la expresión de forma larga, de mínimo común denominador de la prueba. Por ejemplo, cuando volca la estructura de datos del
input
producto anterior, puede construir fácilmente:tipo de prueba que prueba al nivel más bajo. De esa forma, obtienes una buena combinación: un puñado de las pruebas primitivas más básicas (pruebas unitarias puras), pero no has pasado una semana escribiendo pruebas en ese nivel primitivo. Eso le brinda el recurso de tiempo necesario para escribir muchas más pruebas atómicas, un poco menos, utilizando
Parser
como ayudante. Resultado final: más pruebas, más cobertura, más esquinas y otros casos interesantes, mejor código y mayor garantía de calidad.fuente
¡Por supuesto que está bien!
Siempre necesita una prueba funcional / de integración que ejercite la ruta completa del código. Y la ruta de código completa en este caso significa incluir la evaluación del código generado. Es decir, prueba que el análisis
x = 2 + 3 * a
produce código que si se ejecuta cona = 5
se estableceráx
en17
y si se ejecuta cona = -2
se estableceráx
en-4
.Debajo de esto, debe hacer pruebas unitarias para bits más pequeños siempre que realmente ayude a depurar el código . Las pruebas más finas que tendrá, la mayor probabilidad de que cualquier cambio en el código también necesite cambiar la prueba, porque la interfaz interna cambia. Dicha prueba tiene poco valor a largo plazo y agrega trabajo de mantenimiento. Entonces hay un punto de rendimientos decrecientes y debes detenerte antes.
fuente
Las pruebas unitarias le permiten señalar elementos específicos que se rompen y en qué parte del código se rompieron. Entonces son buenos para pruebas de grano muy fino. Las buenas pruebas unitarias ayudarán a disminuir el tiempo de depuración.
Sin embargo, según mi experiencia, las pruebas unitarias rara vez son lo suficientemente buenas como para verificar la operación correcta. Por lo tanto, las pruebas de integración también son útiles para verificar una cadena o secuencia de operaciones. Las pruebas de integración lo ayudan a realizar pruebas funcionales. Sin embargo, como señaló, debido a la complejidad de las pruebas de integración, es más difícil encontrar el lugar específico en el código donde se rompe la prueba. También tiene algo más de fragilidad porque las fallas en cualquier parte de la cadena harán que la prueba falle. Sin embargo, todavía tendrá esa cadena en el código de producción, por lo que probar la cadena real sigue siendo útil.
Lo ideal sería tener ambos, pero en cualquier caso, generalmente es mejor tener una prueba automatizada que no tenerla.
fuente
Realice muchas pruebas en el analizador y, a medida que el analizador pase las pruebas, guarde esas salidas en un archivo para simular el analizador y probar el otro componente.
fuente