Pruebas unitarias sucintadas estrategias para pruebas unitarias

Los enfoques de prueba dependen de dónde se encuentre en el proyecto y de su "presupuesto", en términos de tiempo, dinero, mano de obra, necesidad, etc. Idealmente, las pruebas de unidad se incluyen en el proceso de desarrollo, pero de manera realista, a menudo nos encontramos con programas existentes o heredados. que tienen poca o ninguna cobertura de código, pero deben actualizarse o mantenerse. 

El peor escenario es un producto que se está desarrollando actualmente, pero muestra un mayor número de fallas durante su desarrollo, nuevamente con poca o ninguna cobertura de código. Como gerente de producto, ya sea al inicio de un esfuerzo de desarrollo o como resultado de recibir una aplicación existente, es importante desarrollar una estrategia de prueba de unidad razonable. 

Recuerde que las pruebas unitarias deben proporcionar beneficios mensurables a su proyecto para compensar la responsabilidad de su desarrollo, mantenimiento y sus propias pruebas. Además, la estrategia que adopte para la prueba de su unidad puede afectar la arquitectura de su aplicación. Si bien esto es casi siempre algo bueno, puede generar una sobrecarga innecesaria para sus necesidades..

A partir de los requisitos

Si está iniciando una aplicación suficientemente compleja desde una pizarra limpia, y todo lo que está en sus manos es un conjunto de requisitos, considere la siguiente guía.

Priorización de requisitos computacionales

Priorice los requisitos computacionales de la aplicación para determinar dónde reside la complejidad. La complejidad puede determinarse descubriendo el número de estados que un cómputo particular debe acomodar, o puede ser el resultado de un gran conjunto de datos de entrada requeridos para realizar el cálculo, o simplemente podría ser algorítmicamente complejo, como hacer un análisis de caso de falla en el anillo de redundancia de un satélite. También considere dónde es probable que el código cambie en el futuro como resultado de requisitos de cambio desconocidos. Si bien suena como que requiere clarividencia, un arquitecto de software capacitado puede clasificar el código en propósito general (resolver un problema común) y específico de dominio (resolver un problema de requisitos específicos). Este último se convierte en candidato para futuros cambios..

Si bien la escritura de pruebas unitarias para funciones triviales es fácil, rápida y gratificante en el número de casos de prueba que el programa realiza, son las pruebas menos rentables: toman tiempo para escribir y, ya que lo más probable es que se escriban correctamente para empezar, y lo más probable es que no cambien con el tiempo, son los menos útiles a medida que crece la base de código de la aplicación. En su lugar, enfoca tu estrategia de prueba de unidad en el código que es específico del dominio y complejo.

Seleccione una arquitectura

Una de las ventajas de iniciar un proyecto a partir de un conjunto de requisitos es que puede crear la arquitectura (o seleccionar una arquitectura de terceros) como parte del proceso de desarrollo. Marcos de terceros que le permiten aprovechar arquitecturas como la inversión de control (y el concepto relacionado de inyección de dependencias), así como arquitecturas formales como Model-View-Controller (MVC) y Model-View-ViewModel (MVVM) facilitan pruebas unitarias por la sencilla razón de que una arquitectura modular suele ser más fácil de realizar una prueba unitaria. Estas arquitecturas se separan:

  • La presentación (ver).
  • El modelo (responsable de la persistencia y representación de datos)..
  • El controlador (donde deberían estar ocurriendo los cálculos).

Si bien algunos aspectos del modelo pueden ser candidatos para pruebas de unidad, la mayoría de las pruebas de unidad probablemente se escribirán contra métodos en el controlador o modelo de vista, que es donde se implementan los cálculos en el modelo o vista..

Fase de mantenimiento

