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

Aksjomaty testowania

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.

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

Korzyści TDD i testowania

Struktura testw jednostkowych

Wzorzec AAA: Arrange → Act → Assert

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

xUnit

Unit Test Frameworks

Środowiska testowe

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

List_of_unit_testing_frameworks

DotNET frameworks

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

Scenariusze

Zakres dostępu

Testy jednostkowe w Visual Studio

UT w VS 2012/2013

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

Menu test

VS Test Explorer

Typy asercji

Artybuty

UnitTesting Namespace

Kontekst testu

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

Setup fixture

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

NUnit

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

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

Xunit.net: DDT Inline

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

Xunit.net: DDT Property

[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 { return new[] { new object[] { "xUnit", 1 }, 
                     new object[] { "is fun", 2 }, 
                     new bject[]  { "to test with", 3 }  };
   }
}

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

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
}

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

Techniki izolacji

Mock objects

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

Mocks, fakes i inne

Dependency Injection

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:

Code coverage w VS


Testy w Resharper

Unit test explorer

Unit test sesions

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

Bibliografia