Unit Testing - Aplicándolo al Dominio (2024)

Los Unit Testing o también llamados "pruebas unitarios" resultan muy importantes durante el proceso de desarrollo y construcción del software. Las pruebas unitarias nos permiten realizar diversas evaluaciones a medida que vamos desarrollando nuestro software. Precisamente la modalidad TDD (Test-Driven Development) hace uso de esta práctica.

Las pruebas unitarias se realizan de modo atomizado. Ello quiere decir que las mismas se aplican a determinadas partes de los algoritmos de un programa. Por ejemplo, supongamos que tenemos una clase que alberga en su interior varios tipos de funciones y métodos. Cada una de estas funciones y métodos podrían llevar acabo diversos tipos de operaciones. Una evaluación podría tratarse de la prueba de algunas de esas operaciones de modo separado o bien, en forma conjunta.

Las pruebas unitarias resultan muy útiles para varios fines. Por empezar, nos permite determinar si nuestro software está operando correctamente. Por otra parte, esta práctica nos permite supervisar que un proceso de despliegue del software se realice de manera correcta, anticipándonos seguramente a potenciales problemas que podrían trasladarse directamente hacia los ambientes productivos. Esto último debe evitarse a toda costa.

Unit Testing en el Dominio de una Arquitectura Limpia

Como recien señalamos que las pruebas unitarias se realizan de modo atomizada. Por tanto, ¿cómo podríamos aplicar esta práctica en un Dominio en una arquitectura limpia? Antes de contestar esta pregunta, voy a mostrarte un ejemplo de Dominio para luego comprender el test de prueba que se deberá aplicar.

Unit Testing - Aplicándolo al Dominio (1)

Como puedes apreciar, el dominio se compone de una clase de entidad de datos o model llamada Stock. Esta clase contiene un conjunto de propiedades de tipo getter & setter. Cada una de estas propiedades posee su propio tipo de datos. Cada tipo de datos original hacen uso de otros tipos de datos primitivos como int, string, etc. Esta forma de diseño del modelo para la clase Stock obedece una normalización. Esta normalización sencillamente propone que los diseñadores del software se concentren en las reglas del negocio en lugar de las trivialidades del mismo.

Por otra parte, contamos con un registro llamado Monedas albergada dentro del paquete Abstractions. Este registro se encarga de obtener algunos valores monetarios, totales y totales más impuestos. Este registro es utilizado a igual que los tipos de objetos que componen a la clase Stock que es la entidad de datos. Este paquete se encuentra contenido dentro del paquete Domain.Stocks. Veamos el código fuente que compone el dominio de Stock.

// Registros para el Stock. (Archivos individuales)namespace Domain.Stocks;public record ID(int Value);---namespace Domain.Stocks;public record Articulo(string Value);---namespace Domain.Stocks;public record Cantidad(int Value);---namespace Domain.Stocks;public record IVA(decimal Value);---namespace Domain.Stocks;public record Marca(string Value); --- namespace Domain.Stocks;public record Maxima(int Value);--- namespace Domain.Stocks;public record Modelo(string Value);--- namespace Domain.Stocks;public record RubroId(int Value); --- namespace Domain.Stocks;public record SucursalId(int Value); --- namespace Domain.Stocks;public record UnitarioDolar(decimal Value); ---// Entidad de Datos Stocknamespace Domain.Stocks;public class Stock{ public ID? ID { get; set; } public SucursalId? SucursalId { get; set; } public RubroId? RubroId { get; set; } public Articulo? Articulo { get; set; } public Marca? Marca { get; set; } public Modelo? Modelo { get; set; } public Cantidad? Cantidad { get; set; } public Maxima? Maxima { get; set; } public IVA? IVA { get; set; } public UnitarioDolar? UnitarioDolar { get; set; }}---// Registro para el cálculos de las monedas. namespace Domain.Stocks.Abstractions;public record Monedas( decimal ValorUnitarioDolar, int ValorCantidad, decimal ValorCotizacion, decimal ValorIVA){ public decimal GetTotalDolar() => ValorUnitarioDolar * ValorCantidad; public decimal GetUnitarioPesos() => ValorUnitarioDolar * ValorCotizacion; public decimal GetTotalPesosSinIVA() => GetUnitarioPesos() * ValorCantidad; public decimal GetTotalPesosConIVA() => (GetTotalPesosSinIVA() * ValorIVA / 100) + GetTotalPesosSinIVA();} 

