Primeros pasos con las pruebas unitarias
Una prueba de unidad básica
En su forma más simple, una prueba unitaria consta de tres etapas:
- Preparar el ambiente para la prueba
- Ejecutar el código a probar
- Validar que el comportamiento esperado coincida con el comportamiento observado
Estas tres etapas a menudo se denominan ‘Organizar-Actuar-Afirmar’ o ‘Dar-cuándo-entonces’.
A continuación se muestra un ejemplo en C# que utiliza el marco NUnit.
[TestFixture]
public CalculatorTest
{
[Test]
public void Add_PassSevenAndThree_ExpectTen()
{
// Arrange - setup environment
var systemUnderTest = new Calculator();
// Act - Call system under test
var calculatedSum = systemUnderTest.Add(7, 3);
// Assert - Validate expected result
Assert.AreEqual(10, calculatedSum);
}
}
Cuando es necesario, se ordena una cuarta etapa de limpieza opcional.
Una prueba unitaria con dependencia tachada
Las buenas pruebas unitarias son independientes, pero el código a menudo tiene dependencias. Usamos varios tipos de dobles de prueba para eliminar las dependencias para la prueba. Uno de los dobles de prueba más simples es un trozo. Esta es una función con un valor de retorno codificado que se llama en lugar de la dependencia del mundo real.
// Test that oneDayFromNow returns a value 24*60*60 seconds later than current time
let systemUnderTest = new FortuneTeller() // Arrange - setup environment
systemUnderTest.setNow(() => {return 10000}) // inject a stub which will
// return 10000 as the result
let actual = systemUnderTest.oneDayFromNow() // Act - Call system under test
assert.equals(actual, 10000 + 24 * 60 * 60) // Assert - Validate expected result
En el código de producción, oneDayFromNow
llamaría a Date.now(), pero eso generaría pruebas inconsistentes y poco confiables. Así que aquí lo apagamos.
Una prueba unitaria con un espía (prueba de interacción)
Las pruebas unitarias clásicas prueban estado, pero puede ser imposible probar correctamente métodos cuyo comportamiento depende de otras clases a través del estado. Probamos estos métodos a través de pruebas de interacción, que verifican que el sistema bajo prueba llama correctamente a sus colaboradores. Dado que los colaboradores tienen sus propias pruebas unitarias, esto es suficiente y, de hecho, una mejor prueba de la responsabilidad real del método probado. No probamos que este método devuelva un resultado particular dada una entrada, sino que llame correctamente a su(s) colaborador(es).
// Test that squareOfDouble invokes square() with the doubled value
let systemUnderTest = new Calculator() // Arrange - setup environment
let square = spy()
systemUnderTest.setSquare(square) // inject a spy
let actual = systemUnderTest.squareOfDouble(3) // Act - Call system under test
assert(square.calledWith(6)) // Assert - Validate expected interaction
Prueba simple de Java+JUnit
JUnit es el marco de prueba líder que se utiliza para probar el código Java.
La clase bajo prueba modela una cuenta bancaria simple, que cobra una multa cuando se sobregira.
public class BankAccount {
private int balance;
public BankAccount(int i){
balance = i;
}
public BankAccount(){
balance = 0;
}
public int getBalance(){
return balance;
}
public void deposit(int i){
balance += i;
}
public void withdraw(int i){
balance -= i;
if (balance < 0){
balance -= 10; // penalty if overdrawn
}
}
}
Esta clase de prueba valida el comportamiento de algunos de los métodos públicos BankAccount
.
import org.junit.Test;
import static org.junit.Assert.*;
// Class that tests
public class BankAccountTest{
BankAccount acc;
@Before // This will run **before** EACH @Test
public void setUptestDepositUpdatesBalance(){
acc = new BankAccount(100);
}
@After // This Will run **after** EACH @Test
public void tearDown(){
// clean up code
}
@Test
public void testDeposit(){
// no need to instantiate a new BankAccount(), @Before does it for us
acc.deposit(100);
assertEquals(acc.getBalance(),200);
}
@Test
public void testWithdrawUpdatesBalance(){
acc.withdraw(30);
assertEquals(acc.getBalance(),70); // pass
}
@Test
public void testWithdrawAppliesPenaltyWhenOverdrawn(){
acc.withdraw(120);
assertEquals(acc.getBalance(),-30);
}
}
Prueba unitaria con parámetros usando NUnit y C#
using NUnit.Framework;
namespace MyModuleTests
{
[TestFixture]
public class MyClassTests
{
[TestCase(1, "Hello", true)]
[TestCase(2, "bye", false)]
public void MyMethod_WhenCalledWithParameters_ReturnsExpected(int param1, string param2, bool expected)
{
//Arrange
var foo = new MyClass(param1);
//Act
var result = foo.MyMethod(param2);
//Assert
Assert.AreEqual(expected, result);
}
}
}
Una prueba unitaria básica de python
import unittest
def addition(*args):
""" add two or more summands and return the sum """
if len(args) < 2:
raise ValueError, 'at least two summands are needed'
for ii in args:
if not isinstance(ii, (int, long, float, complex )):
raise TypeError
# use build in function to do the job
return sum(args)
Ahora la parte de prueba:
class Test_SystemUnderTest(unittest.TestCase):
def test_addition(self):
"""test addition function"""
# use only one summand - raise an error
with self.assertRaisesRegexp(ValueError, 'at least two summands'):
addition(1)
# use None - raise an error
with self.assertRaises(TypeError):
addition(1, None)
# use ints and floats
self.assertEqual(addition(1, 1.), 2)
# use complex numbers
self.assertEqual(addition(1, 1., 1+2j), 3+2j)
if __name__ == '__main__':
unittest.main()
Una prueba XUnit con parámetros
using Xunit;
public class SimpleCalculatorTests
{
[Theory]
[InlineData(0, 0, 0, true)]
[InlineData(1, 1, 2, true)]
[InlineData(1, 1, 3, false)]
public void Add_PassMultipleParameters_VerifyExpected(
int inputX, int inputY, int expected, bool isExpectedCorrect)
{
// Arrange
var sut = new SimpleCalculator();
// Act
var actual = sut.Add(inputX, inputY);
// Assert
if (isExpectedCorrect)
{
Assert.Equal(expected, actual);
}
else
{
Assert.NotEqual(expected, actual);
}
}
}
public class SimpleCalculator
{
public int Add(int x, int y)
{
return x + y;
}
}