c# Principios SOLID – Caso práctico: Módulo de seguridad Unit test + Code Coverage Parte 3

Módulo Seguridad parte3
Módulo Seguridad parte3

Artículos relacionados

En la parte1 y parte2 vimos la construcción del ejemplo del módulo de seguridad aplicando los principios SOLID. Continuando con esta serie de artículos ahora veremos cómo definir una estrategia para crear pruebas unitarias al código que ya fue desarrollado. Primero empezaremos calculando la complejidad ciclomática y luego crearemos paso a paso las pruebas unitarias necesarias para poder abarcar todo el código en este ejemplo. Por ahora solo aplicaremos Test-Last y no Test-First.

NOTA: Quiero dejar claro que acá no estamos aplicando TDD, en otros artículos hablare al respeto acerca de cómo poder aplicar esta práctica.

Para descargar el código haz clic acá.

Prueba ServicioAutenticacion

Análisis de complejidad ciclomática

Hacemos clic derecho en el proyecto Core y seleccionar la opción Analyze -> Calculate Code Metrics. Obtenemos el siguiente resultado:

Resultado Code Metrics Servicio Autenticación
Resultado Code Metrics Servicio Autenticación

Vemos que el metodo AutenticarUsuario tiene una complejidad cliclomática de 5.

public ResultadoAutenticacion AutenticarUsuario(Credencial credencial)
{
    Usuario usuario = 
        _repositorioConsultaUsuario.ObtenerPorNombre(credencial.Usuario);

    if (usuario == null)
    {
        return 
            CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.CredencialesIncorrectas);
    }

    if (ValidarContrasenaEsInvalida(usuario, credencial))
    {
        return
            CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.CredencialesIncorrectas);
    }
            
    if (usuario.Bloqueado)
    {
        return
            CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.UsuarioBloqueado);
    }
            
    return ValidarContrasenaSiExpiro(usuario) ? 
        CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.ContrasenaExpiro) : 
        new ResultadoAutenticacion { Autenticado = true, Usuario = usuario };
}

Por lo tanto se necesitan escribir 5 pruebas unitarias para que abarque el 100% del código de este método.

  • flujo normal, complejidad 1.
  • if (usuario == null), complejidad 1.
  • if (ValidarContrasenaEsInvalida(usuario, credencial)), complejidad 1.
  • if (usuario.Bloqueado), complejidad 1.
  • ValidarContrasenaSiExpiro(usuario) ? : ,  complejidad 1.
Prueba unitaria ServicioAutenticacion

Agregamos una clase de prueba llamada ServicioAutenticacionTest que contendrá las pruebas unitarias de la clase ServicioAutenticacion. Ahora desde el administrador de paquetes del Nuget agregamos la referencia al paquete Moq:

PM> Install-Package Moq

Nota: El Moq es un framework para .net que permite generar mocks para reemplazar objetos al momento de hacer pruebas unitarias.

Primera condición: if (usuario == null)

Ahora agregamos la primera prueba unitaria que contemple la condición if (usuario == null) o cuando el usuario no existe:

[TestMethod]
public void AutenticarUsuario_UsuarioNoExiste_RetornaCredencialesIncorrectas()
{
    //Arrange
    var repositorioConsultaUsuario = 
            new Mock<IRepositorioConsultaUsuario>();
    repositorioConsultaUsuario.Setup(x => 
            x.ObtenerPorNombre(It.IsAny<string>())).Returns((Usuario)null);

    var criptografia = new CriptografiaAes();
            
    //Act
    var servicioAutenticacion = 
        new ServicioAutenticacion(repositorioConsultaUsuario.Object, criptografia);
    var resultado = 
      servicioAutenticacion.AutenticarUsuario(
             new Credencial{Usuario = string.Empty});

    //Assert
    Assert.IsFalse(resultado.Autenticado);
    Assert.AreEqual(ErrorAutenticacion.CredencialesIncorrectas, resultado.Respuesta);
    Assert.IsNull(resultado.Usuario);
}