Las pruebas unitarias pueden ser beneficiosas incluso si está involucrado en el mantenimiento de una aplicación, una que requiere agregar nuevas funciones a una aplicación existente o simplemente corregir errores de una aplicación heredada. Hay varios enfoques que se pueden aplicar a una aplicación existente y las preguntas subyacentes a esos enfoques que pueden determinar la rentabilidad de las pruebas unitarias:

  • ¿Escribes pruebas unitarias solo para nuevas características y correcciones de errores? ¿Es la corrección de errores o la característica algo que se beneficiará de las pruebas de regresión, o es un problema aislado por única vez que se prueba más fácilmente durante las pruebas de integración??
  • ¿Comienzas a escribir pruebas unitarias contra características existentes? Si es así, ¿cómo prioriza qué características probar primero??
  • ¿La base de código existente funciona bien con la prueba de unidades o el código primero necesita refactorización para aislar las unidades de código??
  • Qué configuraciones o desmontes son necesarios para la prueba de características o errores?
  • Qué dependencias se pueden descubrir acerca de los cambios en el código que pueden resultar en efectos secundarios en otro código, y si las pruebas de la unidad se amplían para probar el comportamiento del código dependiente?

Entrar en la fase de mantenimiento de una aplicación heredada que carece de pruebas unitarias no es trivial: la planificación, la consideración y la investigación del código a menudo requieren más recursos que la simple solución del error. Sin embargo, el uso juicioso de las pruebas unitarias puede ser rentable, y si bien esto no siempre es fácil de determinar, vale la pena realizar este ejercicio, aunque solo sea para comprender mejor el código base..


Determine su proceso

Hay tres estrategias que se pueden tomar con respecto al proceso de prueba de unidad: "Desarrollo guiado por prueba", "Código primero" y, aunque pueda parecer antitético al tema de este libro, el proceso de "Prueba sin unidad".

Desarrollo guiado por pruebas

Un campamento es el "Desarrollo dirigido por pruebas", que se resume en el siguiente flujo de trabajo:

Dado un requisito computacional (vea la sección anterior), primero escriba un talón para el método.

  • Si se requieren dependencias de otros objetos que aún no están implementados (objetos pasados ​​como parámetros al método o devueltos por el método), implemente esos como interfaces vacías.
  • Si faltan propiedades, implemente apéndices para las propiedades que se necesitan para verificar los resultados.
  • Escriba cualquier requisito de configuración o prueba de desmontaje.
  • Escribe las pruebas. Las razones para escribir cualquier talón antes de las pruebas de escritura son: primero, para aprovechar IntelliSense al escribir la prueba; segundo, establecer que el código aún se compila; y tercero, para garantizar que el método que se está probando, sus parámetros, interfaces y propiedades se hayan sincronizado con respecto a la asignación de nombres.
  • Ejecutar las pruebas, verificando que fallan..
  • Codificar la implementación.
  • Ejecuta las pruebas, verificando que tengan éxito..

En la práctica, esto es más difícil de lo que parece. Es fácil caer en la redacción de pruebas que no son rentables y, a menudo, uno descubre que el método que se está probando no es una unidad lo suficientemente precisa como para ser un buen candidato para una prueba. Tal vez el método sea demasiado, requiera demasiada configuración o eliminación, o tenga dependencias en muchos otros objetos que todos deben inicializarse a un estado conocido. Estas son todas las cosas que se descubren más fácilmente al escribir el código, no la prueba.

Una ventaja de un enfoque basado en pruebas es que el proceso inculca la disciplina de las pruebas unitarias y la escritura de las pruebas unitarias primero. Es fácil determinar si el desarrollador está siguiendo el proceso. Con la práctica, uno puede volverse fácil al hacer que el proceso sea rentable..

Otra ventaja de un enfoque basado en pruebas es que, por su naturaleza, impone un tipo de arquitectura. Sería absurdo pero factible escribir una prueba unitaria que inicialice un formulario, ponga valores en un control y luego llame a un método que se espera que realice algún cálculo en los valores, como lo requeriría este código (en realidad se encuentra aquí):

private void btnCalculate_Click (objeto remitente, System.EventArgs e) double Principal, AnnualRate, InterestEarned; doble FutureValue, RatePerPeriod; int NumberOfPeriods, CompoundType; Principal = Double.Parse (txtPrincipal.Text); AnnualRate = Double.Parse (txtInterest.Text) / 100; if (rdoMonthly.Checked) CompoundType = 12; else if (rdoQuarterly.Checked) CompoundType = 4; else if (rdoSemiannually.Checked) CompoundType = 2; else CompoundType = 1; NumberOfPeriods = Int32.Parse (txtPeriods.Text); doble i = AnnualRate / CompoundType; int n = CompoundType * NumberOfPeriods; RatePerPeriod = AnnualRate / NumberOfPeriods; FutureValue = Principal * Math.Pow (1 + i, n); InterestEarned = FutureValue - Principal; txtInterestEarned.Text = InterestEarned.ToString ("C"); txtAmountEarned.Text = FutureValue.ToString ("C"); 

