Testowanie - testy jednostkowe

Test jednostkowy (ang. unit test) to technika testowania tworzonego oprogramowania poprzez wykonywanie testów weryfikujących poprawność działania pojedynczych elementów (jednostek) programu - np. metod lub obiektów w programowaniu obiektowym lub procedur w programowaniu proceduralnym. Testowany fragment programu poddawany jest testowi, który wykonuje go i porównuje wynik (np. zwrócone wartości, stan obiektu, wyrzucone wyjątki) z oczekiwanymi wynikami - tak pozytywnymi, jak i negatywnymi (niepowodzenie działania kodu w określonych sytuacjach również może podlegać testowaniu)

Źródło: pl.wikipedia.org

Test jednostkowy to fragment kodu, który sprawdza inny fragment kodu

Unit - najmniejsza testowalna część aplikacji

funkcja

klasa

metoda

Aksjomaty testowania

Czy warto tracić czas na pisanie testów ?

Żadnego realnego oprogramowanie nie da się przetestować całkowicie. Test nie udowadnia braku błędów, a udowadnia jedynie to, ze ich nie znaleźliśmy

Testy są czasochłonne

Testowanie jest kosztowne

Testowanie jest ryzykowne

Paradoks pestycydów

Nie wszystkie znalezione błędy zostaną naprawione (za duży koszt, brak czasu, ryzyko naprawy)

Testy są nużące

Testowanie wymaga wyobraźni i złośliwości

Czym są testy jednostkowe?

Przykład: klasa testowana

Klasa testowana

public class Kalkulator { public int Suma(int [] x) { if (x == null) throw new ArgumentNullException(); int s = 0; for(int i=1; i < x.Length ; i++) s+=x[i]; return s; } }

Przykład: klasa testowa

public class KalkulatorTest { public void SumaTest() { int[] x = { 1, 2, 3, 4 }; Kalkulator c = new Kalkulator(); int oczekiwanyWynik = 10; int aktualnyWynik = c.Suma(x); if (aktualnyWynik != oczekiwanyWynik) throw new Exception(String.Format("Test oblany: spodziewana wartosc {0}, aktualna wartosc {1}", oczekiwanyWynik, aktualnyWynik)); } public void SumaTestException() { Kalkulator c = new Kalkulator(); try { c.Suma(null); } catch (ArgumentNullException) { return; } throw new Exception("Test oblany"); } }

Przykład: środowisko uruchomieniowe

class Program { static void Main(string[] args) { KalkulatorTest test = new KalkulatorTest(); test.SumaTest(); test.SumaTestException(); Console.WriteLine("Wszystkie testy zaliczone."); } }

Przykład: VS2012 MS Test

