unit-testing Tutorial
Erste Schritte mit dem Komponententest
Suche…
Bemerkungen
Unit-Tests beschreibt den Prozess, bei dem einzelne Codeeinheiten isoliert von dem System geprüft werden, zu dem sie gehören. Was eine Einheit ausmacht, kann von System zu System variieren und von einer einzelnen Methode bis zu einer Gruppe eng verwandter Klassen oder eines Moduls reichen.
Die Einheit wird von ihren Abhängigkeiten mit Hilfe von Testverdopplungen bei Bedarf isoliert und in einen bekannten Zustand versetzt. Ihr Verhalten als Reaktion auf Reize (Methodenaufrufe, Ereignisse, simulierte Daten) wird dann anhand des erwarteten Verhaltens getestet.
Unit-Tests für ganze Systeme können mit benutzerdefinierten schriftlichen Test-Kabeln durchgeführt werden. Es wurden jedoch viele Test-Frameworks geschrieben, um den Prozess zu rationalisieren und einen Großteil der Aufgaben im Bereich Sanitär-, Wiederholungs- und Alltagsaufgaben zu erledigen. Dadurch können sich die Entwickler auf das konzentrieren, was sie testen möchten.
Wenn für ein Projekt genügend Unit-Tests vorhanden sind, können Änderungen am Hinzufügen neuer Funktionen oder an einem Code-Refactoring problemlos durchgeführt werden, indem am Ende überprüft wird, dass alles wie zuvor funktioniert.
Code Coverage , normalerweise in Prozent ausgedrückt, ist die typische Metrik, die verwendet wird, um zu zeigen, wie viel Code in einem System von Unit Tests abgedeckt wird. Beachten Sie, dass es keine strenge Regel gibt, wie hoch dies sein sollte. Es wird jedoch allgemein akzeptiert, dass je höher, desto besser.
Test Driven Development (TDD) ist ein Prinzip, das vorsieht, dass ein Entwickler mit dem Codieren beginnen soll, indem er einen fehlerhaften Unit-Test schreibt und erst dann den Produktionscode schreibt, der den Test bestanden hat. Beim Üben von TDD kann gesagt werden, dass die Tests selbst der erste Benutzer des erstellten Codes sind. Sie helfen daher, das Design des Codes zu überprüfen und voranzutreiben, damit er so einfach zu verwenden und so robust wie möglich ist.
Versionen
Unit Testing ist ein Konzept, das keine Versionsnummern hat.
Ein grundlegender Unit-Test
Im einfachsten Fall besteht ein Komponententest aus drei Stufen:
- Bereiten Sie die Umgebung für den Test vor
- Führen Sie den zu testenden Code aus
- Überprüfen Sie, ob das erwartete Verhalten mit dem beobachteten Verhalten übereinstimmt
Diese drei Phasen werden häufig als Arrange-Act-Assert oder Gegeben-Wann-Dann-Zustand bezeichnet.
Unten ist ein Beispiel in C #, das das NUnit- Framework verwendet.
[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);
}
}
Bei Bedarf wird eine optionale vierte Reinigungsstufe aufgeräumt.
Ein Unit-Test mit abgestumpfter Abhängigkeit
Gute Unit-Tests sind unabhängig, aber Code hat oft Abhängigkeiten. Wir verwenden verschiedene Arten von Testverdopplungen , um die Abhängigkeiten für das Testen zu entfernen. Einer der einfachsten Test-Doubles ist ein Stub. Dies ist eine Funktion mit einem hart codierten Rückgabewert, der anstelle der realen Abhängigkeit aufgerufen wird.
// 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 Produktionscode oneDayFromNow
nennen würde Date.now (), aber das wäre für widersprüchlich und unzuverlässig Tests machen. Also hier stummeln wir es raus.
Ein Unit-Test mit einem Spion (Interaktionstest)
Bei klassischen Einheitentests wird der Status der Tests geprüft. Es kann jedoch unmöglich sein, Methoden richtig zu testen, deren Verhalten von anderen Klassen durch den Status abhängt. Wir testen diese Methoden durch Interaktionstests , mit denen sichergestellt wird, dass das getestete System seine Mitarbeiter korrekt aufruft. Da die Mitarbeiter ihre eigenen Komponententests haben, ist dies ausreichend und eigentlich ein besserer Test der tatsächlichen Verantwortung der getesteten Methode. Wir testen nicht, ob diese Methode bei einer Eingabe ein bestimmtes Ergebnis zurückgibt, sondern ruft die entsprechenden Mitarbeiter korrekt auf.
// 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
Einfacher Java + JUnit-Test
JUnit ist das führende Testframework zum Testen von Java-Code.
Die getestete Klasse modelliert ein einfaches Bankkonto, das eine Strafe verlangt, wenn Sie überzogen werden.
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
}
}
}
Diese BankAccount
überprüft das Verhalten einiger öffentlicher Methoden von 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);
}
}
Komponententest mit Parametern mit NUnit und 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);
}
}
}
Ein grundlegender Python-Unit-Test
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)
Nun zum Testteil:
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()
Ein XUnit-Test mit Parametern
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;
}
}