42

software Dada donde ...¿Debería uno probar la implementación interna, o solo probar el comportamiento público?

  • El sistema consiste en unos subsistemas
  • Cada subsistema consta de unos componentes
  • Cada componente se implementa utilizando muchas clases

... Me gusta escribir pruebas automatizadas de cada subsistema o componente.

No escribo una prueba para cada clase interna de un componente (excepto en la medida en que cada clase contribuye a la funcionalidad pública del componente y, por lo tanto, se puede probar/probar desde fuera a través de la API pública del componente).

Cuando refactorizo ​​la implementación de un componente (que a menudo hago, como parte de agregar nuevas funcionalidades), no es necesario alterar ninguna prueba automatizada existente: porque las pruebas solo dependen de la API pública del componente, y las API públicas suelen expandirse en lugar de modificarse.

Creo que esta política contrasta con un documento como Refactoring Test Code, que dice cosas como ...

  • "... la unidad de pruebas ..."
  • " ... una clase de prueba para cada clase en el sistema ... "
  • " ... prueba de razón de código de código/producción ... está muy bien considerado para acercarse a una proporción de 1: 1 ..."

... todos con lo cual supongo que no estoy de acuerdo (o al menos no practico hielo).

Mi pregunta es, si no está de acuerdo con mi política, ¿podría explicar por qué? ¿En qué escenarios es este grado de prueba insuficiente?

En resumen:

  • interfaces públicas son probados (y ensayarse), y rara vez cambian (que están añadido a, pero rara vez alterado)
  • API internos están ocultos detrás de las API públicas, y pueden ser cambiado sin volver a escribir los casos de prueba que ponen a prueba las API públicas

nota al pie: algunos de mis casos de prueba '' son en realidad implementado como datos. Por ejemplo, los casos de prueba para la IU consisten en archivos de datos que contienen varias entradas de usuario y las salidas del sistema esperadas correspondientes. Probar el sistema significa tener un código de prueba que lee cada archivo de datos, reproduce la entrada en el sistema y afirma que obtiene el resultado esperado correspondiente.

Aunque rara vez necesito cambiar el código de prueba (porque generalmente las API públicas se agregan en lugar de cambiar), creo que a veces (por ejemplo, dos veces por semana) necesito cambiar algunos archivos de datos existentes. Esto puede ocurrir cuando modifico la salida del sistema para mejor (es decir, la nueva funcionalidad mejora la salida existente), lo que puede causar que una prueba existente 'falle' (porque el código de prueba solo intenta afirmar que la salida no ha cambiado).Para hacer frente a estos casos hago lo siguiente:

  • Vuelva a ejecutar el conjunto de pruebas automatizado que una bandera en tiempo de ejecución especial, que le dice que no afirma la salida, pero en lugar de capturar la nueva salida en un nuevo directorio
  • Utilice una herramienta de diferencia visual para ver qué archivos de datos de salida (es decir, qué casos de prueba) han cambiado y para verificar que estos cambios sean correctos y esperados dada la nueva funcionalidad
  • Actualice las pruebas existentes copiando los nuevos archivos de salida del nuevo directorio en el directorio desde el cual se ejecutan los casos de prueba (sobrescribiendo las pruebas anteriores)

Nota: por "componente", me refiero a algo así como "una DLL" o "un ensamblaje" ... algo que es lo suficientemente grande para ser visible en una arquitectura o diagrama de despliegue del sistema, a menudo implementado usando docenas o 100 clases, y con una API pública que consta de solo 1 o un puñado de interfaces ... algo que se puede asignar a un equipo de desarrolladores (donde se asigna un componente diferente a un equipo diferente), y que por lo tanto, según Conway's Law que tiene una API pública relativamente estable.


Nota: El artículo Object-Oriented Testing: Myth and Reality dice,

Mito: las pruebas de caja Negro es suficiente. Si realiza un trabajo cuidadoso en el caso de prueba diseño utilizando la interfaz de clase o la especificación , puede estar seguro de que la clase se ha ejercido por completo. La prueba de caja blanca (mirando la implementación de un método para diseñar las pruebas ) viola el concepto de encapsulación .

