Test Unitarios en C#

En este post daremos un pequeño vistazo a qué son los test, empezando en los test unitarios.

Pero antes de empezar por un tipo de test en concreto, debemos entender Qué son los test y para qué sirven. 

 

 

1 - Qué son los test

 

Primero tenemos que tener clara la importancia que los test dan a nuestro software, ya que escribir tests, nos permitirá estar seguros de que nuestro código funciona, utilizando diferentes elementos de entrada y bajo diferentes circunstancias. 

 

1.1 -  Diferentes tipos de test

 

Cuando hablamos de test, se suelen representar con una imagen de una pirámide: 

 

estructura de test

 

Esta  pirámide puede cambiar, para simplificarlo, por ahora la he dividido en tres partes, que son las partes más comunes que se suelen testear

Primero, en la parte inferior, tenemos los: 

 

  • Test unitarios, los cuales van a testear métodos o servicios concretos de forma individual.
    • Este tipo de test es el que más vamos a escribir, y del tipo que trata este post.
  • Test de integración, los cuales probamos diferentes integraciones del sistema, procesos “semicompletos”, pero siempre simulando llamadas a aplicaciones externas o bases de datos.
    • Este tipo de tests los veremos más adelante. Un ejemplo puede ser, el proceso de creación de un usuario en un servicio, pero sin guardar ese usuario en la base de datos.
  • Test End to end, que hacen la prueba de un proceso completo, en el cual no simulamos llamadas a otros servicios o bases de datos.
    • Un ejemplo, como en el caso anterior, es la creación de un usuario, pero en este caso crearemos el usuario en la base de datos. 

Se utiliza una pirámide indicando que en la parte inferior disponemos de un mayor número de test que en el bloque de la parte superior. Lo mismo sucede para encontrar y arreglar errores, si encontramos un error durante un test unitario, será más fácil de arreglar que si lo encontramos durante los test de integración o de end to end.  

 

Idealmente deberíamos de tener todos estos test automatizados, y se puede hacer sin mucho problema, pero es común en las empresas hacer los test end to end de forma manual. 

 

 

2 - Por qué escribir test?

 

Como he indicado en el punto anterior, hay que tener claro que escribir test nos va a traer varias ventajas, entre las que entran:

  • saber que nuestro código funciona.
    • Debemos escribir tests para todos o la mayoría de casos posibles. para así asegurarnos que ese trocito de código no tiene bugs.
  • Mejorar la calidad de nuestro código, al asegurarnos que, si tenemos un test que pasa, si cambiamos algo en el código y este deja de funcionar, este test va a fallar.
  • Tener un mayor control de los errores, permitiéndonos ya sea filtrarlos antes para que no pasen o capturarlos y lanzar excepciones acordes, recuerda que hay un post de como capturar excepciones 

 

3 - Cómo crear test unitarios en C#

 

Cabe destacar que podemos crear test unitarios en cualquier parte de nuestro código, pero, vamos a seguir el sentido común y para ello utilizaremos, dentro del proyecto en el que estamos trabajando, uno nuevo que sea exclusivamente para tests.

 

para ello en visual studio pulsamos en archivo -> nuevo -> proyecto y seleccionamos un test.

 

 Como vemos hay tres tipos de tests para .NET Core

 

  •  MsTest
  • NUnit
  • xUnit

Los tres frameworks son muy similares, cambian algunas configuraciones, etc, que veremos en sus propios posts. 

 

Seleccionamos MsTest Test Project y veremos como visual studio nos crea un nuevo proyecto, el cual contiene una sola clase, la cual contiene nuestro test.

 

Nota:  Para el ejemplo de uso he creado una pequeña librería en C# que es una calculadora, únicamente, suma, resta, multiplica y divide. puedes ver un ejemplo más completo de test en la libreria para crear CSV que tengo en github  

 

 Por lo tanto la clase que vamos a testear es la siguiente:

 

public static class CalculadoraEjemplo
{
    public static int Suma(int item1, int item2)
    {
        return item1 + item2;
    }

    public static int Resta (int item1, int item2)
    {
        return item1 - item2;
    }

    public static int Multipliacion (int item1, int item2)
    {
        return item1 * item2;
    }
    public static double Division (int item1, int item2)
    {
        return item1 / item2;
    }
}

 

Y la clase que contiene los test, la cual se ha creado automaticamente cuando hemos creado el proyecto, es la siguiente:

 

[TestClass] 
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
    }
}

 

Como podemos observar por defecto nos crea una clase que va a ejecutar un test, y esto lo sabemos porque la clase contiene un atributo llamado [TestClass] y el método que va a ejecutar el test contiene un atributo llamado [TestMethod]

 