El código anterior no se puede probar, ya que está enredado con el controlador de eventos y la interfaz de usuario. Más bien, uno podría escribir el método de cálculo de interés compuesto:

public enum CompoundType Anualmente = 1, Semestralmente = 2, Trimestral = 4, Mensual = 12 privado CompoundInterestCalculation (doble principal, Double annual Rate, CompoundType compoundType, int periodos) double annualRateDecimal = annual Rate / 100.0; double i = annualRateDecimal / (int) compoundType; int n = (int) compoundType * periodos; double ratePerPeriod = annualRateDecimal / periodos; double futureValue = principal * Math.Pow (1 + i, n); double interestEaned = futureValue - principal; interés devuelto Eaned; 

lo que entonces permitiría que se escribiera una prueba simple:

[TestMethod] public void CompoundInterestTest () double interest = CompoundInterestCalculation (2500, 7.55, CompoundType.Monthly, 4); Assert.AreEqual (878.21, interés, 0.01); 

Además, al usar pruebas parametrizadas, sería sencillo probar cada tipo de compuesto, un rango de años y diferentes intereses y cantidades principales..

El enfoque de prueba en realidad facilita un proceso de desarrollo más formalizado mediante el descubrimiento de unidades reales comprobables y el aislamiento de las dependencias de cruce de límites.

Código Primero, Prueba Segundo

La codificación primero es más natural, aunque solo sea porque esa es la forma habitual en que se desarrollan las aplicaciones. El requisito y su implementación también pueden parecer bastante fáciles a primera vista, por lo que escribir varias pruebas unitarias parece un mal uso del tiempo. Otros factores, como los plazos, pueden forzar a un proyecto a un proceso de desarrollo "solo escriba el código para que podamos enviarlo".

El problema con el enfoque del código primero es que es fácil escribir código que requiere el tipo de prueba que vimos anteriormente. El código primero requiere una disciplina activa para probar el código que se ha escrito. Esta disciplina es increíblemente difícil de lograr, especialmente porque siempre hay la siguiente nueva característica para implementar.

También requiere inteligencia, si así lo desea, para evitar la escritura de códigos entrelazados, de cruce de límites y la disciplina para hacerlo. ¿Quién no ha hecho clic en un botón en el diseñador de Visual Studio y ha codificado el cómputo del evento allí mismo en el código auxiliar que Visual Studio crea para ti? Es fácil y debido a que la herramienta lo está orientando en esa dirección, el programador ingenuo pensará que esta es la forma correcta de codificación.

Este enfoque requiere una cuidadosa consideración de las habilidades y la disciplina de su equipo, y requiere un monitoreo más estrecho del equipo, especialmente durante los períodos de alto estrés, cuando los enfoques disciplinados tienden a romperse. Por supuesto, una disciplina basada en pruebas también se puede descartar a medida que se avecinan los plazos, pero eso tiende a ser una decisión consciente para hacer una excepción, mientras que puede convertirse fácilmente en la regla en un enfoque de código por primera vez.

No hay pruebas unitarias

El hecho de que no tenga pruebas unitarias no significa que esté desechando las pruebas. Simplemente puede ser que las pruebas enfaticen los procedimientos de pruebas de aceptación o las pruebas de integración..

Estrategias de prueba de equilibrio

Un proceso de prueba unitaria rentable requiere un equilibrio entre el desarrollo impulsado por la prueba, el código primero, la prueba segundo y las estrategias de "prueba de otra manera". Siempre se debe considerar la rentabilidad de las pruebas unitarias, así como factores como la experiencia de los desarrolladores en el equipo. Como gerente, es posible que no desee escuchar que un enfoque basado en pruebas es una buena idea si su equipo es bastante verde y necesita el proceso para inculcar disciplina y enfoque..