Realidad: OO estructura importa, parte II. Muchos estudios han demostrado que de pruebas de recuadro negro piensa que son terriblemente a fondo por los desarrolladores único ejercicio de un tercio a un medio de los estados (y mucho caminos solos o estados) en la implementación bajo prueba. Hay tres razones para esto. En primer lugar, las entradas o los estados seleccionados normalmente tienen rutas normales , pero no fuerzan todas las rutas/estados posibles . En segundo lugar, las pruebas de caja negra por sí solas no pueden revelar sorpresas. Supongamos que hemos probado todos los comportamientos especificados del sistema bajo prueba. Para estar seguro de que hay no hay comportamientos no especificados que necesitamos saber si alguna parte del sistema tiene no ha sido ejercida por el paquete de caja negra . La única forma de obtener esta información es mediante el código instrumentación. En tercer lugar, a menudo es difícil de ejercer excepción y error-handling sin examen de el código fuente.

Debo agregar que estoy haciendo pruebas funcionales de whitebox: veo el código (en la implementación) y escribo pruebas funcionales (que dirigen la API pública) para ejercitar las diversas ramas de código (detalles de la característica implementación).

+0

Esto empieza a parecerse a un duplicado de http: // stackoverflow .com/questions/182325/why-are-functional-tests-not-enough-what-do-unit-tests-offer - verifique si esa pregunta aborda lo que está buscando. – darch

+0

@darch Sin duda está cerca si no es un duplicado; Gracias por mencionarlo. La respuesta aceptada en ese tema es que lo bueno de las pruebas unitarias es que son repetibles/automáticas: en mi caso, he automatizado mis pruebas funcionales para que sean repetibles. – ChrisW

Respuesta

14

Mi práctica es probar las partes internas a través de la API/UI pública. Si no se puede acceder a algún código interno desde el exterior, entonces refactorizaré para eliminarlo.

+0

¿Utiliza una herramienta de cobertura de código para descubrir el código interno que no puede ser alcanzado o que no está siendo alcanzado desde el exterior? Me pregunto cómo nació ese código. – ChrisW

+1

Ocurre algunas veces, tomemos el caso de excepciones que manejan bloques. La mayoría de ellos a veces pasan sin examen, por la misma razón. –

+0

@ChrisW: Dependiendo de la cantidad de esfuerzo que desee gastar en él, utilizo las pistas de depuración o gcov (que está integrado a Xcode). Sobre cómo nació ese código, es cierto que usar TDD me ayuda a no escribirlo. Pero a veces las funciones se eliminan o modifican. @Vinegar: Por lo general, trato de probar bloques de manejo de excepciones, al menos con un caso de prueba manual que ejecuto solo una vez. Si no puedo imaginar una situación para alcanzar ese código, tiendo a eliminarlo. – mouviciel

0

personalmente probar partes protegidas también, porque son "público" a los tipos heredados ...

+0

Me parece aplicable si mis piezas protegidas están siendo utilizadas por los clientes (por ejemplo, si estuviera enviando una biblioteca con piezas protegidas a otros programadores). – ChrisW

2

Si usted está practicando desarrollo basado en pruebas pura, después, sólo implementar cualquier código después de que haya cualquier prueba en su defecto, y solo implemente código de prueba cuando no tenga pruebas fallidas. Además, solo implemente lo más simple para realizar una prueba de falla o aprobación.

En la práctica limitada de TDD que he tenido he visto cómo esto me ayuda a eliminar pruebas unitarias para cada condición lógica producida por el código. No estoy del todo seguro de que el 100% de las funciones lógicas de mi código privado esté expuesto en mis interfaces públicas. La práctica de TDD parece complementaria a esa métrica, pero todavía hay funciones ocultas no permitidas por las API públicas.

Supongo que podría decir que esta práctica me protege contra futuros defectos en mis interfaces públicas. O lo encuentra útil (y le permite agregar nuevas funciones más rápidamente) o encuentra que es una pérdida de tiempo.

+0

Entendí los dos primeros párrafos, pero no la primera oración del tercer párrafo. – ChrisW

+0

Al realizar pruebas para todo mi código interno, estoy protegido cuando elijo usar más de ese código interno que al principio no está expuesto al público. Eso es lo que quiero decir con "defectos futuros". A medida que extiendo mi programa, es más probable que cubra casos internos que no fueron expuestos al principio. –

0