Como podemos apreciar, este es todo el código que tenemos implementado en nuestro Dominio. El registro Monedas nos permite hacer algunas operaciones como por ejemplo convertir la paridad cambiaria de dólares a pesos argentino. También este tipo de conversión incluye el IVA (Impuesto Valor Agregado), que es un impuesto aplicado a los valores agregados. Se trata de un impuesto que rige en Argentina desde 1975 bajo la ley 20.631.

Evaluando al Dominio Stock

Hemos visto cuáles son las reglas de negocio del dominio Stock. La pregunta que nos estaremos haciendo es la siguientes, ¿cómo evaluar al dominio Stock haciendo uso de Unit Testing? A continuación, expongo el código que cumple esta función.

using Domain.Stocks;using System.Reflection;using FluentAssertions;using Domain.Stocks.Abstractions;namespace UnitTests.Domain;public class StockUnitTest{ // ********************************************************************* // *** Sección para Mockable ******************************************* // ********************************************************************* private readonly decimal Cotizacion = 903; private Stock Mockable() { return new Stock() { ID = new ID(1), SucursalId = new SucursalId(1), RubroId = new RubroId(1), Articulo = new Articulo("Pinza"), Marca = new Marca("Waco"), Modelo = new Modelo("X2"), Cantidad = new Cantidad(25), Maxima = new Maxima(100), IVA = new IVA(21), UnitarioDolar = new UnitarioDolar(12.45m) }; } // ********************************************************************* [Fact] public void TestStockStructure() { int f = 0; Type type = typeof(Stock); string[] props = { "ID", "SucursalId", "RubroId", "Articulo", "Marca", "Modelo", "Cantidad", "Maxima", "IVA", "UnitarioDolar" }; // Testear los nombres de las propiedades. foreach (PropertyInfo property in type.GetProperties()) { property.Name.Should().BeEquivalentTo(props[f]); f++; } } [Fact] public void TestStockInstances() { var stk = new Stock() { ID = new ID(1), SucursalId = new SucursalId(1), RubroId = new RubroId(1), Articulo = new Articulo("Pinza"), Marca = new Marca("Waco"), Modelo = new Modelo("X2"), Cantidad = new Cantidad(25), Maxima = new Maxima(100), IVA = new IVA(21), UnitarioDolar = new UnitarioDolar(12.45m) }; Mockable().Should().BeEquivalentTo(stk); } [Fact] public void TestStockValues() { // Instancia de Monedas var moneda = new Monedas( Mockable().UnitarioDolar!.Value, Mockable().Cantidad!.Value, Cotizacion, Mockable().IVA!.Value ); // Valores convertidos monetarios decimal totalDolar = moneda.GetTotalDolar(); decimal unitarioPesos = moneda.GetUnitarioPesos(); decimal totalPesosSinIVA = moneda.GetTotalPesosSinIVA(); decimal totalPesosConIVA = moneda.GetTotalPesosConIVA(); // Testear resultados. Assert.Equal(311.25m, totalDolar); Assert.Equal(11242.35m, unitarioPesos); Assert.Equal(281058.75m, totalPesosSinIVA); Assert.Equal(340081.0875m, totalPesosConIVA); }} 

El código lo vamos a dividir al menos en dos partes importantes. Por un lado, tenemos la sección del Mockable y en la otra parte, tenemos el proceso de evaluaciones y pruebas de Unit Testingusing Domain.Stocks;

using Domain.Stocks;using System.Reflection;using FluentAssertions;using Domain.Stocks.Abstractions;namespace UnitTests.Domain;public class StockUnitTest{ // ********************************************************************* // *** Sección para Mockable ******************************************* // ********************************************************************* private readonly decimal Cotizacion = 903; private Stock Mockable() { return new Stock() { ID = new ID(1), SucursalId = new SucursalId(1), RubroId = new RubroId(1), Articulo = new Articulo("Pinza"), Marca = new Marca("Waco"), Modelo = new Modelo("X2"), Cantidad = new Cantidad(25), Maxima = new Maxima(100), IVA = new IVA(21), UnitarioDolar = new UnitarioDolar(12.45m) }; } // ********************************************************************* [Fact] public void TestStockStructure() { int f = 0; Type type = typeof(Stock); string[] props = { "ID", "SucursalId", "RubroId", "Articulo", "Marca", "Modelo", "Cantidad", "Maxima", "IVA", "UnitarioDolar" }; // Testear los nombres de las propiedades. foreach (PropertyInfo property in type.GetProperties()) { property.Name.Should().BeEquivalentTo(props[f]); f++; } } [Fact] public void TestStockInstances() { var stk = new Stock() { ID = new ID(1), SucursalId = new SucursalId(1), RubroId = new RubroId(1), Articulo = new Articulo("Pinza"), Marca = new Marca("Waco"), Modelo = new Modelo("X2"), Cantidad = new Cantidad(25), Maxima = new Maxima(100), IVA = new IVA(21), UnitarioDolar = new UnitarioDolar(12.45m) }; Mockable().Should().BeEquivalentTo(stk); } [Fact] public void TestStockValues() { // Instancia de Monedas var moneda = new Monedas( Mockable().UnitarioDolar!.Value, Mockable().Cantidad!.Value, Cotizacion, Mockable().IVA!.Value ); // Valores convertidos monetarios decimal totalDolar = moneda.GetTotalDolar(); decimal unitarioPesos = moneda.GetUnitarioPesos(); decimal totalPesosSinIVA = moneda.GetTotalPesosSinIVA(); decimal totalPesosConIVA = moneda.GetTotalPesosConIVA(); // Testear resultados. Assert.Equal(311.25m, totalDolar); Assert.Equal(11242.35m, unitarioPesos); Assert.Equal(281058.75m, totalPesosSinIVA); Assert.Equal(340081.0875m, totalPesosConIVA); }} 

Como podemos apreciar en el código fuente, tenemos todo el proceso para las evaluaciones. Para poder estudiar y comprender mejor el código, lo voy a dividir groseramente en dos grandes partes importantes. Por un lado, tenemos la sección del Mockable y en segundo lugar, contamos con todos los procesos de evaluaciones.

El Mockable se trata sencillamente de un recurso con datos que es utilizado para poder compararlos con otros procesos que se encuentran en la segunda parte del código. En breve, Mockable estaría simulando el ingreso de datos que luego serían manipulados por las reglas de negocio que posee programado el dominio Stock.

En la segunda parte vemos cómo se implementa cada uno de los puntos de pruebas o evaluaciones para Unit Testing. Empezando por el primer método TestStockStructure(), este nos permite determinar si la estructura de la clase Stock se mantiene intacta. Para ello se vale de la reflexión del código. Esta característica interna del código nos permite determina si cada una de las propiedades mantienen el mismo nombre y no han sido alteradas. Podría que el desarrollador por error podría cambiar los nombres de las propiedades o de algunos de sus tipos de datos por accidente. Si esto sucediera, el resultado de la evaluación de la prueba arrojaría error.

El siguiente método TestStockInstances() permite evaluar las instancias de clase. Si observamos dentro del cuerpo del método, podremos observar que hemos creado una instancia de clase, de forma muy similar al Mockable. El método simplemente evalúa si ambas instancias de clases poseen los mismos valores asignados a cada una de sus propiedades. En caso de no coincidir, el test nos arrojaría un error.

Por último, el método TestStockValues() tiene la finalidad de probar los datos asignados que son utilizados para ejecutar un conjunto de operaciones matemáticas. La finalidad es la de obtener la conversión de moneda entre dólares y pesos argentinos. Además, se incluye el cálculo de los totales en pesos con y sin impuestos.

Para ir cerrando, las pruebas unitarias resultan ser muy flexibles. Puedes aplicar tus pruebas de la forma que lo desees. Sin embargo, resulta importante que consideres que las pruebas unitarias requieren ser muy específicas y sus tamaños deberían ser lo más reducida posible. La prueba debe evaluar funcionalidades que cumplan un rol particular y no múltiples roles particulares a la vez. Esto último no se aconseja y, no porque no funcione, sino por una cuestión como regla de pruebas atomizadas. Las pruebas individualizadas permiten aislar más rápidamente un problema o fallo en el código fuente que un conjunto complejo de pruebas aglutinadas en una.

Resultados de los Tests

A continuación vemos una imagen de cómo se verían los resultados de las evaluaciones producidas por las pruenas de Unit Testing.

Unit Testing - Aplicándolo al Dominio (2)

Los resultados que se muestran en la figura nos indican que todas las evaluaciones se cumplieron satisfactoriamente. Ahora bien, ¿qué ocurre cuando se produce algún error? Es decir, cuando las pruebas no son superadas correctamente, bien lo estudiamos a continuación.

Caso de un Típico Error de Evaluaciones

Cualquier cambio en la lógica del código o de algunos resultados resulta suficiente para que las evaluaciones no se cumplan apropiadamente. Veamos el siguiente error producido.

Unit Testing - Aplicándolo al Dominio (3)

La prueba no ha sido superada porque hubo alguna alteración en el código. En este caso, he alterado apropósito un valor en el código como te muestro a continuación.

... [Fact] public void TestStockInstances() { var stk = new Stock() { ID = new ID(1), SucursalId = new SucursalId(1), RubroId = new RubroId(1), Articulo = new Articulo("Pinza"), Marca = new Marca("Waco"), Modelo = new Modelo("X2"), Cantidad = new Cantidad(25), Maxima = new Maxima(100), IVA = new IVA(21), UnitarioDolar = new UnitarioDolar(18.45m) // Aquí el valor debe ser 12.45 }; Mockable().Should().BeEquivalentTo(stk); }.... 

Como puede apreciar, el valor se encuentra alterado. Esta alteración es suficiente para que la evaluación no pase. Lo más interesante de todo ello es que el gráfico de evaluaciones en Visual Studio 2022 nos permite explorar dentro de los errores producidos para que podamos conocer los motivos del problema. Esto se muestra en la siguiente figura.

Unit Testing - Aplicándolo al Dominio (4)

Como puedes apreciar en la figura se marca en rojo el método que ha fallado su evaluación. En efecto, se trata del método TestStockInstancias(). Si exploramos el código podremos observar que se ha alterado un valor en la instancia.

En resumen, este tipo de pruebas resulta muy interesante porque nos permite controlar correctamente el comportamiento de nuestro software y evitar potenciales errores que se puedan trasladar hacia otros ambientes de pruebas, hom*ologaciones o de producción. Por otro lado, las pruebas unitarias también forman parte de las verificaciones que se realizan en los ambientes de QA. En breve, las complementan mejoran significativamente el control de calidad de nuestro producto.

Saludos,

Ariel.

Unit Testing - Aplicándolo al Dominio (2024)
Top Articles
Latest Posts
Article information

Author: Prof. Nancy Dach

Last Updated:

Views: 6459

Rating: 4.7 / 5 (57 voted)

Reviews: 88% of readers found this page helpful

Author information

Name: Prof. Nancy Dach

Birthday: 1993-08-23

Address: 569 Waelchi Ports, South Blainebury, LA 11589

Phone: +9958996486049

Job: Sales Manager

Hobby: Web surfing, Scuba diving, Mountaineering, Writing, Sailing, Dance, Blacksmithing

Introduction: My name is Prof. Nancy Dach, I am a lively, joyous, courageous, lovely, tender, charming, open person who loves writing and wants to share my knowledge and understanding with you.