[TestClass] public class UnitTestKalkulator { [TestMethod] public void TestSuma10() { // arrange int[] x = { 1, 2, 3, 4 }; Kalkulator c = new Kalkulator(); int oczekiwanyWynik = 10; //act int aktualnyWynik = c.Suma(x); //assert Assert.AreEqual(oczekiwanyWynik, aktualnyWynik); } }

Test-driven development

Technika zwinna (agile), zaliczana także do programowania ekstremalnego.

Test -> code -> refactor

najpierw test sprawdzający dodawaną funkcjonalność (test nieudany)

implementacja funkcjonalności (test udany)

refaktoryzacja napisanego kodu, żeby spełniał on oczekiwane standardy.

Źródło: Test-driven development Wstęp do Test Driven Development (MSDN)

Korzyści TDD i testowania

Wczesne wykrywanie błedów. Odporność na błedy regresyjne. Zmniejszenie kosztu, błędy wykryte przez autora kodu i poprawiane na bieżąco kosztują niewiele

Ułatwienie refaktoringu i zmian w kodzie pokrytym testami

Dokumentowanie i wyjaśnianie kodu. Test wyjaśnia jaką funkcjonalność realizuje jednostka kodu i jak należy jej używać.

Lepiej zaprojektowane interfejsy i API. Testy zmuszają do lepszego przemyślenia rozwiązań i dokładnego określenia jakie zadania dana metoda ma wykonywać. Testy mogą pełnić rolę specyfikacji projektowej (TDD) podobnej do UML.

Uproszczona integracja, łatwiejsze łączenie różnych fragmentów kodu poddanych wcześniej testom. Testowanie bottom-up. Jednak zbiór UT nie zastąpi testów systemowych (które najczęściej są wykonywane ręcznie)

Automatyzacja i powtarzalność (contignous integration): testy można uruchamiać regularnie o określonych porach lub na pewnych etapach produkcji. Oszczędność czasu w stosunku do ręcznego testowania.

Możliwość przetestowania funkcjonalności bez uruchamiania całego oprogramowania

Struktura testw jednostkowych

Wzorzec AAA: Arrange -> Act -> Assert

przygotowanie środowiska testowego (Fixtures)

ustawienie stanu wejściowego

uruchomienie testowanych jednostek kodu

porównanie uzyskanych wyników (Assert)

zwolnienie zasobów (Teardown)

xUnit

Wzorzec xUnit zbiór środowisk testowych (frameworks) wywodzących swoją architekturę od SUnit (Kent Beck, 1998, Smaltalk).

Assercje : zbiór funkcji i makr weryfikujących stan i zachowanie jednostek kodu. Zazwyczaj w postaci wyrażenia logicznego, które zwraca albo prawdę albo fałsz. Niepowodzenie przerywa dany test (rzucany wyjątek).

Test runner : program uruchamiający testy i raportujący wyniki

Test case : podstawowa klasa od której wywodzą się wszystkie testy

Test fixtures (test context) : zbiór warunków wymaganych do przeprowadzenia testu

Test suites : zbiór testów uruchamianych w jesdnym środowisku (fixture). Kolejność uruchomienia nie ma znaczenia.

Unit Test Frameworks

Biblioteka asercji i innych metod przydatnych przy testowaniu

Automatyzacja w procesie wytwórczym

Generatory testów, testy parametryczne

Mechanizmy izolacji: fake, mock, itd

Metryki: pokrycie kodu, ścieżek, ...

Środowiska testowe

Narzędzia i biblioteki wspierające tworzenie testów, ich organizację, automatyzację wykonywania, raportowanie

Java -> JUnit, TestNG

PHP -> PHPUnit

C -> MinUnit, CUnit, (biblioteka assert.h), cmocka

C++ -> CppUnit, Cantata, GoogleTest, Boost Test Library

.Net -> Visual Studio Unit Testing Framework (Prof./Ultimate), NUnit, xUnit.net

Python -> PyUnit

MATLAB Unit Testing Framework

Delphi -> DUnit, Fortran -> pFUnit

List_of_unit_testing_frameworks

DotNET frameworks

Unit Test

MS Test

Nunit

XUnit.net

MBUnit

Isolation

Moq The most popular and friendly mocking framework for .NET

Rhino mock

FakeItEasy The easy mocking library for .NET

NSubstitute A friendly substitute for .NET mocking frameworks

wiele innych: NMock, Typemock, ...

Przykład: MS Test

using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] public class UnitTestKalkulator { private Kalkulator _calc; [TestInitialize] public void Init() { _calc = new Kalkulator(); } [TestMethod] public void TestSuma10() { int[] x = { 1, 2, 3, 4 }; int oczekiwanyWynik = 10; int aktualnyWynik = _calc.Suma(x); Assert.AreEqual(oczekiwanyWynik, aktualnyWynik); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void TestSumaException() { _calc.Suma(null); } }

Przykład: NUnit/MBUnit

using NUnit.Framework; [TestFixture] public class UnitTestKalkulator { private Kalkulator _calc; [SetUp] public void Create() { _calc = new Kalkulator(); } [Test] public void TestSuma10() { int[] x = { 1, 2, 3, 4 }; int oczekiwanyWynik = 10; int aktualnyWynik = _calc.Suma(x); Assert.AreEqual(oczekiwanyWynik, aktualnyWynik); } [Test] [ExpectedException(typeof(ArgumentNullException))] public void TestSumaException() { _calc.Suma(null); } }

Przykład: xUnit.net

using Xunit; public class KalkulatorTest { private Kalkulator _calc; public KalkulatorTest() { _calc = new Kalkulator(); } [Fact] public void TestSuma() { int[] x = { 1, 2, 3, 4 }; int oczekiwanyWynik = 10; int aktualnyWynik = _calc.Suma(x); Assert.Equal(oczekiwanyWynik, aktualnyWynik); } [Fact] public void TestSumaException() { Assert.Throws<ArgumentNullException>(() => _calc.Suma(null)); } }

Dobrze napisane testy

Test wyłącznie pojedynczej jednostki, nie można pisać testów badających kilka funkcji naraz

Testy posinny być niezależne od siebie

Izolacja: testy niezależne od zewnętrznych zasobów, są one zastępowane makietami symulującymi ich działanie (mocq, fake, stub, ...)

Przejrzystość - testy wyjaśniają i dokumentują kod

Szybkość - testy powinny wykonywać sie szybko, to nie są testy wydajności

Konwencje nazewnicze - nazwa testu oddaje intencje

Pisz kod tak aby był łatwy do testowania

Scenariusze

Analiza ścieżek - badanie przebiegu mozliwych ścieżek od punktu początkowego do koncowego, wszystkie możliwe kombinacje rozgałęzień

Boundary test - działania w pętli pomijane lub pętla uruchamiana raz (dla wszystkicj ścieżek)

Interior test - działania we wnętrzu pętli uważa się za przetestowane, jeśli zostały wykonane wszystkie ścieżki, które są możliwe przy dwukrotnym powtórzeniu pętli

Klasy równoważniości - zbiór danych o podobnym sposobie przetwarzania. Testujemy wybrane elementy ze zbioru. na wejściu klka elementów przetwarzanych w ten sam spoób

klasy poprawności/niepoprawności - przypadki dla których przewidujemy poprawne/niepoprawne działanie
Np. {2, 4, 70) dla liczb dodatnich całkowitych

Wartości brzegowe - wewnątrz, pomiędzy lub na granicy klas równoważności
Np. {-1, 0, 2, 4, 70) dla liczb dodatnich całkowitych

Zakres dostępu

metody publiczne, bez problemu

metody chronione - nie można w bezpośredni sposób wywołać metody chronionej ze sterownika testu. Rozwiązaniem jest stworzenie klasy opakowującej dziedziczącej po badanej klasie i wywołanie chronionych metod w w dziedziczącej klasie bazowej

metody prywatne są jeszcze trudniejsze do przetestowania. Nie nie można ich wywołać bezpośrednio w kodzie. Jednak modyfikatory public, protected, private mają znaczenie wyłącznie dla kompilatora. Za pomocą mechanizmu refleksji można ominąć ograniczenia i wywołać prywatną metodę.

Czy testy jednostkowe powinny dotyczyć wyłącznie metod publicznych?

Testy funkcjonalne (czarna skrzynka), zazwyczaj wykonywanie przez osoby nie wytwarzające kodu

Testy strukturalne (biała/przezroczysta skrzynka)

Rodzaje testów

jednostkowe - pojedyńcze funkcjonalności

systemowe - testy aplikacji jako całość: bezpieczeństwo, wymagania niefunkcjonalne, wydajność

integracyjne - współpraca komponentów, testuje współdziałanie klas i modułów

testy white-box: unit, regression, integration, mutation

testy black-box: acceptance, fuzz, smoke, performance (load, stress)

Testy jednostkowe w Visual Studio

Narzędzia do generowania testów dostępne w VS 2010 ale nie w 2012/13

Tworzenie testu istniejącej metody:

Create Unit test

Wybieranie metod, dla których zostaną wygenerowane testy jednostkowe

wygenerowany nowy projekt zawierający testy jednostkowe

Automatycznie wygenerowana metoda testująca

Uruchamianie testów -> Run Tests

Raport z wykonania testów

Namespace: Microsoft.VisualStudio.TestTools.UnitTesting

Assert

ExpectedExceptionAttribute

...

Wiele rozszerzeń wspierających, np.: Resharper, TestDriven.net

UT w VS 2012/2013

Szablony projektów: Unit test C#, C++

Menu test

Uruchomienie wybranych testów

Debbugowanie wybranych testów

Pokrycie kodu

Okna: Test Explorer, Code Coverage Results

VS Test Explorer

Typy asercji

Assert Class (MSDN)

AreEqual / AreNotEqual - porównanie warości

AreSame / AreNotSame - porównanie referencji

Fail - test jednostkowy jest niezaliczony

Inconclusive - wykonanie testu jest nierozstrzygnięte

IsTrue / IsFalse - porównanie wartości true/false

IsInstanceOfType / IsNotInstanceOfType - czy dostarczona wartość nie jest podanego typu

IsNull / IsNotNull - czy dostarczona wartość nie jest NULL

CollectionAssert zestaw metod testowych dla porównywania kolekcji (np. CollectionAssert.AreEqual)

StringAssert zestaw asercji do testowania napisów

Contains

Matches / DoesNotMatch dopasowanie wyrażenia regularnego

StartsWith / EndsWith

Artybuty

TestClass określa klasę zawierającą metody testowe

Przygotowanie środowiska testowego (fixture/teardown) dla assembly, zestawu testów w klasie i pojedyńczej metody testowej

AssemblyInitialize / AssemblyCleanup

ClassInitialize / ClassCleanup

TestInitialize / TestCleanup

ExpectedException spodziewany wyjątek rzucany z testu

Description opis testu

Ignore test nie będzie uruchomiony

TestCategory pozwala grupować testy

WorkItem powiązanie testu z zadaniem (work item)

UnitTesting Namespace

Kontekst testu

Klasa TestContext przechowuje informacje, które można dostarczyć do testów

Połaczenia do danych (data row, Data Driven Unit Test)

Informacje o wykonywanym teście (np. ścieżki dostępu)

Infromacje do testowania ASP.NET, np.: URL serwera Web, dostęp do obiektu Page

Dostęp do własności TextContext (tworzona automatycznie lub należy dodać ręcznie)

Metody oznaczone atrybutem ClassInitialize i AssemblyInitialize muszą dostarczyć obiekt TextContext w argumencie

private TestContext testContextInstance; public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } }

