→ 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

↓ 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

Testy jednostkowe w VS

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

↓ Slide 24

Generowanie metod testowych

↓ Slide 25

↓ Slide 26

VS Test Explorer

↓ Slide 27

Typy asercji

↓ Slide 28

Artybuty

UnitTesting Namespace

↓ Slide 29

Kontekst testu

↓ Slide 30

Setup fixture

→ Slide 31

NUnit

↓ Slide 32

GUI

↓ Slide 33

CLI

→ Slide 34

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 35

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 36

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 37

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 38

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 39

AutoData

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

Techniki izolacji

↓ Slide 41

Mock objects

↓ Slide 42

Mocks, fakes i inne

↓ Slide 43

Dependency Injection

↓ Slide 44

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 45

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 46

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 47

Metryki pokrycia

↓ Slide 48

Code coverage w VS

→ Slide 49

Testy w Resharper

↓ Slide 50

Unit test explorer

↓ Slide 51

Unit test sesions

↓ Slide 52

dotTrace i DotCover

→ Slide 53

Integracja z TFS

→ Slide 54

Podsumowanie

→ Slide 55

Bibliografia