Unit Testing .Net¶
Pre-requisitos¶
Introducción¶
Para crear un Test en .Net debemos:
- Agregar un proyecto a la solución.
- Seleccionar el proyecto de UnitTest
En .Net podemos seleccionar el proyecto con xUnit o NUnit para realizar los test, son dos librerías que permiten realizar los test. Los ejemplos de esta documentación estan realizados con xUnit. Docs xUnit y NUnit.
- Referenciar el proyecto a testear
Basic¶
Para comenzar a realizar un test debemos crear una clase.
Los test no son otra cosa que funciones de una clase.
Para identificar un test debemos agregar el atributo [Fact]
, este es definido por la librería xUnit.
[Fact]
public void Account_ValidCustomerName()
{
}
Sugerimos esta nomenclatura:
metodo_escenario_resultado
Donde el
método
: es el metodo que se va a ejecutarescenario
: la condición que se va probarresultado
: el resultado esperado de la acción.
Ejemplo:
Credit_WithValidAmount_UpdatesBalance()
Debit_WithValidAmount_UpdatesBalance()
Handle_CreatePerson_Success()
Primer test¶
[Fact]
public void Account_ValidCustomerName()
{
// Arrange
double beginningBalance = 11.99;
string name = "Mr. Bryan Walton";
BankAccount account = new BankAccount(name, beginningBalance);
// Act
var result = account.CustomerName;
// Assert
Assert.Equal(name, result);
}
- Arrange: construimos el escenario, creamos un objeto account que será el que validaremos.
- Act: ejecutamos el método que queremos probar.
- Assert: Validamos la respuesta del método.
El Objeto Assert
es el objeto que nos permite validar nuestro test, debemos especificar que es lo que queremos validar y en caso que no se cumpla, Assert
se encargará de marcar el test como fail y de especificar el motivo del fallo.
Run Test
Para correr nuestro test en visual studio utilizaremos el Explorador de Pruebas
o Test Explorer
.
Con los botones podemos correr parcial o totalmente nuestros test e incluso debugear.
En caso que querramos correr en consola debemos ejecutar
dotnet test
FluentAssertions¶
Es una libreria que permite realizar las comprobaciones de una manera más visual.
Ejemplo:
[Fact]
public void Account_ValidCustomerName()
{
// Arrange
double beginningBalance = 11.99;
string name = "Mr. Bryan Walton";
BankAccount account = new BankAccount(name, beginningBalance);
// Act
var result = account.CustomerName;
// Assert
result.Should().Be(name);
}
Advanced¶
Theory¶
Es posible que se haya preguntado por qué sus primeras pruebas unitarias usan un atributo con nombre [Fact]
en lugar de uno con un nombre más tradicional como Test
. xUnit.net incluye soporte para dos tipos principales diferentes de pruebas unitarias: Fact y Theory. Al describir la diferencia entre hechos y teorías, nos gusta decir:
Los hechos son pruebas que siempre son verdaderas. Prueban condiciones invariantes.
Las teorías son pruebas que sólo son verdaderas para un conjunto particular de datos.
Un buen ejemplo de esto es probar algoritmos numéricos. Supongamos que desea probar un algoritmo que determina si un número es impar o no. Si está escribiendo las pruebas del lado positivo (números impares), entonces introducir números pares en la prueba haría que fallara, y no porque la prueba o el algoritmo sean incorrectos.
[Theory]
[InlineData(3)]
[InlineData(5)]
[InlineData(6)]
public void MyFirstTheory(int value)
{
Assert.True(IsOdd(value));
}
bool IsOdd(int value)
{
return value % 2 == 1;
}
Moq¶
Moq es una librería que permite generar objetos "Mock" que sustituyan a las dependencias de nuestra clase a probar. Puede repasar la documentación para ver estos conceptos.
Por Ejemplo, podemos generar un mock de las dependencias de un Handler
var _query = new Mock<IQuerySqlServer>();
var _handler = new ListPersonHandler(_query.Object);
ListPersonHandler
la dependencia de consultas a base de datos "moqueada"
También podemos manipular ese objeto Mock para que se comporte como necesitamos en el Test. Por ejemplo, podemos pedir que la función que busque en base de datos devuelva una lista de objetos que creamos con anterioridad.
Esto se consigue con la función Setup
del Mock.
Ejemplo:
[Fact]
public async Task Handle_GetListUser_Success()
{
// Arrange
var request = new ListPerson();
var resonse = new Fixture().CreateMany<PersonDto>();
// se configura el Mock
_query.Setup(_ => _.GetAllAsync<PersonDto>(It.IsAny<string>()))
.ReturnsAsync(resonse);
// Act
var result = await _handler.Handle(request, _cancellationToken);
// Assert
result.Content.Should().Equal(resonse);
result.StatusCode.Should().Be(HttpStatusCode.OK);
}
AutoFixture¶
AutoFixture es una librería que permite generar un objeto de prueba. Se recomienda usar cuando los objetos que debemos crear para preparar la prueba sean grandes o varios y no tenga validación del contenido del mismo ya que AutoFixture los genera con información inválida.
Otro ejemplo:
[Fact]
public async Task Handler_UpdatePerson_ThrowUpdateDatabase()
{
// Arrange
var request = new Fixture().Create<UpdatePersonCommand>();
var person = new Fixture().Create<Person>();
_query.Setup(_ => _.GetByIdAsync<Person>(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(person);
_repository.Setup(_ => _.SaveChangeAsync()).ThrowsAsync(new DbUpdateException());
// Act
// Assert
await Assert.ThrowsAsync<DbUpdateException>(() => _handler.Handle(request, _cancellationToken));
}
Para ver más funcionalidades sobre AutoFixture ver: Guide AutoFixture
Como Mockear el DbContext¶
Para testear nuestro servicio QuerySqlServer
y mockear el DbContext
, debemos crear una clase base con el nombre ServiceDbContextBaseTest
y utilizar la configuración de la base de datos en memoria de EF Core. Aquí tienes un ejemplo:
public class ServiceDbContextBaseTest
{
protected ApplicationDbContext _inMemoryDbContext;
public ServiceDbContextBaseTest()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.UseInMemoryDatabase(Guid.NewGuid().ToString("N"), b => b.EnableNullChecks(false)).Options;
_inMemoryDbContext = new(options);
_inMemoryDbContext.Database.EnsureDeleted();
_inMemoryDbContext.Database.EnsureCreated();
}
}
Una vez que hemos configurado el contexto, heredamos de ServiceDbContextBaseTest
y estamos listos para testear nuestras funciones. En este ejemplo, vamos a testear el método GetPersonByNameAsync
. Creamos un conjunto de datos, añadimos y guardamos los cambios con el contexto y validamos los datos. Aquí tienes un ejemplo:
public class QuerySqlServerTest : ServiceDbContextBaseTest
{
private readonly Mock<IReadOnlyQueryConfiguration> _config;
private readonly QuerySqlServer _query;
public QuerySqlServerTest() : base()
{
_config = new();
_query = new(_config.Object, _inMemoryDbContext);
}
[Fact]
public async Task GetPersonByNameAsync_Returns_Person_By_Name()
{
// Arrange
List<Person> people = [
new Person
{
PersonId = 4,
Apellido = "Messi",
Nombre = "Lionel"
},
new Person
{
PersonId= 5,
Apellido= "Son",
Nombre= "Goku"
}
];
_inMemoryDbContext.Person.AddRange(people);
await _inMemoryDbContext.SaveChangesAsync();
// Act
var result = await _query.GetPersonByNameAsync("Lionel");
// Assert
Assert.NotNull(result);
Assert.Equal(people[0], result);
}
}
CodeCoverage¶
Platform¶
La herramienta platform-cli está equipada con una funcionalidad integrada que facilita el análisis automático de la cobertura de código (code coverage). Ver más sobre el funcionamiento y compatibilidad
Ejemplo basico¶
- Abrimos una terminal.
- Nos posicionamos en nuestra carpeta root o la que posee el archivo *.sln (Solucion)
- Ejecutamos
platform test
Obtendremos el siguiente resultado:
Configuración Manual¶
Si no contamos con compatibilidad con platform-cli podemos hacer la configuración manual en nuestros proyectos.
Configuración¶
Install ReportGenerator
ReportGenerator es una herramienta de código abierto utilizada en el desarrollo de software para convertir informes de cobertura de código de varias herramientas de prueba en informes legibles y comprensibles. Es ampliamente utilizada en entornos de integración continua y para análisis detallados de la cobertura de pruebas.
dotnet tool install -g dotnet-reportgenerator-globaltool
Packages Test
Devemos incorporar a nuestros projectos de test el nuget coverlet.msbuild
dotnet add package coverlet.msbuild --version 6.0.0
Configurar .gitignore
Agregar las siguientes exclusiones al archivo .gitignore
# coverage
*coverage.opencover.xml
*coverage.cobertura.xml
**/coveragereport/*
Run test¶
El siguiente comando ejectua los test y genera los archivos de cobertura.
dotnet test --collect:\"XPlat Code Coverage\" /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura
Generar el reporte¶
Generamos el reporte
reportgenerator -reports:test\*\*.cobertura.xml
-targetdir:coveragereport
-reporttypes:Html
-classfilters:-WebApi.Controllers.*;-Startup;-Program;-*.Domain.Entities.*;-*Dto*;-*DependencyInjection;-*Context;-*.Infrastructure.Migrations.*;-*.Infrastructure.Persistence.Configurations.*;-*Entity;-*Event;-*Vm;-*Exception
index.html
ubicado en la carpeta coveragereport
Ejemplos¶
Se dispone un repo con varios ejemplos de test para que puedan guiarse en los primeros pasos. GitHub/Architecture-it
Autor/a: Olivera Lucas
Contacto: lolivera@andreani.com