Pruebas unitarias C ++: ¿Qué probar?

20

TL; DR

Escribir buenas y útiles pruebas es difícil y tiene un alto costo en C ++. ¿Pueden los desarrolladores experimentados compartir su razonamiento sobre qué y cuándo probar?

Larga historia

Solía ​​hacer un desarrollo basado en pruebas, de hecho todo mi equipo, pero no funcionó bien para nosotros. Tenemos muchas pruebas, pero nunca parecen cubrir los casos en que tenemos errores y regresiones reales, que generalmente ocurren cuando las unidades interactúan, no por su comportamiento aislado.

Esto es a menudo tan difícil de probar en el nivel de la unidad que dejamos de hacer TDD (a excepción de los componentes donde realmente acelera el desarrollo), y en cambio invertimos más tiempo aumentando la cobertura de la prueba de integración. Si bien las pruebas de unidades pequeñas nunca detectaron ningún error real y fueron básicamente solo gastos generales de mantenimiento, las pruebas de integración realmente han valido la pena.

Ahora heredé un nuevo proyecto y me pregunto cómo probarlo. Es una aplicación nativa de C ++ / OpenGL, por lo que las pruebas de integración no son realmente una opción. Pero las pruebas unitarias en C ++ son un poco más difíciles que en Java (tienes que hacer cosas explícitamente virtual), y el programa no está muy orientado a objetos, por lo que no puedo burlarme de algunas cosas.

No quiero destrozar y OO-ize todo el asunto solo para escribir algunas pruebas en aras de escribir pruebas. Entonces te pregunto: ¿para qué debo escribir las pruebas? p.ej:

  • ¿Funciones / clases que espero cambiar con frecuencia?
  • ¿Funciones / clases que son más difíciles de probar manualmente?
  • ¿Funciones / clases que ya son fáciles de probar?

Comencé a investigar algunas bases de código respetuosas de C ++ para ver cómo funcionan las pruebas. En este momento estoy investigando el código fuente de Chromium, pero me resulta difícil extraer su lógica de prueba del código. Si alguien tiene un buen ejemplo o una publicación sobre cómo los usuarios populares de C ++ (chicos del comité, autores de libros, Google, Facebook, Microsoft, ...) abordan esto, sería de gran ayuda.

Actualizar

He buscado en este sitio y en la web desde que escribí esto. Encontré algunas cosas buenas:

Lamentablemente, todos estos son más bien centrados en Java / C #. Escribir muchas pruebas en Java / C # no es un gran problema, por lo que el beneficio generalmente supera los costos.

Pero como escribí anteriormente, es más difícil en C ++. Especialmente si su código base no es tan OO, tiene que estropear gravemente las cosas para obtener una buena cobertura de prueba de unidad. Por ejemplo: la aplicación que heredé tiene un Graphicsespacio de nombre que es una capa delgada sobre OpenGL. Para probar cualquiera de las entidades, que usan sus funciones directamente, tendría que convertir esto en una interfaz y una clase e inyectarlo en todas las entidades. Ese es solo un ejemplo.

Entonces, al responder esta pregunta, tenga en cuenta que tengo que hacer una inversión bastante grande para escribir pruebas.

futlib
fuente
3
+1 por la dificultad en las pruebas unitarias de C ++. Si su prueba unitaria requiere que cambie el código, no lo haga.
DPD
2
@DPD: No estoy tan seguro, ¿qué pasa si realmente vale la pena probar algo? En la base del código actual, apenas puedo probar nada en el código de simulación porque todo llama a las funciones gráficas directamente, y no puedo burlarme de ellas. Todo lo que puedo probar en este momento son funciones de utilidad. Pero estoy de acuerdo, cambiar el código para que sea "comprobable" se siente ... mal. Los defensores de TDD a menudo dicen que esto mejorará todo su código en todas las formas imaginables, pero estoy humildemente en desacuerdo. No todo necesita una interfaz y varias implementaciones.
futlib
Permíteme darte un ejemplo reciente: pasé un día entero tratando de probar una sola función (escrita en C ++ / CLI) y la herramienta de prueba MS Test siempre se bloqueaba para esta prueba. Parecía tener algún problema con las referencias simples de CPP. En cambio, solo probé la salida de su función de llamada y funcionó bien. Perdí todo un día para UT una función. Esa fue una pérdida de tiempo precioso. Además, no pude obtener ninguna herramienta de apriete adecuada a mis necesidades. Hice troquelado manual siempre que fue posible.
DPD
Ese es el tipo de cosas que me gustaría evitar: DI supongo que los desarrolladores de C ++ tenemos que ser especialmente pragmáticos sobre las pruebas. Terminaste probándolo, así que supongo que está bien.
futlib
@DPD: He pensado un poco más sobre esto, y creo que tienes razón, la pregunta es qué tipo de compensación quiero hacer. ¿Vale la pena refactorizar todo el sistema gráfico para probar un par de entidades? No había ningún error allí que conozca, así que probablemente: No. Si comienza a sentirse defectuoso, escribiré pruebas. Lástima que no puedo aceptar su respuesta porque es un comentario :)
futlib

Respuestas:

5