Estoy de acuerdo en que la cobertura del código debería ser idealmente del 100%. Esto no necesariamente significa que 60 líneas de código tendrían 60 líneas de código de prueba, pero que cada ruta de ejecución se prueba. Lo único más molesto que un error es un error que aún no se ha ejecutado.

Al solo probar la API pública, corre el riesgo de no probar todas las instancias de las clases internas. Realmente estoy diciendo lo obvio al decir eso, pero creo que debería mencionarse. Cuanto más se prueba cada comportamiento, más fácil es reconocer no solo que está roto, sino también lo que está roto.

+0

Usted dijo, "Esto no necesariamente significa que 60 líneas de código tendrían 60 líneas de código de prueba". La gente de la prueba * unidad * parece decir que cada clase debe tener las pruebas correspondientes ... mientras que tengo pruebas para colecciones de clases (es decir, para componentes/paquetes/ensamblajes/bibliotecas) ... las únicas clases para las que tengo pruebas son las clases públicas que definen la API externa. – ChrisW

+0

Encuentro que para agregar una nueva funcionalidad, necesito agregar un nuevo caso de prueba (para probar la nueva funcionalidad) y quizás editar una docena de clases existentes (para implementar la nueva funcionalidad). nótese bien que editar una docena de clases existentes * no * significa editar o crear una docena de casos de prueba (un caso de prueba por clase). – ChrisW

+0

No, solo edita esos casos de prueba que resultan rotos. No edite la prueba que no está rota. Y crear una docena de clases, de ninguna manera en nuestro caso ya estarían en su lugar. –

8

No tengo mi copia de Lakos en frente de mí, así que en lugar de citarlo me limitaré a señalar que él hace un mejor trabajo que el de explicar por qué las pruebas son importantes en todos los niveles.

El problema con la prueba de "comportamiento público" es que una prueba de este tipo proporciona muy poca información. Capturará muchos errores (al igual que el compilador detectará muchos errores), pero no puede decirle dónde están los errores. Es común que una unidad mal implementada devuelva buenos valores durante mucho tiempo y luego deje de hacerlo cuando cambian las condiciones; si esa unidad se hubiera probado directamente, el hecho de que se haya implementado mal se habría evidenciado antes.

El mejor nivel de granularidad de prueba es el nivel de la unidad. Proporcione pruebas para cada unidad a través de su (s) interfaz (es). Esto le permite validar y documentar sus creencias sobre cómo se comporta cada componente, lo que a su vez le permite probar el código dependiente al probar solo la nueva funcionalidad que presenta, lo que a su vez mantiene las pruebas cortas y segmentadas. Como beneficio adicional, realiza pruebas con el código que están probando.

Para decirlo de otra manera, es correcto probar solo el comportamiento público, siempre y cuando note que cada clase visible públicamente tiene un comportamiento público.

+0

Tiene toda la razón: agregué mi definición de 'componente' como nota al pie del OP. La definición de 'componente' de Lakos es 'un archivo fuente', que es mucho más pequeño que lo que estoy usando. Lo que quiero decir con 'componente' es posiblemente lo que Lakos llama un 'paquete'. – ChrisW

+0

Usted dijo que "probar solo el comportamiento público ... atrapará muchos errores (al igual que el compilador detectará muchos errores), pero no puede decirle dónde están los errores."Dos comentarios: 1) Cualquier error suele estar relacionado con lo que estoy editando en este momento y aún no lo he registrado (lo cual reduce mucho, dado que me registro con frecuencia). 2) Pruebas unitarias (de cada clase) no necesariamente ayudaría, porque un error a menudo no está en una clase, sino más bien en la interacción entre clases. – ChrisW

+0

Para eso tenemos pruebas basadas en la interacción. ¿No lo sabes? :). Mira esto. http://www.woodwardweb.com/programming/state_based_tes.html –

0

Pruebo los detalles de implementación privada, así como las interfaces públicas. Si cambio un detalle de implementación y la nueva versión tiene un error, esto me permite tener una mejor idea de dónde está realmente el error y no solo de qué está afectando.

30

La respuesta es muy simple: usted está describiendo las pruebas funcionales, que es una parte importante del control de calidad del software. La implementación interna de pruebas es una prueba unitaria, que es otra parte del control de calidad del software con un objetivo diferente. Es por eso que siente que la gente no está de acuerdo con su enfoque.

