2009-02-12 17 views
8

Estoy escribiendo pruebas de unidad para algunos de mis códigos y me he encontrado con un objeto con una pequeña interfaz expuesta pero estructuras internas complejas ya que cada método expuesto se ejecuta a través de un gran número de funciones internas incluyendo dependencias en el estado del objeto . Esto hace que los métodos en la interfaz externa sean bastante difíciles de probar por unidad.¿Deben probarse las unidades no públicas y cómo?

Mi pregunta inicial es, ¿debería apuntar a probar estas funciones internas también, ya que son más simples y, por lo tanto, más fáciles de escribir las pruebas? Mi instinto me dice que sí, lo que me lleva a la pregunta de seguimiento de si es así, ¿cómo haré esto en C++?

Las opciones que he propuesto son cambiar estas funciones internas de privadas a protegidas y usar una clase de amigo o una herencia para acceder a estas funciones internas. ¿Es este el mejor/único método de hacerlo preservando parte de la semántica de mantener ocultos los métodos internos?

+0

Posible duplicado de [¿Cómo puedo probar una clase que tiene métodos privados, campos o clases internas?] (Https://stackoverflow.com/questions/34571/how-do-i-test-a-class-that -has-private-methods-fields-or-inner-classes) – Raedwald

Respuesta

11

Respuesta corta: sí.

cuanto a la forma, vi una referencia de pasada en SO hace unos días:

#define private public 

en el código de prueba de la unidad evaluada antes de que las cabeceras relevantes son leídos ...
Asimismo para protegido.

Muy buena idea. respuesta


Ligeramente más largo: Prueba si el código no es correcto, obviamente, . Lo que significa esencialmente cualquier código que haga algo no trivial.


En consideración, me pregunto acerca de esto. No podrá enlazar con el mismo archivo objeto que usa en la compilación de producción. Ahora, las pruebas unitarias son un poco artificiales, así que quizás esto no sea un factor decisivo. ¿Alguien puede arrojar algo de luz sobre los pros y los contras de este truco?

+0

Tiene razón, probar * es * un caso especial, así que supongo que no puedo realmente quejarme acerca de "#define private public" en este caso. ¡Todavía así * se siente * no saludable! :) –

+0

Parece una buena idea. Funcionaría en entornos de desarrollador individuales, pero no estoy seguro de qué tipo de impacto tendría si/cuando las pruebas de la unidad se integran en el proceso de construcción de producción. – dlanod

+1

* Teóricamente *, dos clases que contienen los mismos miembros pero con diferentes especificadores de acceso pueden tener diferentes diseños de memoria, ya que la que tiene miembros privados no es "POD". Entonces, "#define private public" puede romper el ABI, lo que lleva a bloqueos, aunque lo dudo con cualquier compilador existente. –

0

Siempre se puede utilizar un interruptor de compilación en torno a la privada: como

#if defined (UNIT_TEST)

o con herramientas de cobertura de código de comprobar que su unidad de pruebas de sus funciones públicas ejercer plenamente los privados.

0

Sí, debería. Se llama prueba de caja blanca, esto significa que debe saber mucho sobre los aspectos internos del programa para probarlo correctamente.

Crearía 'stubs' públicos que llamen a las funciones privadas para las pruebas. #ifdef los talones para que pueda compilarlos cuando se complete la prueba.

2

Yo diría que use una herramienta de cobertura de código para verificar si estas funciones ya han sido probadas de alguna manera.

Teóricamente, si su API pública pasa todas las pruebas, las funciones privadas funcionan bien, siempre que se cubran todos los escenarios posibles. Ese es el problema principal, creo.

Sé que hay herramientas para trabajar con C/C++. CoverageMeter es uno de ellos.

+0

Usted plantea un buen punto. Si la interfaz pública pasa todas las pruebas, debería implicar que las funciones internas que estoy usando también lo hacen (suponiendo que mi selección de casos de prueba esté a punto). Sin embargo, parece que la resolución adicional sería útil para centrarse en dónde se produjo una falla de prueba. – dlanod

+0

Tenga en cuenta que la cobertura no garantiza la corrección. –

+0

Especialmente en presencia de plantillas: la cobertura es 100% necesaria, pero 0% suficiente. – Tom

2

A menos que usted está haciendo una biblioteca de propósito general que debe tratar de limitar lo que usted ha construido a lo que va a utilizar. Extiéndalo como lo necesite. Como tal, debe tener una cobertura de código completa, y debe probarlo todo.