Setup fixture

Data Driven Unit Test

Metody testowe mogą pobierać dane do testów ze źródeł dostępnych na maszynach uruchamiających testy

Atrybut DataSource określa źródło danych : bazy danych, pliki xml, tabele Exela, app.config, ...

Metoda DataRow klasy TextContext pozwala pobrać wiersz danych

[DataSource(@"Provider=Microsoft.SqlServerCe.Client.4.0; Data Source=C:\Data\MathsData.sdf;", "Numbers")] [TestMethod] public void AddIntegers_FromDataSourceTest() { var target = new Maths(); // Access the data int x = Convert.ToInt32(TestContext.DataRow["FirstNumber"]); int y = Convert.ToInt32(TestContext.DataRow["SecondNumber"]); int expected = Convert.ToInt32(TestContext.DataRow["Sum"]); int actual = target.IntegerMethod(x, y); Assert.AreEqual(expected, actual); }

Źródło: How To: Create a Data-Driven Unit Test

NUnit

xUnit dla języków .Net, początkowo port z JUnit

najpopularniejsze środowisko dla tej platformy

obecnie wersja 2.6.4 , w przygotowaniu wersja 3.0 przepisana od nowa

biblioteki: framework, mock

aplikacja uruchamiająca testy: GUI, console

Assercja: classic model vs. Constraint-Based Assert Model (NUnit 2.4)

xUnit.net

„xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit testing C#, F#, VB.NET and other .NET languages. xUnit.net works with ReSharper, CodeRush, TestDriven.NET and Xamarin.”

Zaprojektowany z myślą o maksymalnym uproszczeniu testowania

xUnit.net framework (NuGet)

xunit.runner.console uruchamianie testów w konsoli

xunit.runner.visualstudio integracja z Test Explorer w VS

Wtyczka do Resharpera: runner for xUnit.net

xUnit website
Porównanie: NUnit vs. MS Test vs. xUnit

Testy parametryzowane

Teorie w xUnit.net

[Theory] [InlineData(3)] [InlineData(5)] [InlineData(6)] public void MyFirstTheory(int value) { Assert.True(IsOdd(value)); } bool IsOdd(int value) { return value % 2 == 1; } }

