unit-testing Tutorial
Iniziare con i test unitari
Ricerca…
Osservazioni
Il test unitario descrive il processo di testare singole unità di codice isolate dal sistema di cui fanno parte. Ciò che costituisce un'unità può variare da sistema a sistema, passando da un metodo individuale a un gruppo di classi strettamente correlate o un modulo.
L'unità è isolata dalle sue dipendenze utilizzando i duplicati di test quando necessario e configurati in uno stato noto. Il suo comportamento in reazione agli stimoli (chiamate di metodo, eventi, dati simulati) viene quindi testato rispetto al comportamento previsto.
I test unitari di interi sistemi possono essere eseguiti utilizzando cablaggi di test personalizzati, tuttavia sono stati scritti molti framework di test per semplificare il processo e occuparsi di gran parte delle attività idrauliche, ripetitive e banali. Ciò consente agli sviluppatori di concentrarsi su ciò che vogliono testare.
Quando un progetto ha sufficienti test unitari, qualsiasi modifica dell'aggiunta di nuove funzionalità o esecuzione di un refactoring del codice può essere effettuata facilmente verificando alla fine che tutto funzioni come prima.
La copertura del codice , normalmente espressa in percentuale, è la tipica metrica utilizzata per mostrare quanto del codice in un sistema è coperto dai Test unitari; si noti che non esiste una regola ferrea su quanto dovrebbe essere elevato, ma è generalmente accettato che quanto più alto è, tanto meglio.
Test Driven Development (TDD) è un principio che specifica che uno sviluppatore dovrebbe iniziare a scrivere un codice scrivendo un test dell'unità in errore e solo successivamente a scrivere il codice di produzione che fa passare il test. Quando si pratica TDD, si può affermare che i test stessi sono il primo consumatore del codice creato; quindi aiutano a controllare e guidare il design del codice in modo che sia semplice da usare e il più robusto possibile.
Versioni
Il test delle unità è un concetto che non ha numeri di versione.
Un test unitario di base
Nel modo più semplice, un test unitario consiste in tre fasi:
- Preparare l'ambiente per il test
- Esegui il codice da testare
- Convalidare il comportamento previsto corrisponde al comportamento osservato
Queste tre fasi sono spesso denominate "Arrange-Act-Assert" o "Given-When-Then".
Di seguito è riportato un esempio in C # che utilizza il framework 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);
}
}
Se necessario, una quarta fase di pulizia facoltativa riordina.
Un test unitario con dipendenza stoppia
I buoni test unitari sono indipendenti, ma il codice spesso ha dipendenze. Usiamo vari tipi di duplicati di test per rimuovere le dipendenze per i test. Uno dei doppi test più semplici è uno stub. Questa è una funzione con un valore di ritorno hard-coded chiamato al posto della dipendenza del mondo reale.
// 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
Nel codice di produzione, oneDayFromNow
chiamerebbe Date.now (), ma ciò renderebbe i test incoerenti e inaffidabili. Quindi qui lo mozziamo.
Un test unitario con una spia (test di interazione)
Lo stato di test dei test di unità classiche, ma può essere impossibile per testare correttamente i metodi il cui comportamento dipende da altre classi attraverso lo stato. Testiamo questi metodi attraverso test di interazione , che verificano che il sistema sotto test chiami correttamente i suoi collaboratori. Poiché i collaboratori hanno i loro test unitari, questo è sufficiente, e in realtà un test migliore della reale responsabilità del metodo testato. Non testiamo che questo metodo restituisca un risultato particolare dato un input, ma invece che chiama correttamente i suoi collaboratori.
// 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
Semplice test Java + JUnit
JUnit è il framework di test leader utilizzato per testare il codice Java.
La classe sotto test modella un semplice conto in banca, che addebita una penalità quando si va in overdrawn.
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
}
}
}
Questa classe di test convalida il comportamento di alcuni metodi pubblici 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);
}
}
Test unitario con parametri usando NUnit e 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);
}
}
}
Un test di base su 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)
Ora la parte di test:
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()
Un test XUnit con parametri
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;
}
}