→ Slide 1

Testy jednostkowe

→ Slide 2

Unit test - co to jest?

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, obiektów.

Testowany fragment programu poddawany jest testowi, który wykonuje go i porównuje wynik z oczekiwanymi wynikami.

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

↓ Slide 3

Aksjomaty testowania

↓ Slide 4

Czym są testy jednostkowe?

↓ Slide 5

Przykład: 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;
	}
}
↓ Slide 6

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($"Test oblany: spodziewana wartosc {oczekiwanyWynik}, aktualna wartosc {aktualnyWynik}");
 
	}
}
public class KalkulatorTest
{
	public void SumaTestException()
	{
		Kalkulator c = new Kalkulator();
		try {
			c.Suma(null);
		} 
                catch (ArgumentNullException) {
			return;
		}
		throw new Exception("Test oblany");
	}
}
↓ Slide 7

Przykład: środowisko uruchomieniowe

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

Porażka

Sukces

↓ Slide 8

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);
    }
}
→ Slide 9

Test-driven development (TDD)

Technika zwinna (agile), wywodząca się z programowania ekstremalnego.

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

↓ Slide 10

Korzyści TDD i testowania

→ Slide 11

Wzorce w testowaniu jednostkowym

Wzorzec AAA: Arrange → Act → Assert

  1. przygotowanie środowiska testowego (Fixtures)
  2. ustawienie stanu wejściowego
  3. uruchomienie testowanych jednostek kodu (Act)
  4. porównanie uzyskanych wyników (Assert)
  5. zwolnienie zasobów (Teardown)
↓ Slide 12

xUnit

→ Slide 13

Unit Test Frameworks

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

↓ Slide 14

Środowiska testowe

Wikipedia: List of unit testing frameworks

↓ Slide 15

.NET frameworks

↓ Slide 16

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);
	}
}
↓ Slide 17

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);
	}
}
↓ Slide 18

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));
   }
}
→ Slide 19

Dobrze napisane testy

↓ Slide 20

Scenariusze

↓ Slide 21

Zakres dostępu

↓ Slide 22

Testy prywatnych metod

Klasa PrivateObject pozwala opakować klasę zawierającą prywatne elementy do testowania. Dostęp do prywatnych elementów za pomocą metody Invoke()

using Microsoft.VisualStudio.TestTools.UnitTesting;
 
[TestMethod]
public void TestPrywatnejMetody()
{
   var obj = new PrivateObject(typeof(KlasaZPrywatnąMetodą));
   var wynik = obj.Invoke("PrywatnaMetoda");
   Assert.AreEqual(wynik, spodziewanyWynik);
}
→ Slide 23

Rodzaje testów

→ Slide 24

Testy jednostkowe w VS

Szablony projektów: Unit Test Project, Visual C#, C++

↓ Slide 25

Generowanie metod testowych

↓ Slide 26

↓ Slide 27

VS Test Explorer

↓ Slide 28

Typy asercji

↓ Slide 29

Artybuty

UnitTesting Namespace

↓ Slide 30

Kontekst testu

↓ Slide 31

Setup fixture

→ Slide 32

NUnit

↓ Slide 33

GUI

↓ Slide 34

CLI

→ Slide 35

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

Pakiety NuGet

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

→ Slide 36

Data Driven Unit Test

[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

→ Slide 37

Xunit.net: Teorie

[Theory]
[InlineData(3)]
[InlineData(5)]
[InlineData(6)]
public void MyFirstTheory(int value)
{
    Assert.True(IsOdd(value));
    }
 
    bool IsOdd(int value)
    {
        return value % 2 == 1;
    }
}
[Theory]
[PropertyData("SplitCountData")]
public void SplitCount(string input, int expectedCount)
{
     var actualCount = input.Split(' ').Count();
     Assert.Equal(expectedCount, actualCount);
}
 
public static IEnumerable<object[]> SplitCountData
{
   get
   {
      // Or this could read from a file. :)
      return new[]
      {
         new object[] { "xUnit", 1 },
         new object[] { "is fun", 2 },
         new object[] { "to test with", 3 }
      };
   }
}

Zródło: Theory DDT, Tom DuPont

→ Slide 38

Skip i Timeout

[Fact(Skip="Trait Extensibility is not working in 1654"), Category("Slow Test")]
public void LongTest()
{
    Thread.Sleep(500);
}
 
[Fact, Trait("Category", "Supa")]
public void LongTest2()
{
   Assert.True(true); 
}
[Fact(Timeout=50)]
public void TestThatRunsTooLong()
{
    System.Threading.Thread.Sleep(250);
}
→ Slide 39

AutoFixture

[TestMethod]
public void IntroductoryTest()
{
   // Fixture setup
   Fixture fixture = new Fixture();
 
   int expectedNumber = fixture.Create<int>();
   MyClass sut = fixture.Create<MyClass>();
   // Exercise system
   int result = sut.Echo(expectedNumber);
   // Verify outcome
   Assert.AreEqual<int>(expectedNumber, result, "Echo");
   // Teardown
}
→ Slide 40

AutoData

[Theory, AutoData]
public void IntroductoryTest(int expectedNumber, MyClass sut)
{
   int result = sut.Echo(expectedNumber);
   Assert.Equal(expectedNumber, result);
}
→ Slide 41

Techniki izolacji

↓ Slide 42

Mock objects

↓ Slide 43

Mocks, fakes i inne

↓ Slide 44

Dependency Injection

↓ Slide 45

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();
	}
}
↓ Slide 46

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);
→ Slide 47

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

↓ Slide 48

Metryki pokrycia

↓ Slide 49

Code coverage w VS

↓ Slide 50

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

→ Slide 51

Testy w Resharper

↓ Slide 52

Unit test explorer

↓ Slide 53

Unit test sesions

↓ Slide 54

dotTrace i DotCover

→ Slide 55

Integracja z TFS

→ Slide 56

Podsumowanie

→ Slide 57

Bibliografia