Las pruebas funcionales son importantes para validar que el sistema o subsistema hace lo que se supone que debe hacer. Todo lo que el cliente vea debe probarse de esta manera.

La prueba de la unidad está aquí para verificar que las 10 líneas de código que acaba de escribir hacen lo que se supone que debe hacer. Le da una mayor confianza en su código.

Ambos son complementarios. Si trabaja en un sistema existente, es probable que las pruebas funcionales funcionen primero. Pero tan pronto como agregue código, probarlo también es una buena idea.

+2

Cuando implemente un nueva característica, la ejercito (es decir, la implementación de la nueva característica) con una prueba funcional. Por qué/cuándo ¿podría ser una "buena idea probar también la unidad"? ¿No es suficiente una prueba funcional? ¿No es una prueba unitaria una pérdida de tiempo (por ejemplo, porque necesita ser modificada si la implementación se refactoriza)? Es bastante raro que escriba una prueba de unidad: una vez fue cuando tuve que ejercitar una clase que envolvió la fecha del sistema (donde no era conveniente hacer pruebas funcionales reales esperando a que cambie la fecha real del sistema). Además, si soy el que desarrolla dos componentes, ... – ChrisW

+0

... entonces tendré la oportunidad de probar los dos juntos (es decir, "pruebas de integración"): en lugar de crear un "simulacro" de cualquiera de ellos que lo haría déjame probar el otro por ti mismo. – ChrisW

+5

La prueba de unidad permite descubrir la fuente de un error de manera más precisa. Y no, no es una pérdida de tiempo, porque hay muchas cosas que no se pueden probar adecuadamente mediante pruebas funcionales, que todavía vale la pena probar. Típicamente, el error "difícil de simular" es muy útil para la prueba unitaria. Estoy hablando de todas esas funciones que devuelven NULL en lugar de un puntero válido, pérdida de conectividad de red, casos de archivo de configuración ilegible, ... Y sí, tiene que refactorizarlos junto con su código. –

8

Hasta ahora ha habido muchas respuestas excelentes a esta pregunta, pero quiero agregar algunas notas propias. Como prefacio: soy un consultor para una gran empresa que ofrece soluciones tecnológicas a una amplia gama de grandes clientes. Digo esto porque, en mi experiencia, estamos obligados a probar mucho más a fondo que la mayoría de las tiendas de software (salvo quizás los desarrolladores de API). Éstos son algunos de los pasos que seguir para garantizar la calidad:

  • Prueba de la unidad interna: Se espera que
    a los desarrolladores crear pruebas unitarias para todo el código que escriben (es decir: todos los métodos). Las pruebas unitarias deben cubrir condiciones de prueba positivas (¿funciona mi método?) Y condiciones de prueba negativas (¿el método arroja una ArgumentNullException cuando uno de mis argumentos requeridos es nulo?). normalmente incorporamos estas pruebas en el proceso de construcción utilizando una herramienta como CruiseControl.net
  • Sistema de Prueba/Test Asamblea:
    A veces este paso se llama algo diferente, pero esto es cuando comenzamos de probar las funciones públicas. Una vez que sepa que todas sus unidades individuales funcionan como se espera, desea saber que sus funciones externas también funcionan de la manera que usted cree que deberían. Esta es una forma de verificación funcional ya que el objetivo es determinar si todo el sistema funciona como debería. Tenga en cuenta que esto no incluye ningún punto de integración. Para la prueba del sistema, debe utilizar interfaces simuladas en lugar de las reales para que pueda controlar la salida y crear casos de prueba a su alrededor.
  • Prueba de integración del sistema:
    En esta etapa del proceso, desea conectar sus puntos de integración al sistema. Por ejemplo, si está usando un sistema de procesamiento de tarjetas de crédito, querrá incorporar el sistema en vivo en esta etapa para verificar que aún funcione. Desea realizar pruebas similares a la prueba de sistema/ensamblaje.
  • Prueba de verificación funcional:
    La verificación funcional corresponde a usuarios que utilizan el sistema o utilizan la API para verificar que funciona como se espera. Si ha creado un sistema de facturación, esta es la etapa en la que ejecutará sus scripts de prueba de principio a fin para asegurarse de que todo funcione como lo diseñó. Esta es obviamente una etapa crítica en el proceso, ya que te dice si ya has hecho tu trabajo.
  • Prueba de certificación:
    Aquí, pone a los usuarios reales frente al sistema y les permite probarlo. Lo ideal es que ya haya probado su interfaz de usuario en algún momento con sus partes interesadas, pero esta etapa le indicará si a su público objetivo le gusta su producto.Es posible que haya escuchado esto llamado algo así como un "candidato de lanzamiento" por otros proveedores. Si todo va bien en esta etapa, sabes que eres bueno para pasar a la producción. Las pruebas de certificación siempre deben realizarse en el mismo entorno que utilizará para la producción (o un entorno idéntico al menos).

