Tutorial: Crear y ejecutar pruebas unitarias en código administrado

En este artículo se recorre paso a paso la creación, ejecución y personalización de una serie de pruebas unitarias mediante el marco de pruebas unitarias para código administrado de Microsoft y el Explorador de pruebas de Visual Studio. Se empieza con un proyecto C# que está en desarrollo, se crean pruebas que utilizan el código, se ejecutan las pruebas y se examinan los resultados. Luego se cambia el código del proyecto y se vuelven a ejecutar las pruebas. Si quiere obtener información general conceptual de estas tareas antes de seguir estos pasos, consulte Conceptos básicos de prueba unitaria.

Crear un proyecto para pruebas

  1. Abra Visual Studio.

  2. En la ventana de inicio, elija Crear un proyecto nuevo.

  3. Busque y seleccione la plantilla de proyecto de C# Aplicación de consola para .NET y haga clic en Siguiente.

    Nota:

    Si no ve la plantilla Aplicación de consola, puede instalarla desde la ventana Crear un proyecto. En el mensaje ¿No encuentra lo que busca? , elija el vínculo Instalar más herramientas y características. A continuación, en el Instalador de Visual Studio, elija la carga de trabajo Desarrollo de escritorio de .NET.

  4. Asigne al proyecto el nombre Bank y haga clic en Siguiente.

    Seleccione la plataforma de destino recomendada o .NET 8 y, después, elija Crear.

    Se crea el proyecto Bank y se muestra en el Explorador de soluciones con el archivo Program.cs abierto en el editor de código.

    Nota

    Si Program.cs no se abre en el editor, haga doble clic en el archivo Program.cs en el Explorador de soluciones para abrirlo.

  5. Reemplace el contenido de Program.cs por el siguiente código de C# que define una clase, BankAccount:

    using System;
    
    namespace BankAccountNS
    {
        /// <summary>
        /// Bank account demo class.
        /// </summary>
        public class BankAccount
        {
            private readonly string m_customerName;
            private double m_balance;
    
            private BankAccount() { }
    
            public BankAccount(string customerName, double balance)
            {
                m_customerName = customerName;
                m_balance = balance;
            }
    
            public string CustomerName
            {
                get { return m_customerName; }
            }
    
            public double Balance
            {
                get { return m_balance; }
            }
    
            public void Debit(double amount)
            {
                if (amount > m_balance)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount; // intentionally incorrect code
            }
    
            public void Credit(double amount)
            {
                if (amount < 0)
                {
                    throw new ArgumentOutOfRangeException("amount");
                }
    
                m_balance += amount;
            }
    
            public static void Main()
            {
                BankAccount ba = new BankAccount("Mr. Bryan Walton", 11.99);
    
                ba.Credit(5.77);
                ba.Debit(11.22);
                Console.WriteLine("Current balance is ${0}", ba.Balance);
            }
        }
    }
    
  6. Cambie el nombre del archivo a BankAccount.cs al hacer clic con el botón derecho y elegir Cambiar nombre en el Explorador de soluciones.

  7. En el menú Compilación, haga clic en Compilar solución (o presione CTRL + Mayús + B).

Ahora tiene un proyecto con métodos que puede probar. En este artículo, las pruebas se centran en el método Debit. Se llama al método Debit cuando se retira dinero de una cuenta.