Abrimos el Test Explorer, escogemos la prueba  AutenticarUsuario_UsuarioNoExiste_RetornaCredencialesIncorrectas y seleccionamos la opción Analisys Code Coverage for Selected Tests.

Ejecutar Code Coverage
Ejecutar Code Coverage

Vemos que la cobertura del código es del 27.27%.

Code coverage autenticar usuario parte1
Code coverage autenticar usuario parte1

Hacemos doble clic sobre el método AutenticarUsuario para ver el código abarcado por las pruebas. En esta prueba solo hemos abarcado la condición if (usuario == null).

Code coverage usuario null
Code coverage usuario null
Segunda condición: if (ValidarContrasenaEsInvalida(usuario, credencial))

Ahora agregamos la prueba unitaria que contemple la condición if (ValidarContrasenaEsInvalida(usuario, credencial)) o cuando el usuario envió una contraseña invalida:

[TestMethod]
public void AutenticarUsuario_ContrasenaInvalida_RetornaCredencialesIncorrectas()
{
    //Arrange
    var repositorioConsultaUsuario = new Mock<IRepositorioConsultaUsuario>();
    repositorioConsultaUsuario.Setup(
        x => x.ObtenerPorNombre(It.IsAny<string>())).Returns(new Usuario
        {
            Contrasena = "123"
        });

    var criptografia = new CriptografiaAes();


    //Act
    var servicioAutenticacion = 
       new ServicioAutenticacion(repositorioConsultaUsuario.Object, criptografia);
    var resultado = 
      servicioAutenticacion.AutenticarUsuario(new Credencial {Contrasena = "1234"});

    //Assert
    Assert.IsFalse(resultado.Autenticado);
    Assert.AreEqual(ErrorAutenticacion.CredencialesIncorrectas, resultado.Respuesta);
    Assert.IsNull(resultado.Usuario);
}

Abrimos el Test Explorer  y seleccionamos la opción Analisys Code Coverage for All Tests.

Code coverage run all test
Code coverage run all test

Vemos que la cobertura del código es del 45.45%.

Code coverage autenticar usuario parte2
Code coverage autenticar usuario parte2

Hacemos doble clic sobre el método AutenticarUsuario para ver el código abarcado por las pruebas.

Code coverage contrasena incorrecta
Code coverage contrasena incorrecta
Tercera condición: if (usuario.Bloqueado)

Agregamos la prueba unitaria que contemple la condición if (usuario.Bloqueado) o cuando el usuario se encuentre bloqueado:

[TestMethod]
public void AutenticarUsuario_UsuarioBLoqueado_RetornaUsuarioBloqueado()
{
    //Arrange
    var repositorioConsultaUsuario = 
          new Mock<IRepositorioConsultaUsuario>();
    repositorioConsultaUsuario.Setup(
        x => x.ObtenerPorNombre(It.IsAny<string>())).Returns(new Usuario
        {
            Contrasena = "123",
            Bloqueado = true
        });

    var criptografia = new CriptografiaAes();
    
    //Act
    var servicioAutenticacion = 
          new ServicioAutenticacion(repositorioConsultaUsuario.Object, criptografia);
    var resultado = 
          servicioAutenticacion.AutenticarUsuario(new Credencial { Contrasena = "123" });

    //Assert
    Assert.IsFalse(resultado.Autenticado);
    Assert.AreEqual(ErrorAutenticacion.UsuarioBloqueado, resultado.Respuesta);
    Assert.IsNull(resultado.Usuario);
}

Seleccionamos la opción Analisys Code Coverage for All Tests y vemos que la cobertura del código es del 63.64%.

Code coverage autenticar usuario parte3
Code coverage autenticar usuario parte3

Hacemos doble clic sobre el método AutenticarUsuario para ver el código abarcado por las pruebas.

Code coverage usuario bloqueado
Code coverage usuario bloqueado
Cuarta condición: ValidarContrasenaSiExpiro(usuario) ? :