Por supuesto, sé que no todo el mundo sigue este proceso, pero si lo mira de principio a fin, puede comenzar a ver los beneficios de los componentes individuales. No he incluido cosas como pruebas de verificación de compilación, ya que ocurren en una línea de tiempo diferente (por ejemplo, todos los días). Personalmente, creo que las pruebas unitarias son críticas, ya que le dan una idea profunda de qué componente específico de su aplicación está fallando en qué caso de uso específico. Las pruebas unitarias también lo ayudarán a aislar qué métodos funcionan correctamente para que no pierda tiempo mirándolos para obtener más información acerca de un error cuando no hay nada de malo en ellos.

Por supuesto, las pruebas unitarias también pueden ser incorrectas, pero si desarrolla sus casos de prueba a partir de su especificación funcional/técnica (tiene una, ¿verdad?;)), No debería tener demasiados problemas.

+2

Creo que nombraría estos pasos "prueba de unidad" (una unidad), "prueba de componente" (cada componente mayor), "prueba de integración" (varios componentes), "prueba de sistema" (sistema completo) y "prueba de aceptación" "(por el cliente y/o usuarios finales). – ChrisW

+0

ChrisW, siéntase libre de nombrarlos como mejor le parezca, por supuesto; los nombres que proporcioné son los nombres que usamos en nuestra compañía. He visto el ensamblaje/prueba del sistema intercambiados, pero sí. A fin de cuentas, es el concepto y la ejecución lo que nos importa. –

+1

Tal vez las pruebas unitarias no mejoren necesariamente la calidad final general del software: es más bien la razón principal para que las pruebas unitarias proporcionen pruebas * anteriores * (es decir, prueba precomponente y prueba previa a la integración). El software que no ha sido probado en unidades puede ser tan bueno como el software que fue probado en unidades: porque la cobertura de las pruebas funcionales puede ser tan buena (o incluso mejor) que la cobertura de las pruebas unitarias. Lo que las pruebas unitarias sí tienen efecto no es tanto la calidad del producto final, sino más el costo y la eficiencia del proceso de desarrollo. – ChrisW

0

[Una respuesta a mi propia pregunta]

Tal vez una de las variables que importa mucho es la cantidad de programadores diferente allí son la codificación:

  • axioma: cada programador debe probar su propio código

  • por lo tanto: si un programador escribe y una entrega "unidad", entonces también debería haber probado que la unidad, posiblemente escribiendo una "prueba de unidad"

  • Corolario: si un programador solo escribe un paquete completo, es suficiente que el programador escriba pruebas funcionales de todo el paquete (no es necesario escribir pruebas de unidad de unidad dentro del paquete, ya que esas unidades son detalles de implementación para qué otros programadores no tienen acceso directo/exposición).

Del mismo modo, la práctica de la construcción de componentes "falsas" que se puede probar en contra:

  • Si tiene dos equipos de la construcción de dos componentes, cada uno puede necesitar componente de "falsas" de otra manera que tienen algo (el simulacro) contra el cual probar su propio componente, antes de que su componente se considere listo para posteriores "pruebas de integración", y antes de que el otro equipo haya entregado su componente contra el cual su componente puede ser probado.

  • Si está desarrollando todo el sistema, puede hacer crecer todo el sistema ... por ejemplo, desarrollar un nuevo campo de GUI, un nuevo campo de base de datos, una nueva transacción comercial y una nueva prueba de sistema/funcional, todo como parte de una iteración, sin necesidad de desarrollar "burlas" de ninguna capa (ya que en su lugar puedes probar contra la realidad).