Tal vez su código es un poco apestoso? ¿Es hora de refactorizar? Si tiene una clase grande que hace muchas cosas internamente, tal vez debería dividirse en varias clases más pequeñas con interfaces que podría probar por separado.

5

Mi opinión personal es que si probar la interfaz pública no es suficiente para probar adecuadamente los métodos privados, es probable que necesite para descomponer la clase adicional. Mi razonamiento es: los métodos privados deberían estar haciendo solo lo suficiente para admitir todos los casos de uso de la interfaz pública.

Pero mi experiencia con las pruebas unitarias es (desafortunadamente) delgada; si alguien puede proporcionar un ejemplo convincente en el que no se puede separar una gran parte del código privado, estoy dispuesto a reconsiderarlo.

15

Si el objeto está realizando operaciones muy complejas que son extremadamente difíciles de probar a través de la interfaz pública limitada, una opción es objeto de eliminar algo de esa lógica compleja en clases de utilidad que encapsulan tareas específicas. Luego puedes probar esas clases individualmente. Siempre es una buena idea organizar su código en trozos fácilmente digeribles.

+0

Ese es el camino a seguir. Como beneficio adicional, obtiene un código más modular y más fácil de mantener. –

5

Hay varios enfoques posibles. suponiendo que su clase es X:

  1. Sólo utilice la interfaz pública de X. Usted tendrá grandes problemas de configuración y pueden necesitar una herramienta de cobertura para asegurarse de que el código está cubierto, pero no hay trucos especiales involucrados.
  2. Usa el "#define private public" o un truco similar para vincularlo con una versión de X.o que esté expuesta a todos.
  3. Agregue un método público "estático X :: unitTest()". Esto significa que su código se enviará vinculado a su marco de prueba. (Sin embargo, una empresa en la que trabajó con utiliza esto para el diagnóstico a distancia del software.)
  4. Agregar "clase TestX" como un amigo de X. TestX no se envía en que la producción de DLL/EXE. Solo se define en su programa de prueba, pero tiene acceso a las partes internas de X.
  5. Otros ...
+0

Me gusta la opción 4. Es conforme al estándar y limita la violación de la encapsulación a una sola clase. Y si encuentra un problema en el campo, puede compilar una versión diferente de TestX y usar eso para echar un vistazo dentro del código liberado. –

3

Mi opinión es que no, por lo general deben no probarse directamente.

Las pruebas unitarias son una caja blanca, desde una perspectiva más alta del sistema, pero deben ser caja negra desde la perspectiva de la interfaz de clase probada (sus métodos públicos y su comportamiento esperado).

Así, por ejemplo, una clase de cadena (que no necesitaría legado char * soporte):

  • debe verificar que su método length() está trabajando correcly.
  • no debería tener que verificar que pone el carácter '\ 0' al final de su búfer interno. Este es un detalle de implementación.

Esto le permite refactorizar la aplicación casi sin tocar las pruebas más adelante
Esto ayuda a reducir el acoplamiento mediante la aplicación de responsabilidades clase
Esto hace que sus pruebas más fáciles de mantener

La excepción es para bastante complejo métodos de ayuda que le gustaría verificar más a fondo.
Pero entonces, esto puede ser un indicio de que este fragmento de código debe ser "oficial" al hacerlo público estático o extraído en su propia clase con sus métodos públicos.

+0

Totalmente de acuerdo. ¡Las pruebas deben ser mantenibles también! –

2

Siempre he pensado que esto tenderá a encajar si se usa el desarrollo impulsado por pruebas. Hay dos formas de abordar el desarrollo, ya sea que empiece con su interfaz pública y escriba una nueva prueba antes de cada adición a los métodos privados complejos o empiece a trabajar en cosas complejas como públicas y luego refactorice el código para que los métodos sean privados y las pruebas que ya ha escrito para usar la nueva interfaz pública. De cualquier manera, debe obtener cobertura completa.

Por supuesto, nunca he logrado escribir toda una aplicación (o incluso clase) de una manera estricta tdd y la refacturación de cosas complejas en las clases de utilidad es el camino a seguir si es posible.

0

Puede que le resulte productivo escribir un programa de prueba. En el programa de prueba, crea una clase que use la clase para probarla como base.

Agregue métodos a su nueva clase, para probar las funciones que no están visibles en la interfaz pública. Haga que su programa de prueba simple, llame a estos métodos para validar las funciones que le preocupan.

Cuestiones relacionadas