unit-testingAan de slag met unit-testing


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 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;
    }
}
 

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 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 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
 

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.

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);
        }
    }
}