Crear un proyecto de prueba unitaria

  1. En el menú Archivo, seleccione Agregar>Nuevo proyecto.

    Sugerencia

    También puede hacer clic con el botón derecho en la solución en el Explorador de soluciones y elegir Agregar>Nuevo proyecto.

  2. Escriba prueba en el cuadro de búsqueda, seleccione C# como lenguaje y, a continuación, seleccione el proyecto de prueba unitaria de MSTest de C# para la plantilla de .NET y, a continuación, haga clic en Siguiente.

    Nota:

    En Visual Studio 2019 versión 16.9, la plantilla de proyecto MSTest es Proyecto de prueba unitaria.

  3. Asigne al proyecto el nombre BankTests y haga clic en Siguiente.

  4. Seleccione la plataforma de destino recomendada o .NET 8 y, después, elija Crear.

    El proyecto BankTests se agrega a la solución Bank.

  5. En el proyecto BankTests, agregue una referencia al proyecto Bank.

    En el Explorador de soluciones, seleccione Dependencias en el proyecto BankTests y luego seleccione Agregar referencia (o Agregar referencia de proyecto) en el menú contextual.

  6. En el cuadro de diálogo Administrador de referencias, expanda Proyectos, seleccione Solución y active el elemento Bank.

  7. Elija Aceptar.

Crear la clase de prueba

Cree una clase de prueba para comprobar la clase BankAccount. Puede utilizar el archivo UnitTest1.cs, generado por la plantilla de proyecto, pero asigne al archivo y a la clase nombres más descriptivos.

Cambio de nombre de un archivo y una clase

  1. Para cambiar el nombre del archivo, en el Explorador de soluciones, seleccione el archivo UnitTest1.cs del proyecto BankTests. En el menú contextual, seleccione Cambiar nombre (o presione F2) y cambie el nombre del archivo a BankAccountTests.cs.

  2. Para cambiar el nombre de la clase, sitúe el cursor sobre UnitTest1 en el editor de código, haga clic con el botón derecho y seleccione Cambiar nombre (o presione F2). Escriba BankAccountTests y presione Entrar.

El archivo BankAccountTests.cs contiene ahora el siguiente código:

// The 'using' statement for Test Tools is in GlobalUsings.cs
// using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankTests
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

Agregar una instrucción using

Agregue una instrucción using a la clase de prueba para poder llamar al proyecto que se está probando sin usar nombres completos. En la parte superior del archivo de clase, agregue:

using BankAccountNS;

Requisitos de la clase de prueba

Los requisitos mínimos para una clase de prueba son los siguientes:

  • Se requiere el atributo [TestClass] en cualquier clase que contenga métodos de prueba unitaria que quiera ejecutar en el Explorador de pruebas.

  • Cada método de prueba que quiera que el Explorador de pruebas reconozca debe tener el atributo [TestMethod].

Puede tener otras clases de un proyecto de prueba unitaria que no tengan el atributo [TestClass] y puede tener otros métodos de clases de prueba que no tengan el atributo [TestMethod] . Puede llamar a estos otros métodos y clases desde los métodos de prueba.

Crear el primer método de prueba

En este procedimiento, escribe métodos de prueba unitaria para comprobar el comportamiento del método Debit de la clase BankAccount.

Hay al menos tres comportamientos que deben comprobarse:

  • El método produce ArgumentOutOfRangeException si la cantidad de débito es mayor que el saldo.

  • El método produce ArgumentOutOfRangeException si la cantidad de débito es menor que cero.

  • Si la cantidad de débito es válida, el método resta la cantidad de débito del saldo de cuenta.

Sugerencia

Puede eliminar el método TestMethod1 predeterminado, ya que no lo usará en este tutorial.

Para crear un método de prueba

En la primera prueba, se comprueba que una cantidad válida (es decir, una menor que el saldo de cuenta y mayor que cero) retire la cantidad correcta de la cuenta. Agregue el siguiente método a esa clase BankAccountTests :

[TestMethod]
public void Debit_WithValidAmount_UpdatesBalance()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 4.55;
    double expected = 7.44;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    account.Debit(debitAmount);

    // Assert
    double actual = account.Balance;
    Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly");
}

El método es sencillo: configura un nuevo objeto BankAccount con un saldo inicial y luego retira una cantidad válida. Usa el método Assert.AreEqual para comprobar que el saldo de cierre es el esperado. Los métodos como Assert.AreEqual, Assert.IsTrue y otros se usan con frecuencia en las pruebas unitarias. Para obtener más información conceptual sobre cómo escribir una prueba unitaria, vea Escribir las pruebas.