+0

Si tiene otra opción, debe usar "prueba de adversario". No quieres que el tipo que escribió el código lo pruebe; no puede ver los agujeros porque cree que funciona. Desea un probador imparcial o incluso antagónico para considerar posibles agujeros y escribir pruebas para verificar que esos casos no ocurran. –

+1

Ira: Estoy de acuerdo en que las "pruebas adversas" pueden ser valiosas, pero solo como un proceso posterior. Confiar en "pruebas de adversarios" es terriblemente derrochador a nivel de unidad/prueba de integración. La peor parte es que si el software está escrito sin tener en cuenta la capacidad de prueba, ¡es extremadamente difícil escribir el código de prueba para él!El ingeniero de software es absuelto de la responsabilidad de limpiar su propio código no comprobable y hace que el trabajo del probador sea una pesadilla. Encuentro que es mucho más productivo cuando el desarrollador escribe la mayor parte de las pruebas y un pase de "prueba de adversario" se trata más adelante (o una revisión del código). –

0

axioma: cada programador debe probar su propio código

No creo que esto es una verdad universal.

En criptografía, hay un dicho muy conocido: "es fácil crear un cifrado tan seguro que no sabes cómo romperlo tú mismo"."

En su proceso de desarrollo típico, escribe su código, luego compínelo y ejecútelo para verificar que haga lo que cree que es. Repita esto un montón de tiempo y se sentirá muy seguro acerca de su código.

su confianza le hará un probador menos vigilantes. el que no comparte tu experiencia con el código no tendrá el problema.

Además, un nuevo par de ojos puede tener menos prejuicios no sólo sobre el código de confiabilidad, pero también acerca de lo que hace el código. Como consecuencia, pueden surgir casos de prueba en los que el autor del código no haya pensado. Uno esperaría que descubran más errores o difundan conocimientos sobre qué hace el código alrededor de la organización un poco más.

Además, hay un argumento para hacer que para ser un buen programador hay que preocuparse por los casos extremos, pero para ser un buen probador se tiene que preocupar obsesivamente ;-) también, los probadores pueden ser más baratos, por lo que puede ser vale la pena tener un equipo de prueba por separado por esa razón.

Creo que la pregunta principal es esta: ¿qué metodología es la mejor para encontrar errores en el software? Hace poco vi un video (sin enlace, lo siento) que indica que las pruebas aleatorias son más baratas y efectivas que las pruebas generadas por humanos.

+0

No me refiero a que prueben su propio código * en lugar de que * alguien más lo pruebe: quiero decir, cuando están trabajando en un equipo de desarrolladores deben probar su propio código * antes * que alguien más lo pruebe ... en otras palabras, en un equipo no se puede verificar el código no probado que romperá la construcción e interferirá con el trabajo de otro desarrollador ... y otros componentes que necesita para las pruebas de integración pueden no existir aún ... y, la depuración los errores que se encuentran en la integración son más difíciles/costosos ... y, por lo tanto, cuanto más trabaje en un equipo, más importante puede ser hacer pruebas tempranas de unidades. – ChrisW

+0

Por el contrario, cuanto más coherente sea su visión del software, y mientras menos esté interfiriendo y dependiendo de otros desarrolladores, más podrá permitirse omitir las pruebas iniciales de la unidad y en su lugar tener solo pruebas de integración. – ChrisW

1

No debe pensar ciegamente que una unidad == una clase. Creo que puede ser contraproducente. Cuando digo que escribo una prueba unitaria, estoy probando una unidad lógica, "algo" que proporciona cierto comportamiento. Una unidad puede ser una sola clase, o pueden ser varias clases trabajando juntas para proporcionar ese comportamiento. A veces comienza como una sola clase, pero evoluciona para convertirse en tres o cuatro clases más tarde.

Si comienzo con una clase y escribo pruebas para eso, pero más tarde se convierte en varias clases, generalmente no escribiré pruebas separadas para las otras clases; son detalles de implementación en la unidad que se está probando. De esta manera, dejo crecer mi diseño y mis pruebas no son tan frágiles.