w NUnit bardzo szeroki zakres parametryzacji za pomocą odpowiednich atrybutów

Techniki izolacji

Izolacja: test jednostkowy nie może wykraczać poza zakres testowanej jednostki kodu

Testy wykraczające poza ten zakres

to testy integracji

są wolniejsze

mogą się nie powieść ze względu na błedy w tych zewnętrznych modułach

Separacja od zewnętrznych procesów wymusza tworzenie kodu bardziej modularnego, prostszego w testowaniu i ponownym wykorzystaniu (dependency inversion principle, warstwy abstrakcji pomiędzy komunikującymi się modułami)

Obiekty imitujące dostęp do zewnętrznych procesów: Fake, Mock, Stub, Spies, Exstract and Override, ....

Frameworks .NET: Moq, FakeItEasy, RhinoMocks, NSubstitute, ...

Mock objects

Atrapy obiektów (mock objects) - symulowane obiekty naśladujące w kontrolowany sposób zachowanie rzeczywistych obiektów.

symulowanie połaczenia do bazy danych

symulowanie z zasobami, które mogą być niedostępne, np.: połaczenia z serwisami sieciowymi

symulowanie obiektów, które jeszcze nie powstały

symulowanie w celu zwiększenia szybkości testów

Obiekty Fake i Mock realizują wzorzec Dependency injection

Mocks, fakes i inne

Wzorce Test Doubles, różnią się przede wszystkim sposobami wykorzystania

Dummy - zwraca pewną domyślna wartość

Stub - pozwala zwracać więcej wartości, bardziej złożona logika wewnętrzna

Spy

Fake - metody tego obiektu dostarczają niezbędne dane do działania testu

