unit-testing Zelfstudie
Aan de slag met unit-testing
Zoeken…
Opmerkingen
Eenheidstesten beschrijft het proces van het testen van afzonderlijke eenheden code afzonderlijk van het systeem waarvan ze deel uitmaken. Wat een eenheid vormt, kan van systeem tot systeem variëren, variërend van een individuele methode tot een groep nauw verwante klassen of een module.
De eenheid wordt geïsoleerd van zijn afhankelijkheden met behulp van testverdubbelingen indien nodig en in een bekende toestand ingesteld. Het gedrag in reactie op stimuli (methodeaanroepen, gebeurtenissen, gesimuleerde gegevens) wordt vervolgens getest tegen het verwachte gedrag.
Eenheidstesten van complete systemen kunnen worden gedaan met behulp van op maat geschreven testharnassen, maar er zijn veel testkaders geschreven om het proces te stroomlijnen en veel van de sanitaire, repetitieve en alledaagse taken uit te voeren. Hierdoor kunnen ontwikkelaars zich concentreren op wat ze willen testen.
Wanneer een project voldoende unit-tests heeft, kan elke wijziging van het toevoegen van nieuwe functionaliteit of het uitvoeren van een code-refactoring eenvoudig worden uitgevoerd door aan het einde te verifiëren dat alles werkt zoals voorheen.
Codedekking , normaal uitgedrukt als een percentage, is de typische maatstaf die wordt gebruikt om aan te tonen hoeveel van de code in een systeem wordt gedekt door eenheidstests; merk op dat er geen harde regel bestaat over hoe hoog dit moet zijn, maar algemeen wordt aangenomen dat hoe hoger, hoe beter.
Test Driven Development (TDD) is een principe dat aangeeft dat een ontwikkelaar moet beginnen met coderen door een falende unit-test te schrijven en dan pas de productiecode te schrijven die de test doorstaat. Bij het oefenen van TDD kan worden gezegd dat de tests zelf de eerste consument zijn van de code die wordt gemaakt; daarom helpen ze bij het controleren en aansturen van het ontwerp van de code, zodat deze zo eenvoudig te gebruiken en zo robuust mogelijk is.
versies
Het testen van eenheden is een concept zonder versienummers.
Een basiseenheidstest
In het eenvoudigste geval bestaat een eenheidstest uit drie fasen:
- Bereid de omgeving voor op de test
- Voer de te testen code uit
- Valideer het verwachte gedrag komt overeen met het waargenomen gedrag
Deze drie fasen worden vaak 'Arrange-Act-Assert' of 'Gegeven-wanneer-dan' genoemd.
Hieronder is een voorbeeld in C # dat het NUnit- framework gebruikt.
[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);
}
}
Waar nodig wordt een optionele vierde schoonmaakfase opgeruimd.
Een eenheidstest met verstopte afhankelijkheid
Goede eenheidstests zijn onafhankelijk, maar code heeft vaak afhankelijkheden. We gebruiken verschillende soorten testdubbels om de afhankelijkheden voor het testen te verwijderen. Een van de eenvoudigste testdubbels is een stub. Dit is een functie met een hardgecodeerde retourwaarde die wordt aangeroepen in plaats van de werkelijke afhankelijkheid.
// 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
In productiecode, oneDayFromNow
zou Date.now () te bellen, maar dat zou te maken voor inconsistent en onbetrouwbaar testen. Dus hier stoppen we het.
Een eenheidstest met een spion (interactietest)
Classic unit test-test staat, maar het kan onmogelijk zijn om de juiste testmethoden wier gedrag afhankelijk is van andere klassen door middel van state. We testen deze methoden door interactietests , die controleren of het geteste systeem zijn medewerkers correct oproept. Omdat de medewerkers hun eigen unit-tests hebben, is dit voldoende, en eigenlijk een betere test van de feitelijke verantwoordelijkheid van de geteste methode. We testen niet dat deze methode een bepaald resultaat retourneert bij invoer, maar in plaats daarvan dat het de medewerker (s) correct aanroept.
// 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
Eenvoudige Java + JUnit-test
JUnit is het toonaangevende testraamwerk dat wordt gebruikt voor het testen van Java-code.
De klasse die wordt getest, modelleert een eenvoudige bankrekening, die een boete in rekening brengt wanneer u te laat gaat.
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
}
}
}
Deze BankAccount
valideert het gedrag van sommige openbare methoden van 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);
}
}
Eenheidstest met parameters met behulp van NUnit en 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);
}
}
}
Een eenvoudige python-eenheidstest
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)
Nu het testonderdeel:
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()
Een XUnit-test met parameters
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;
}
}