Escribir pruebas unitarias en C# (xUnit, NUnit, MSTest)
Aprende a escribir pruebas unitarias en C# con xUnit, NUnit y MSTest para crear aplicaciones más confiables.
En el desarrollo de software, las pruebas unitarias (unit tests) garantizan que las partes más pequeñas de una aplicación (métodos, clases o funciones) funcionen correctamente de forma aislada. En C#, los marcos de prueba más utilizados son xUnit, NUnit y MSTest. Las pruebas unitarias ayudan a detectar errores en etapas tempranas, mejorar la calidad del código y permitir un refactoring seguro.
¿Qué es una prueba unitaria?
Las pruebas unitarias verifican si un método produce la salida esperada para entradas determinadas. Generalmente, las pruebas se aíslan por escenario y se separan de las dependencias externas (base de datos, sistema de archivos, acceso a red).
// Ejemplo: método a probar
public class Calculadora
{
public int Sumar(int a, int b) => a + b;
}
En el ejemplo anterior, el método Sumar es la unidad que debe probarse.
Crear un proyecto de prueba
Puedes crear fácilmente un proyecto de prueba con Visual Studio o la CLI de .NET:
// Crear un proyecto de prueba con xUnit
dotnet new xunit -n Calculadora.Tests
cd Calculadora.Tests
// Agregar referencia al proyecto principal
dotnet add reference ../Calculadora/Calculadora.csproj
Alternativamente, puedes usar las plantillas nunit o mstest:
dotnet new nunit -n NombreProyecto.Tests
dotnet new mstest -n NombreProyecto.Tests
Escribir pruebas con xUnit
xUnit es un marco de pruebas moderno y flexible ampliamente utilizado en el ecosistema de C#.
El atributo [Fact] define pruebas individuales, mientras que [Theory] se usa para pruebas parametrizadas.
using Xunit;
public class CalculadoraTests
{
[Fact]
public void Sumar_DebeDevolverResultadoCorrecto()
{
// Arrange
var c = new Calculadora();
// Act
int resultado = c.Sumar(2, 3);
// Assert
Assert.Equal(5, resultado);
}
[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, 5, 4)]
[InlineData(10, -2, 8)]
public void Sumar_ConValoresDiferentes_DebeFuncionarCorrectamente(int a, int b, int esperado)
{
var c = new Calculadora();
Assert.Equal(esperado, c.Sumar(a, b));
}
}
Fact: prueba un solo escenario. Theory: prueba el mismo método con varios conjuntos de datos.
Escribir pruebas con NUnit
NUnit es un marco de pruebas potente y de larga trayectoria en el mundo de C#. Es conocido por su simplicidad y su amplia variedad de aserciones.
using NUnit.Framework;
[TestFixture]
public class CalculadoraTests
{
private Calculadora calc;
[SetUp]
public void Setup()
{
calc = new Calculadora();
}
[Test]
public void Sumar_Prueba()
{
int resultado = calc.Sumar(4, 6);
Assert.That(resultado, Is.EqualTo(10));
}
[TestCase(1, 2, 3)]
[TestCase(5, 5, 10)]
public void Sumar_ConValoresDiferentes_Prueba(int a, int b, int esperado)
{
Assert.That(calc.Sumar(a, b), Is.EqualTo(esperado));
}
}
En NUnit, el método [SetUp] se ejecuta antes de cada prueba,
y [TestCase] se utiliza para pruebas parametrizadas.
Escribir pruebas con MSTest
MSTest es el marco de pruebas integrado de Microsoft que viene con Visual Studio. Se utiliza con frecuencia en proyectos empresariales.
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class CalculadoraTests
{
private Calculadora calc;
[TestInitialize]
public void Init()
{
calc = new Calculadora();
}
[TestMethod]
public void Sumar_Prueba()
{
int resultado = calc.Sumar(2, 8);
Assert.AreEqual(10, resultado);
}
[DataTestMethod]
[DataRow(1, 1, 2)]
[DataRow(-3, 5, 2)]
public void Sumar_ConValoresDiferentes_Prueba(int a, int b, int esperado)
{
Assert.AreEqual(esperado, calc.Sumar(a, b));
}
}
En MSTest, [TestInitialize] se usa para la configuración antes de cada prueba,
mientras que [DataTestMethod] se utiliza para pruebas basadas en datos.
Métodos de Aserción (Assert)
Todos los frameworks de pruebas incluyen estructuras de aserción similares:
Assert.Equal(expected, actual)→ Comprueba la igualdad de valores.Assert.NotNull(obj)→ Verifica que el objeto no sea nulo.Assert.True(condition)/Assert.False(condition)Assert.Throws<Exception>(() => ...)→ Verifica que se lance una excepción esperada.
// Ejemplo: prueba de excepción
[Fact]
public void Division_DebeLanzarDivideByZeroException()
{
var calc = new Calculadora();
Assert.Throws<DivideByZeroException>(() => calc.Dividir(5, 0));
}
Ejecución de Pruebas
Las pruebas se pueden ejecutar mediante la CLI o Visual Studio:
// Ejecutar pruebas con .NET CLI
dotnet test
En Visual Studio, la ventana “Test Explorer” permite ejecutar pruebas y visualizar los resultados exitosos o fallidos en una interfaz gráfica.
Mocking y Aislamiento de Dependencias
El mocking se utiliza para aislar las dependencias reales del entorno de prueba. Esto garantiza que las pruebas solo verifiquen el método objetivo y no se vean afectadas por factores externos (base de datos, servicios, etc.).
using Moq;
using Xunit;
public interface IServicioEmail
{
void Enviar(string direccion, string mensaje);
}
public class RegistroUsuario
{
private readonly IServicioEmail _email;
public RegistroUsuario(IServicioEmail email) => _email = email;
public void Registrar(string nombre)
{
_email.Enviar("admin@site.com", $"{nombre} ha sido registrado.");
}
}
public class RegistroUsuarioTests
{
[Fact]
public void Registrar_DebeEnviarEmail()
{
var mock = new Mock<IServicioEmail>();
var registro = new RegistroUsuario(mock.Object);
registro.Registrar("Juan");
mock.Verify(m => m.Enviar("admin@site.com", "Juan ha sido registrado."), Times.Once);
}
}
La biblioteca Moq crea objetos simulados (mocks) para aislar el entorno de prueba.
Organización y Nomenclatura de Pruebas
- Los nombres de las pruebas deben ser descriptivos:
NombreMetodo_Escenario_ResultadoEsperado. - Cada prueba debe verificar un solo comportamiento.
- Siga la estructura Arrange–Act–Assert (Preparar–Actuar–Afirmar).
- Minimice las dependencias entre pruebas.
- Las pruebas deben ejecutarse de forma independiente.
Ejemplo Real: Prueba de Cálculo de Total de Pedido
El siguiente ejemplo prueba el cálculo del total de un pedido.
// Código de la aplicación
public class Pedido
{
public List<decimal> PreciosProductos { get; set; } = new();
public decimal Total() => PreciosProductos.Sum();
}
// Prueba
public class PedidoTests
{
[Fact]
public void Total_DebeDevolverElResultadoCorrecto()
{
var p = new Pedido();
p.PreciosProductos.AddRange(new[] { 10m, 20m, 30m });
Assert.Equal(60m, p.Total());
}
}
Este es un ejemplo básico de prueba unitaria que garantiza que la lógica de negocio funcione correctamente.
Rendimiento y Mejores Prácticas
- Cada prueba debe estar aislada y no depender de un estado global.
- Mantenga las pruebas pequeñas: una prueba debe validar un solo comportamiento.
- Utilice mocks o stubs para aislar dependencias externas.
- Integre las pruebas en su pipeline de Integración Continua (CI).
- Perfilar y optimizar las pruebas lentas.
TL;DR
- Prueba Unitaria: Prueba unidades de código pequeñas e independientes.
- xUnit: Framework moderno, minimalista y ampliamente utilizado en .NET.
- NUnit: Framework clásico con potentes aserciones y enfoque basado en atributos.
- MSTest: Solución integrada de Microsoft, completamente compatible con Visual Studio.
- Mocking: Aísla dependencias externas mediante objetos simulados.
- Modelo AAA: Siga el patrón Arrange–Act–Assert.
- El comando dotnet test ejecuta todas las pruebas desde la CLI.
Artículos relacionados
Consejos de Visual Studio / VS Code para C#
Aprende consejos de Visual Studio y VS Code para C# y mejora tu productividad con atajos y herramientas.
Fundamentos de Inyección de Dependencias en C#
Aprende los fundamentos de Inyección de Dependencias en C#, gestionando dependencias y logrando bajo acoplamiento.