Solía ​​pensar exactamente como CrisW demonstartes en esta pregunta, que las pruebas en niveles superiores serían mejores, pero después de obtener algo más de experiencia mis pensamientos se moderan a algo entre eso y "cada clase debe tener una clase de prueba". Cada unidad debe tener pruebas, pero elijo definir mis unidades ligeramente diferentes de lo que hice una vez. Podrían ser los "componentes" de los que CrisW habla, pero a menudo también es una sola clase.

Además, las pruebas funcionales pueden ser suficientes para demostrar que su sistema hace lo que se supone que debe hacer, pero si desea controlar su diseño con ejemplos/pruebas (TDD/BDD), las pruebas de palanca inferior son una consecuencia natural . Podrías tirar esas pruebas de bajo nivel cuando hayas terminado la implementación, pero eso sería un desperdicio: las pruebas son un efecto secundario positivo. Si decides realizar refactorizaciones drásticas que invaliden tus pruebas de bajo nivel, las descartas y escribes una nueva.

Separar el objetivo de probar/probar su software, y usar pruebas/ejemplos para impulsar su diseño/implementación puede aclarar mucho esta discusión.

Actualización: Además, hay básicamente dos formas de hacer TDD: afuera adentro y adentro adentro. BDD promueve la entrada externa, lo que conduce a pruebas/especificaciones de mayor nivel. Sin embargo, si comienzas desde los detalles, escribirás pruebas detalladas para todas las clases.

+0

Cuando "muy a menudo también es una sola clase", ¿cuál es su motivo para realizar tal prueba? ¿Por qué no, en cambio, cubrir esta clase probando/ejerciendo la funcionalidad externamente visible que ayuda a implementar ('funcionalidad externamente visible' que significa pública/visible desde fuera del paquete del que una sola clase es más que un detalle de implementación)? – ChrisW

+0

Como dije, utilizo pruebas para controlar mi diseño/código. Si solo estuviera interesado en verificar el comportamiento de mis soluciones, las pruebas de alto nivel serían suficientes. Sin embargo, no me ayudan lo suficiente cuando implemento los detalles, por lo que la mayoría de las "responsabilidades" en el diseño obtienen sus propias pruebas. –

2

Puede codificar pruebas funcionales; esta bien. Pero debe validar el uso de la cobertura de prueba en la implementación, para demostrar que el código que se está probando tiene un propósito relativo a las pruebas funcionales, y que realmente hace algo relevante.

+0

¿Está diciendo que las pruebas funcionales no cubren la implementación y que, por lo tanto, debe haber pruebas adicionales (no funcionales?). ¿O está diciendo que debería verificar (tal vez usando una herramienta de cobertura de código como NCover) si la implementación está cubierta por las pruebas funcionales? – ChrisW

+0

Podría decirse que solo el código que tiene un propósito detectable en su función debe estar en su aplicación. Si no puede definir la funcionalidad que ejerce una parte del código, ¿cuál es el sentido de tener ese código en el sistema? (La FAA requiere lo que equivale a una cobertura de prueba del 100% en el software de la aeronave por este motivo). ¡Deberías usar una herramienta de cobertura de código! Y si no obtiene un nivel de cobertura lo suficientemente alto (no está construyendo aviones, es probable que el 100% no sea necesario), debe codificar pruebas más funcionales que ejercerán el código que no fue cubierto por otras pruebas. –

+0

Está diciendo que las pruebas funcionales pueden y deben proporcionar una cobertura suficiente del código, y que debería medir/probar qué parte del código está cubierto. Hablando de cobertura, es incluso más importante tener pruebas que cubran la funcionalidad que tener pruebas que cubran el código. Por ejemplo, podría escribir un programa de 10 líneas y una prueba que cubra ese 100%, pero eso sería insuficiente si ese programa no implementa toda la funcionalidad que se requiere. – ChrisW

1

Estoy de acuerdo con la mayoría de los puestos de aquí, sin embargo, me gustaría añadir:

No es una prioridad principal para probar interfaces públicas, entonces protegido, entonces privado.

Normalmente las interfaces públicas y protegidas son un resumen de una combinación de interfaces privadas y protegidas.

Personalmente: Debe probar todo. Dado un sólido conjunto de pruebas para funciones más pequeñas, se le dará una mayor confianza de que los métodos ocultos funcionan. También estoy de acuerdo con el comentario de otra persona sobre refactorización. La cobertura del código lo ayudará a determinar dónde están los bits adicionales de código y a refactorizarlos si es necesario.

