Monday, January 23, 2023

Notas de testing de aplicaciones .NET con xUnit y Moq


Una deuda pendiente durante mi carrera profesional dentro de la Industria del Software ha sido dedicarle tiempo y esfuerzo a profundizar en temas de Calidad de Software. Por eso he decidido investigar sobre testing de aplicaciones .NET con xUnit y Moq. El objetivo es obtener los conocimientos necesarios para contribuir una estrategia de pruebas de un proyecto.

Intro a las pruebas de código

  • Dos aspectos a tener en cuenta cuando creas tus pruebas de código son: Organización y Optimización.
  • Prácticas de diseño de software que impactan en la mejora de las pruebas: SRP, DIP 
  • Las pruebas van en un proyecto independiente del código que se quiere probar.
  • Con las pruebas de código podemos intentar asegurar que tu proyecto hace lo que se espera de él.
  • Organización de las pruebas: Separar totalmente el código de producción del código de pruebas.
    • Carpeta /src para proyectos de código para producción
    • Carpeta /test para proyectos de código para pruebas
  • Cómo implementar las pruebas: AAA
    • Arrange (Aprovisiona/Organiza): Para crear escenario de prueba
    • Act (Actúa): Ejecutamos el código que queremos probar
    • Assert (Afirma): Comprobar los resultados obtenidos vs los esperados.

  • Las pruebas se lanzan a través de: 
    • El explorador de pruebas
    • La terminal --> dotnet test
    • Live Testing (ejeución en segundo plano) --> Con Visual Studio edición Enterprise
  • Convención para los nombres: NombreDelMétodo_QueDeberíaDevolver_Condiciones
    • Sumar_ShouldBe5_IfA3AndB2()
    • Par_ShouldBeTrue_IfA2()
    • Dividir_ShouldBe4_IfA8AndB2()
    • Dividir_ShouldThrowDivideByZeroException_IfA8AndB0()

xUnit.net

  • Con xUnit.net podemos realizar pruebas de código mediante hechos y teorías.
  • Fact (Hecho): Una prueba de código única que te permite probar unas condiciones concretas.
  • Theory (Teoría): Pruebas de código múltiples. Cada ejecución de una teoría se lleva a cabo de manera independiente.
  • Para que las pruebas sean útiles, no solo hay que probar el resultado, sino todos los caminos lógicos que pueda seguir el código. Esto incluye las excepciones y los retornos nulos.
  • Una aserción no es más que una comprobación que, en caso de no cumplirse, lanza una excepción. xUnit.net trae un conjunto de asersiones, pero también se puede agregar nuevas a través de librerías.
  • Alguna veces es necesario preparar el entorno para las pruebas, por ejemplo incluir ciertos archivos en una carpeta. Podemos hacer esto a través del propio constructor de la clase para preparar las pruebas, e implementar IDisposable para limpiar los recursos una vez que hayamos terminado. Todo este código auxiliar que estamos usando en el constructor lo podemos sacar a una clase independiente a través de los Fixture (Accesorios).
  • In xUnit, a test fixture is all the things we need to have in place in order to run a test and expect a particular outcome. Existen 2 tipos de Fixture: 
    • De clase --> IClassFixture<T>
    • De colección --> ICollectionFixture<T>
  • Con el accesorio de clase (ClassFixture) vamos a poder compartir funcionalidad entre todas las ejecuciones de las pruebas de una clase.
  • La forma de indicar a mi clase de prueba que necesita un accesorio es la siguiente:
  • Para tener un código auxiliar que sea transversal a la ejecución de varias clases de prueba usamos un accesorio de colección (ICollectionFixture<T>). Con una colección agrupamos diferentes clases de prueba dentro de una misma ejecución.
  • xUnit.net permite agrupar/organizar pruebas. Esto lo logramos a través de los rasgos, es decir, a través del uso del atributo [Trait].
  • Los rasgos nos van a permitir filtrar dentro del explorador de pruebas
  • Tambien podemos crear rasgos personalizados. Con estos podemos agrupar bugs, agrupar historias de trabajo, tipos de prueba, funcionalidad, etc.
  • xUnit.net ofrece la posibilidad de registrar los logs que nos interese, y se añadirán al resultado de la prueba, independientemente de si esta pasa o no.