Requisitos del método de prueba

Un método de prueba debe cumplir los siguientes requisitos:

  • Es representativo del atributo [TestMethod].

  • Devuelve void.

  • No puede tener parámetros.

Compilar y ejecutar la prueba

  1. En el menú Compilación, elija Compilar solución (o presione CTRL + Mayús + B).

  2. Si el explorador de pruebas no está abierto, ábralo eligiendo Prueba>Explorador de pruebas (o Prueba>Windows>Explorador de pruebas) en la barra de menús superior (o presione Ctrl + E, T).

  3. Elija Ejecutar todas para ejecutar la prueba (o presione CTRL + R, V).

    Mientras se ejecuta la prueba, la barra de estado de la parte superior de la ventana Explorador de pruebas está animada. Al final de la serie de pruebas, la barra se vuelve verde si todos los métodos de prueba se completan correctamente o roja si no alguna de las prueba no lo hace.

    En este caso, la prueba no se completa correctamente.

  4. Seleccione el método en el Explorador de pruebas para ver los detalles en la parte inferior de la ventana.

Corrija el código y vuelva a ejecutar las pruebas

El resultado de la prueba contiene un mensaje que describe el error. Es posible que tenga que explorar en profundidad para ver este mensaje. En el caso del método AreEqual, el mensaje muestra lo que se esperaba y lo que se ha recibido realmente. Esperaba que se redujera el saldo pero, en su lugar, aumentó en la cantidad retirada.

La prueba unitaria puso al descubierto un error: la cantidad retirada se agrega al saldo de cuenta en lugar de ser restada.

Corregir el error

Para corregir el error, en el archivo BankAccount.cs, reemplace la línea:

m_balance += amount;

Por:

m_balance -= amount;

Vuelva a ejecutar la prueba

En el Explorador de pruebas, elija Ejecutar todas para volver a ejecutar la prueba (o presione CTRL + R, V). La barra de color rojo o verde se vuelve verde para indicar que se ha superado la prueba.

Test Explorer in Visual Studio 2019 showing passed test

Test Explorer in Visual Studio 2019 showing passed test

Utilice pruebas unitarias para mejorar el código

En esta sección se describe cómo un proceso iterativo de análisis, el desarrollo de pruebas unitarias y la refactorización pueden servirle de ayuda para que el código de producción sea más compacto y eficaz.

Analizar los problemas

Ha creado un método de prueba para confirmar que se resta correctamente una cantidad válida en el método Debit. Ahora, compruebe que el método produce ArgumentOutOfRangeException si la cantidad de débito es:

  • mayor que el saldo, o
  • menor que cero.

Creación y ejecución de nuevos métodos de prueba

Cree un método de prueba para comprobar que el comportamiento es el correcto cuando la cantidad de débito es menor que cero:

[TestMethod]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = -100.00;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act and assert
    Assert.ThrowsException<System.ArgumentOutOfRangeException>(() => account.Debit(debitAmount));
}

Use el método ThrowsException para declarar que se ha producido la excepción correcta. Este método provoca un error en la prueba, a menos que se produzca ArgumentOutOfRangeException. Si modifica temporalmente el método en pruebas para que produzca una excepción ApplicationException más genérica cuando la cantidad de débito es menor que cero, la prueba se comporta correctamente; es decir, se produce un error.

Para probar el caso en el que la cantidad retirada es mayor que el saldo, siga los siguientes pasos:

  1. Crear un nuevo método de prueba denominado Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange.

  2. Copiar el cuerpo del método de Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange en el nuevo método.

  3. Establecer debitAmount en un número mayor que el del saldo.

Ejecute las dos pruebas y compruebe que se superan.

Continuar el análisis