0

Depende de su diseño y de dónde obtendrá el mayor valor. Un tipo de aplicación puede exigir un enfoque diferente a otro. A veces apenas se detecta algo interesante con pruebas unitarias, mientras que las pruebas funcionales/de integración producen sorpresas. A veces, las pruebas unitarias fallan cientos de veces durante el desarrollo, atrapando a muchos, muchos errores en la fabricación.

A veces es trivial. La forma en que algunas clases se juntan hace que el retorno de la inversión de probar cada ruta sea menos tentador, por lo que puede trazar una línea y pasar a martillear algo más importante/complicado/muy utilizado.

A veces no basta con probar la API pública porque hay una lógica particularmente interesante al acecho, y es demasiado doloroso poner el sistema en marcha y ejercitar esas rutas particulares. Eso es cuando probar las agallas de eso vale la pena.

Actualmente, tiendo a escribir numerosas, (a menudo extremadamente) clases simples que hacen una o dos cosas en la parte superior. Luego implemento el comportamiento deseado al delegar toda la funcionalidad complicada a esas clases internas. Es decir. Tengo interacciones un poco más complejas, pero clases realmente simples.

Si cambio mi implementación y tengo que refactorizar algunas de esas clases, generalmente no me importa. Mantengo mis pruebas aisladas lo mejor que puedo, por lo que a menudo es un simple cambio hacer que vuelvan a funcionar. Sin embargo, si yo hago tengo que tirar algunas de las clases internas, a menudo reemplazo un puñado de clases y escribo algunas pruebas totalmente nuevas en su lugar. A menudo escucho gente quejándose de tener que mantener las pruebas actualizadas después de la refactorización y, aunque a veces es inevitable y tedioso, si el nivel de granularidad es lo suficientemente bueno, generalmente no es un gran problema tirar algunas pruebas de código +.

Creo que esta es una de las principales diferencias entre el diseño para la capacidad de prueba y no molestar.

+0

¿Cuál es una de las principales diferencias? Y si estoy probando la funcionalidad (como las pruebas de aceptación), entonces creo que son los requisitos o la especificación funcional (en lugar del diseño o la implementación del código) los que deben ser verificables. – ChrisW

0

¿Todavía sigue este enfoque? También creo que este es el enfoque correcto. Solo debe probar las interfaces públicas. Ahora la interfaz pública puede ser un servicio o algún componente que recibe información de algún tipo de UI o cualquier otra fuente.

Pero debe poder evolucionar el servicio o componente principal utilizando el enfoque de Prueba Primero. es decir, defina una interfaz pública y pruébela para una funcionalidad básica. fallará Implementa esa funcionalidad básica usando clases de fondo API. Escriba API para satisfacer solo este último caso de prueba. Luego sigue preguntando qué puede hacer el servicio más y evolucionar.

La única decisión de equilibrio que se debe tomar es dividir el único servicio o componente en unos pocos servicios y componentes más pequeños que se pueden reutilizar. Si cree firmemente que un componente se puede reutilizar en los proyectos.Luego, se deben escribir pruebas automáticas para ese componente. Pero nuevamente, las pruebas escritas para el gran servicio o componente deben duplicar funcionalmente ya probado como un componente.

Ciertas personas pueden entrar en la discusión theorotical de que esto no es una prueba unitaria. Entonces eso está bien. La idea básica es tener pruebas automatizadas que prueben su software. Entonces, ¿qué pasa si no está a nivel de unidad. Si cubre la integración con la base de datos (que usted controla), entonces solo es mejor.

Quiero saber si usted ha desarrollado ningún buen proceso que funciona para you..since su primera entrada ..

respecto Ameet

+2

No estoy de acuerdo con que "solo deba probar las interfaces públicas". Digo que "debe probar interfaces públicas" y que "probar * interfaces privadas/internas * puede * no ser necesario". La prueba de unidades/componentes es útil, si otros componentes aún no existen, o si las pruebas del sistema son costosas, o si la corrección de errores durante la prueba de integración es difícil o requiere mucho tiempo. Además, de mi descripción de mi marco de prueba de regresión, verán que no estoy haciendo un desarrollo de prueba primero. – ChrisW

Cuestiones relacionadas