Simulaciones con Moq

  • Un Mock es un objeto simulado: fake object, dummy object, simulated object...
  • Moq es un framework de mocking.
  • Los mock (objetos simulados) nos van a permitir sustituir elementos de nuestro código por otros cuya respuesta controlamos. Esto lo conseguimos reimplementamos la interfaz con lo que a nosotros nos interese.
  • El hecho de ineyectar las dependencias nos permite redefinir comportamientos para poder hacer pruebas.
  • Podemos simular código creando código auxiliar para poder simular los objetos en las pruebas, pero esto tiene limitaciones cuando la aplicación escala en tamaño y funcionalidades (mucho código extra para mantener).
  • Frameworks para crear objetos simulados: NSubstitute, Moq, RhinoMocks, Foq, etc
  • Cómo usar Moq: 1) crear un objeto tipo Mock<T>, 2) configurar el mock, 3) Reemplazar un objeto desde un mock.
  • Lo más habitual es crear estos mock en el constructor de la clase de pruebas o en un accesorio, ya que así los vamos a poder utilizar en todas las pruebas aunque, si tienes la seguridad de que sólo vas a necesitarlo en una prueba en concreto, puede ir dentro de ella, en el Arrange.

Temas varios

  • Tipos de pruebas
    • Unitarias: Probar una única clase. Reciben un mock de las cosas que necesitan para poder funcionar.
    • De integración: Verifican que la integración de las diferentes clases y servicios funcionan correctamente. En este tipo de pruebas es posible utilizar mocks para algunos niveles que todavía no se pueden probar, como por ejemplo un servicio de terceros.
    • Funcionales: Probar el funcionamiento completo del sistema en base a los requisitos que tiene.
  • TDD
    • Es una forma de desarrollo ligada a las pruebas de código.
    • Pasos
      • Escribir las pruebas sobre un requisito concreto
      • Hacer el código que haga pasar la prueba
      • Por último, se hace una refactorización para dejar el código limpio
    • TDD permite desarrollar funcionalidades de una manera muy sólida.
  • Cobertura de código
    • Es una métrica porcentual que nos da información sobre qué código se ha probado y cuál no.
    • Los criterios (tipos) de cobertura del código  más usados en un proyecto son: a) Cobertura de líneas (line coverage), b) Cobertura de ramas (branch coverage).
    • Herramientas para medir la cobertura de código: Visual Studio Enterprise, extensión Resharper, Rider IDE, etc.
    • Hay diferencias a la hora de generar un informe de cobertura de código en .NET Clásico y .NET
  • Probar código Legacy
    • Si se añade nueva funcionalidad, agregarle sus respectivo código de prueba
    • Si se tiene que modificar compartamiento de un método viejo, evaluar si es viable crear un conjunto de pruebas para asegurar que no se rompe nada de ese código en concreto.
    • Consejo: Al trabajar con código heredado se deben contener esas ansias de conseguir la perfección que nos caracteriza a los desarrolladores.

Integración Continua y testing

  • Detectar que nuestro código funciona en otros servidores además de nuestro entorno local.
  • La integración continua es la parte del desarrollo de software que se encarga de integrar cambios con mucha frecuencia para detectar fallos de manera rápida.
  • Con la mayor frecuencia posible se compile el código desde 0 y se pasen todas las pruebas que haya en la solución.
  • Problemas derivados del desarrollo en equipo:
    • El código no compile
    • Se haya roto alguna funcionalidad
  • Con la Integración Continua podemos detectar errores relativos a dependencias. 
  • Gracias a la CI podemos tener siempre a mano una versión compilada con todos los cambios del equipo de desarrollo unidos.
  • Herramientas para hacer CI: 
    • Online: Azure DevOps, GitHub Actions
    • On-Premise: Jenkins
  • La integración continua de nuestro proyecto se puede iniciar de diversas maneras: a través de un commit + push de Git, con un Webhook, programada a determinadas horas cada día...
  • Proceso general de Integración Continua:

Links

No comments:

Post a Comment

Cuando el código funciona, pero no tiene tests: ¿y ahora qué?

Seguramente te ha pasado alguna vez. Te dan acceso al repositorio de un nuevo proyecto. Lo abres con curiosidad, esperas encontrar una estru...