3.1 - Añadir la referencia del proyecto a testear

 

Cuando queremos testear un proyecto o una librería debemos añadir la referencia en el csproj de nuestro proyecto de test. en este caso las siguientes líneas:

 

<ItemGroup>
    <ProjectReference Include="..\Calculadora\Calculadora.csproj" />
</ItemGroup>

 

Otra opción es seleccionar en el proyecto de test, sobre las dependencias botón Secundario -> add reference

 

4 - Creación de un test unitario en C#

 

Cuando creamos un test, debemos tener en cuenta que debemos seguir cierta estructura para que nuestros test sean fáciles de entender por otros desarrolladores. 

 

Por convención existen unas “best practices” para los test que son un acrónimo llamado AAA cada una de las cuales significa lo siguiente:

 

  • Arrange -> Inicializamos las variables
  • Act -> Llamamos al método a testear
  • Assert - > Verificamos el resultado.

Y cuando escribimos el test, podemos observar el ejemplo

 

[TestMethod]
public void Test_Calcular_Suma()
{
    //Arrange : Inicializar las variables
    int sumando1 = 2;
    int sumando2 = 3;

    //Act : llamar al metodo a testear
    int resultado = CalculadoraEjemplo.Suma(sumando1, sumando2);

    //Assert: comprobar el valor con el esperado.
    Assert.Equals(5, resultado);
}

 

Como vemos, primero asignamos los valores, posteriormente llamamos al método de sumar y finalmente, comprobamos que suma es correcta.

 

La funcionalidad de los test no termina comprobando números, los Assert se van a tragar cualquier tipo de datos que le metamos, y disponemos de gran cantidad de opciones, por ejemplo .Null para comprobar si un elementos es null, o .True para comprobar si es verdadero.

 

4.1 - Comprobación de una excepción dentro de los test. 

 

También disponemos de la opción de comprobar excepciones, para ello es una forma un poco diferente, aunque varía si utilizamos MsTest, Nunit o xUnit. -Si no recuerdas lo que es una excepción por aquí dejo un link -

 

En nuestro caso con MsTest debemos de utilizar el atributo [ExpectedException] sobre el método, el cual va a capturar y comprobar la excepción, y en este caso, no nos hará falta un Assert. 

 

[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void Test_Calcular_Division()
{
    //Arrange : Inicializar las variables
    int dividendo = 120;
    int divisor = 0;

    //Act : llamar al metodo a testear
    double resultado = CalculadoraEjemplo.Division(dividendo, divisor);

    //Assert: comprobar el valor con el esperado.
    Assert.Equals(24, resultado);
}

 

 

5 - Code coverage o cobertura de código 

 

Como último punto, veremos que es el code coverage. ya que cuando programamos siempre nos estamos preguntando si hemos cubierto todo nuestro código con tests. 

 

 Ya que si, cubrimos nuestro código, sabemos al menos, que el curso normal de los acontecimientos pasará sin ningún error, aunque puede ser que salte alguna excepción, pero por eso son excepciones. 

 

Visual Studio contiene una herramienta para hacer cobertura de código, lo malo es que es en la versión enterprise, tenemos otras alternativas, pero todas son de pago, así que por ahora no veremos los resultados.

 

Antes de terminar con el code coverage, no hay que obsesionarse con tener todo testeado al 100%, sobre todo más adelante en test de integración veremos que si la lógica es muy compleja puede llevar mucho tiempo cubrir el 100% del código, por lo que en estos casos es más recomendable, gastar 8h para cubrir el 70% que gastar 40h en cubrir el 100%, aunque siempre habría que intentar tener cubierto ese 30% restante con pequeños test unitarios.  

 

Conclusión:

 

Crear tests en necesario en nuestro desarrollo, ya que no solo nos permite comprobar que el código que hemos escrito esta bien, sino, que en caso de romper otra funcionalidad, esta se verá reflejada en los test, y podremos ver que esa funcionalidad ya no funciona.

 

En proyectos grandes también sirven de gran ayuda, debido a que nos sera mas facil hacer el desarrollo utilizando test, que ir probando todo a mano, obviamente si es algo de interfaz de usuario no, pero si no lo es. Escribir el test que implementara nuestra funcionalidad es muy útil. A este tipo de programación se le llama, Test Driven Design

 

Que una empresa utilice test, es signo de buen hacer, así que hay que fiarse de las empresas que tienen tests. 

 

En otro post mas avanzado veremos como utlizar mock para simular llamadas a la base de datos o diferentes servicios.