Mock - to obiekt Fake zawierający w sobie assercję testową lecz nie tylko

Inne: strict mock, dynamic mock, loose mock, substitute, mole.

„no need to learn what’s the theoretical difference between a mock, a stub, a fake, a dynamic mock, etc.” (Moq project)

„all fake objects are just that — fakes. Usage determines whether they're mocks or stubs.” (FakeItEasy)

Dependency Injection

Wstrzykiwanie zależności - wzorzec projektowy i wzorzec architektury oprogramowania polegający na usuwaniu bezpośrednich zależności pomiędzy komponentami na rzecz architektury typu plug-in. Szczególna realizacja paradygmatu z odwróceniem sterowania sterowania (Inversion of Control, IoC).

Wstrzyknięcie przez : konstruktor, settery, interfejs

DI pozwala wstrzyknąć do obiektów testowanych zależności od obiektów-zaślepek (atrap, mock objects), w ten sposób TTD narzuca stosowanie dobrych praktyk

Przykład DI

public class Client { // Wewnętrzna referencja do usługi private Service service; // Wstrzyknięcie przez konstruktor Client(Service service) { this.service = service; } // Metoda w której klient korzysta z usługi (chcemy wyizolować to działanie) public double Compute(double x) { return x * service.GetSomeValue(); } }

Przykład: biblioteka Moq

// Tworzymy obiekt atrapę var mockClient = new Mock<Service>(); // Ustawiamy metodę aby zwracała symulowaną wartość mockClient.Setup(mock => mock.GetSomeValue()).Returns(1); // Wstrzykujemy atrapę do obiektu który będzie testowany var client = new Clent(mockClient.Object); // Test Assert.IsEqual(client.Compute(2), oczekiwanaWartosc);

Pokrycie kodu (code coverage)

Pokrycie kodu mierzy, ile procent kodu zostało sprawdzone przez testy jednostkowego. Przyjmuje się, że dobrze napisane testy powinny mieć pokrycie rzędu przynajmniej 70% . Różne metryki pokrycia:

Pokrycie wyrażeń (ang. statement coverage) – metryka stanowi iloraz liczby linii kodu wywołanych przez testy jednostkowe oraz całkowitej liczby linii kodu. Jeśli jakaś linia nie została pokryta, istnieje zatem ryzyko wystąpienia w niej błędu.

Pokrycie rozgałęzień (ang. branch coverage) – pokrycie nie bada linii kodu, a rozgałęzienia zrealizowane za pomocą np. instrukcji if (czy test przetestował ścieżki true i false)

Pokrycie ścieżki (path coverage) - ścieżka to unikatowa sekwencja rozgałęźień w funkcji

inne: pokrycie funkcji/metod, klas (metod w klasach)/ patch coverage

Code coverage w VS

VS2010: Test -> Edit Test Settings -> Local

Data and Diagnostic -> Code Coverag

Configure - wybór źródła danych do analizy

VS2012: Test -> Analyze Code Coverage -> All Tests/ Selected Test

Testy w Resharper

Środowisko uruchomieniowe : MS Test, NUnit, xUnit.net (wtyczka do R#)

Unit test explorer

Unit test sesions

dotTrace i DotCover

profilowanie testów dotTrace Performance

Pokrycie kodu DotCover

Testowanie mutacyjne

Testowanie mutacyjne – technika automatycznego badania, na ile dokładnie testy jednostkowe sprawdzają kod programu. Polega na automatycznym wielokrotnym wprowadzaniu małych losowych błędów w programie i uruchamianiu za każdym razem testów jednostkowych, które powinny te błędy wykryć. Testowanie mutacyjne wymaga dużej mocy obliczeniowej i dlatego dopiero od niedawna próbuje się je wykorzystywać w praktyce.

Źródło: pl.wikipedia.pl

Podsumowanie

Testy jednostkowe nie dają 100% pewności, że kod nie posiada błedów

Należy stosować je wraz z innymi testami: testy integracyjne, systemowe, obciązeniowe itd.

Nie wszystko da się przetestować: zachowania niedeterministyczne, aplikacje wielowątkowe

Na jedną linię kodu może przypadać nawet do 5 linii kodu testu - nie zawsze jest to opłacalne

Kod testujący jak każdy inny kod też zawiera błedy

Bibliografia

Testy jednostkowe w Visual Studio

Jeremy Lindblom, Unit Testing

List of unit testing frameworks

XUnit Test Patterns, Gerard Meszaros

Beginning Mocking With Moq 3 by Justin Etheredge (part 2, part 3, part 4)

Introduction to unit testing

Verifying Code by Using Unit Tests (MSDN)