Ahora agregamos la prueba unitaria que contemple la condición ValidarContrasenaSiExpiro(usuario) ? : o cuando la contraseña del usuario expiro:

[TestMethod]
public void AutenticarUsuario_ContrasenaExpiro_RetornaContrasenaExpiro()
{
    //Arrange
    var repositorioConsultaUsuario = new Mock<IRepositorioConsultaUsuario>();
    repositorioConsultaUsuario.Setup(
        x => x.ObtenerPorNombre(It.IsAny<string>())).Returns(new Usuario
        {
            Contrasena = "123",
            Bloqueado = false,
            FechaUltimoCambioContrasena = DateTime.Now.AddDays(-30)
        });

    var criptografia = new CriptografiaAes();

    //Act
    var servicioAutenticacion = new ServicioAutenticacion(repositorioConsultaUsuario.Object, criptografia);
    var resultado = servicioAutenticacion.AutenticarUsuario(new Credencial { Contrasena = "123" });

    //Assert
    Assert.IsFalse(resultado.Autenticado);
    Assert.AreEqual(ErrorAutenticacion.ContrasenaExpiro, resultado.Respuesta);
    Assert.IsNull(resultado.Usuario);
}

Seleccionamos la opción Analisys Code Coverage for All Tests y vemos que la cobertura del código para ese método es del 81.82%.

Code coverage autenticar usuario parte4
Code coverage autenticar usuario parte4

Hacemos doble clic sobre el método AutenticarUsuario para ver el código abarcado por las pruebas.

Code coverage contrasena expiro
Code coverage contrasena expiro
Quinta condición: Flujo Éxito

Ahora agregamos la última prueba unitaria que contemple la condición exitosa donde el usuario se puede autenticar:

[TestMethod]
public void AutenticarUsuario_UsuarioValido_RetornaUsuarioAutenticado()
{
    //Arrange
    var repositorioConsultaUsuario = new Mock&lt;IRepositorioConsultaUsuario&gt;();
    repositorioConsultaUsuario.Setup(
        x =&gt; x.ObtenerPorNombre(It.IsAny&lt;string&gt;())).Returns(new Usuario
        {
            Contrasena = &quot;123&quot;,
            Bloqueado = false,
            FechaUltimoCambioContrasena = DateTime.Now.AddDays(-5)
        });

    var criptografia = new CriptografiaAes();

    //Act
    var servicioAutenticacion = new ServicioAutenticacion(repositorioConsultaUsuario.Object, criptografia);
    var resultado = servicioAutenticacion.AutenticarUsuario(new Credencial { Contrasena = &quot;123&quot; });

    //Assert
    Assert.IsTrue(resultado.Autenticado);
    Assert.IsNotNull(resultado.Usuario);
}

Seleccionamos la opción Analisys Code Coverage for All Tests y vemos que la cobertura del código es del 100.00%.

Code coverage autenticar usuario parte5
Code coverage autenticar usuario parte5

Hacemos doble clic sobre el metodo AutenticarUsuario para ver el código abarcado por las pruebas.

Code coverage exito
Code coverage exito

Para descargar el código haz clic acá.

Conclusión:

Las pruebas se pueden aplicar usando test first (TDD) o test last, en este caso aplicamos test last. Para aplicar un abarcamiento de pruebas al 100% debemos conocer la complejidad ciclomática de las clases, para lograr esto podemos usar el Visual Studio Code Metrics. Luego de conocer el valor de la complejidad ciclomática podemos activar el Visual Studio Code Coverage para saber luego de ejecutar las pruebas la cantidad de código abarcado.

Referencias:
Metal Tip:

Este artículo lo escribí escuchando la canción A slumber did my spirit seal de la banda Draconian de Suecia, les comparto el enlace.

Anuncios

2 comentarios en “c# Principios SOLID – Caso práctico: Módulo de seguridad Unit test + Code Coverage Parte 3

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s