Puede mejorar aún más el método que se está probando. Con la implementación actual, no hay ninguna manera de saber qué condición (amount > m_balance o amount < 0) ha provocado la excepción producida durante la prueba. Sabemos solo que se ha producido un ArgumentOutOfRangeException en algún lugar del método. Sería mejor si pudiéramos decir qué condición en BankAccount.Debit produjo la excepción (amount > m_balance o amount < 0) para que pudiéramos estar seguros de que el método está comprobando correctamente el estado de sus argumentos.

Examine de nuevo el método en pruebas (ArgumentOutOfRangeException) y compruebe que ambas instrucciones condicionales utilizan un constructor BankAccount.Debit que tan solo toma el nombre del argumento como parámetro:

throw new ArgumentOutOfRangeException("amount");

Puede usar un constructor que proporcione información mucho más completa: ArgumentOutOfRangeException(String, Object, String) incluye el nombre y el valor del argumento, y un mensaje definido por el usuario. Puede refactorizar el método en pruebas para utilizar este constructor. Incluso mejor, puede utilizar miembros de tipo que se encuentran disponibles públicamente para especificar los errores.

Refactorizar el código en pruebas

Primero, defina dos constantes para los mensajes de error en el ámbito de la clase. Coloque las definiciones en la clase en prueba, BankAccount:

public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount is less than zero";

A continuación, modifique las dos instrucciones condicionales en el método Debit:

if (amount > m_balance)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}

if (amount < 0)
{
    throw new System.ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}

Refactorizar los métodos de prueba

Refactorice los métodos de prueba mediante la eliminación de la llamada a Assert.ThrowsException. Encapsule la llamada a Debit() en un bloque try/catch, detecte la excepción concreta que se espera y compruebe su mensaje asociado. El método Microsoft.VisualStudio.TestTools.UnitTesting.StringAssert.Contains ofrece la posibilidad de comparar dos cadenas.

Por tanto, Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange quedaría de la siguiente manera:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
    }
}

Vuelva a probar, reescriba y vuelva a analizar

Actualmente, el método de prueba no controla todos los casos que debería. Si el método sometido a prueba (Debit) no ha podido iniciar una excepción ArgumentOutOfRangeException cuando debitAmount era mayor que el saldo (o menor que cero), el método de prueba pasaría. Este escenario no es bueno, porque quiere que el método de prueba no se supere si no se produce ninguna excepción.

Se trata de un error en el método de prueba. Para resolver el problema, agregue una validación Assert.Fail al final del método de prueba para controlar el caso donde no se produce ninguna excepción.

Al volver a ejecutar la prueba, se muestra que se produce un error en ella si se detecta la excepción correcta. El bloque catch detecta la excepción, pero el método sigue ejecutándose y se produce un error en el nueva comprobación de Assert.Fail. Para resolver este problema, agregue una instrucción return después de StringAssert en el bloque catch. Al volver a ejecutar la prueba, se confirma que ha resuelto este problema. La versión final de Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange tiene el siguiente aspecto:

[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
    // Arrange
    double beginningBalance = 11.99;
    double debitAmount = 20.0;
    BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);

    // Act
    try
    {
        account.Debit(debitAmount);
    }
    catch (System.ArgumentOutOfRangeException e)
    {
        // Assert
        StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
        return;
    }

    Assert.Fail("The expected exception was not thrown.");
}

Conclusión

Las mejoras en el código de prueba condujeron a métodos de prueba más eficaces e informativos. Pero lo que es más importante, también mejoraron el código sometido a prueba.

Sugerencia

En este tutorial se utiliza el marco de pruebas unitarias de Microsoft para código administrado. El Explorador de pruebas también puede ejecutar pruebas desde marcos de pruebas unitarias de terceros que tengan adaptadores para el Explorador de pruebas. Para más información, vea Instalar marcos de prueba unitaria de terceros.

Para obtener información sobre cómo ejecutar pruebas desde una línea de comandos, vea Opciones de la línea de comandos para VSTest.Console.exe.