Bueno, las pruebas unitarias son solo una parte. Las pruebas de integración lo ayudan con el problema de su equipo. Las pruebas de integración se pueden escribir para todo tipo de aplicaciones, también para aplicaciones nativas y OpenGL. Debe consultar "Crecimiento de software orientado a objetos guiado por pruebas" por Steve Freemann y Nat Pryce (por ejemplo, http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Le guía paso a paso a través del desarrollo de una aplicación con GUI y comunicación de red.

Las pruebas de software que no se probaron son otra historia. Consulte Michael Feathers "Trabajando eficazmente con código heredado" (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

EricSchaefer
fuente
Conozco ambos libros. Las cosas son: 1. No queremos ir con TDD, porque no funcionó bien con nosotros. Queremos pruebas, pero no religiosamente. 2. Estoy seguro de que las pruebas de integración para aplicaciones OpenGL son posibles de alguna manera, pero requiere demasiado esfuerzo. Quiero seguir mejorando la aplicación, no comenzar un proyecto de investigación.
futlib
Tenga en cuenta que las pruebas unitarias "después del hecho" siempre son más difíciles que "probar primero", porque el código no está diseñado para ser comprobable (reutilizable, mantenible, etc.). Si quieres hacerlo de todos modos, intenta seguir los trucos de Michael Feathers (por ejemplo, costuras) incluso si evitas TDD. Por ejemplo, si desea extender una función, pruebe algo como "método de germinación" e intente mantener el nuevo método comprobable. Es posible hacerlo, pero más difícil en mi humilde opinión.
EricSchaefer
Estoy de acuerdo en que el código que no es TDD no está diseñado para ser comprobable, pero no diría que no es mantenible o reutilizable en sí mismo, como comenté anteriormente, algunas cosas simplemente no necesitan interfaces y múltiples implementaciones. No es un problema en absoluto con Mockito, pero en C ++, necesito hacer todas las funciones que quiero stub / mock virtual. De todos modos, el código no comprobable es mi mayor problema en este momento: tengo que cambiar algunas cosas fundamentales para hacer que algunas partes sean comprobables y, por lo tanto, quiero una buena justificación sobre qué probar, para asegurarme de que valga la pena.
futlib
Tienes razón, por supuesto, tendré cuidado de hacer que cualquier código nuevo que escriba sea comprobable. Pero no será fácil, con la forma en que funcionan las cosas en esta base de código en este momento.
futlib
Cuando agrega una característica / función, solo piense en cómo podría probarla. ¿Podría inyectar alguna dependencia fea? ¿Cómo sabrías que la función hace lo que se supone que debe hacer? ¿Podrías observar algún comportamiento? ¿Hay algún resultado que pueda verificar para su corrección? ¿Hay alguna invariante que pueda verificar?
EricSchaefer
2

Es una pena que TDD "no funcionó bien para usted". Creo que esa es la clave para entender hacia dónde acudir. Revise y comprenda cómo TDD no funcionó, qué podría haber hecho mejor, por qué hubo dificultades.

Entonces, por supuesto, sus pruebas unitarias no detectaron los errores que encontró. Ese es el tipo de punto. :-) No encontró esos errores porque evitó que ocurrieran en primer lugar al pensar cómo deberían funcionar las interfaces y cómo asegurarse de que se probaron correctamente.

Para responder, usted pregunta, como ha concluido, el código de prueba de unidad que no está diseñado para ser probado es difícil. Para el código existente, puede ser más efectivo usar un entorno de prueba funcional o de integración en lugar de un entorno de prueba unitaria. Pruebe el sistema en general enfocándose en áreas específicas.

Por supuesto, el nuevo desarrollo se beneficiará de TDD. A medida que se agregan nuevas características, la refactorización para TDD podría ayudar a probar el nuevo desarrollo, al tiempo que permite el desarrollo de nuevas pruebas unitarias para las funciones heredadas.

Bill Door
fuente
44
Hicimos TDD durante aproximadamente un año y medio, todos muy apasionados por eso. Sin embargo, al comparar los proyectos TDD con los anteriores realizados sin TDD (pero no sin pruebas), no diría que en realidad son más estables o tienen un código mejor diseñado. Tal vez sea nuestro equipo: emparejamos y revisamos mucho, la calidad de nuestro código siempre ha sido bastante buena.
futlib
1
Cuanto más lo pienso, más creo que TDD simplemente no encaja muy bien con la tecnología de ese proyecto en particular: Flex / Swiz. Hay muchos eventos, enlaces e inyecciones que hacen que las interacciones entre objetos sean complicadas y casi imposibles de realizar pruebas unitarias. Desacoplar esos objetos no lo hace mejor, porque en primer lugar funcionan correctamente.
futlib
2

No he hecho TDD en C ++, así que no puedo comentar sobre eso, pero se supone que debes probar el comportamiento esperado de tu código. Si bien la implementación puede cambiar, el comportamiento debería (¿generalmente?) Permanecer igual. En el mundo centrado en Java \ C #, eso significaría que solo prueba los métodos públicos, escribe pruebas para el comportamiento esperado y lo hace antes de la implementación (que generalmente es mejor decirlo que hacerlo :)).

Dante
fuente