Automatyczne generowanie testów jednostkowych: IntelliTest

IntelliTest to wewnętrzna funkcjonalność Visual Studio (Enterprise 2015), która służy do generowania tabeli danych wejściowych oraz zestawu testów jednostkowych. Dla danej metody generowane są dane wejściowe, w oparciu których mogą zostać wygenerowane testy jednostkowe.

Przypadki testowe tworzone są w oparciu o analizę każdego skoku warunkowego (conditional branch). Co więcej, tabela przypadków testowych zawiera scenariusze, które nie zostały obsłużone w naszej logice biznesowej, a które zwracają wyjątek (np. NullReferenceException lub dzielenie przez zero).

IntelliTest współpracuje natywnie z MSTest oraz poprzez rozszerzenia z xUnit.net i NUnit.

7167.2015_2D00_09_2D00_30_2D00_IntelliTest1_5F00_5C49D3D5

Źródło obrazka: http://blogs.msdn.com/b/visualstudio/archive/2015/09/30/intellitest-for-net-test-more-with-less-effort.aspx

Historia projektu

Zanim powstał IntelliTest, Microsoft stworzył i przemianował kilka innych narzędzi:

Pex (Visual Studio 2010)

Pex (http://research.microsoft.com/en-us/projects/pex/) to dziecko Microsoft Research, które posłużyło jako prototyp IntelliTestu. Visual Studio 2010 Power Tools zawiera dwa narzędzia do testowania: Pex i Moles. Pex generuje tablicę wartości we-/wyjściowych i pozwala na automatyczne stworzenie testów jednostkowych. (Moles zmienił nazwę Fakes; jest narzędzie do izolacji kodu.)

Pex dostępny jest w wersji online na stronie: http://www.pexforfun.com/ i jako aplikcja dla Windows Phone.

Wersja online zawiera samouczek (w tym samouczek języka C#/VB/F#), quizy do rozwiązania i coding duel.

pex

Code Digger (Visual Studio 2012, 2013)

Code Digger (http://research.microsoft.com/en-us/projects/codedigger/) to narzędzie z silniczkiem Peksa służącym do generowania testów dla PCL (Portable Class Libraries).

Smart Unit Tests (pre-RC Visual Studio 2015)

Przed Release Candidate Visual Studio 2015, IntelliTest miało nazwę “Smart Unit Tests”.

Przykład—Kalkulator BMI

Aby zobrazować działanie IntelliTest, posłużmy się algorytmem wyliczającym BMI.
Nasz kod bazowy wygląda następująco:

public class BmiCalculator
{
    public BmiKind Calculate(int massInKg, int heightInCentimetres)
    {
        decimal heightInMetres = heightInCentimetres / 100m;
        decimal bmi = massInKg / (heightInMetres * heightInMetres);

        if (bmi < 18.5m) return BmiKind.Underweight;
        if (bmi < 25) return BmiKind.Normal;
        if (bmi < 30) return BmiKind.Overweight;
        if (bmi >= 30) return BmiKind.Obese;

        throw new InvalidOperationException();
    }
}

public enum BmiKind
{
    Underweight = 0,
    Normal = 1,
    Overweight = 2,
    Obese = 3
}

W menu kontekstowym Visual Studio widnieją dwie opcje dla IntelliTest: Run i Create IntelliTest.

1. Run IntelliTest – Analiza przypadków dla danej metody/klasy, które otwiera okno “IntelliTest Exploration Results”:

intellitest1

Powyższa analiza została uruchomiona na naszym kodzie wyliczającym indeks BMI. (Jako że IntelliTest gorzej radzi sobie z liczbami zmiennoprzecinkowymi, wzrost osoby podawana jest w centrymetrach, typ tej zmiennej to int.)

W naszym przypadku analiza poradziła sobie bezproblemowo z wszystkimi przypadkami testowymi. Udało się pokryć logiczną zawartość naszej metody. IntelliTest wygenerował zestaw wartości, który zwraca każdą wartość enum BmiKind.

Uwagi:

  • IntelliTest wskazał wartości wejściowe które nie zostały obsłużone w naszym kodzie. W tym przypadku jest to zestaw wartości (waga: 0 kg, wzrost: 0 cm), które prowadzą do wyrzucenia wyjątku DivideByZeroException.
  • Wyrzucenie wyjątku InvalidOperationException w ostatniej linii metody Calculate nie zostało pokryte, gdyż ten kod jest nieosiągalny.

Zobaczmy co się stanie jeśli wprowadzimy ograniczenia co zmiennych okreś.

Wprowadźmy na początek metody warunki początkowe:

const int MinMass = 40;    // kg
const int maxMass = 635;   // kg
if (massInKg <= MinMass || massInKg > maxMass) throw new ArgumentOutOfRangeException();

const int minHeight = 50;  // cm
const int maxHeight = 272; // cm
if (heightInCentimetres <= minHeight || heightInCentimetres > maxHeight) throw new ArgumentOutOfRangeException();

W wyniku otrzymamy następującą tabelę wartości we-/wyjścia:

intellitest2

W dalszym ciągu zestaw wartości wejściowych pokrywa się z każdą wartością enum BmiKind. Obsłużony został także przypadek dzielenia przez zero, stąd IntelliTest nie zwraca nam uwagi na ten problem.

2. Druga opcja to Create IntelliTest – Stworzenie automatycznych testów jednostkowych:

intellitest-create

Przykładowy kod wygenerowany przez IntelliTest dla wartości wejściowych: masa = 41 kg, wzrost = 51 cm wraz z oczekiwanym rezultatem = BmiKind.Obese wygląda następująco:

[TestMethod]
[PexGeneratedBy(typeof(BmiCalculatorTest))]
public void Calculate186()
{
    BmiKind i;
    global::BmiCalculator.BmiCalculator s0
       = new global::BmiCalculator.BmiCalculator();
    i = this.Calculate(s0, 41, 51);
    Assert.AreEqual<BmiKind>(BmiKind.Obese, i);
    Assert.IsNotNull((object)s0);
}

Wnioski

Do czego w praktyce może przydać się IntelliTest? Można wyróżnić dwie dziedziny:

  • Legacy code: Dla zastanego kodu, IntelliTest można wykorzystać w celu automatycznego pokrycia kodu testami jednostkowymi. Główna zaleta takiego podejścia to oczywiście szybkość i pokrycie bez potrzeby refaktoryzacji. I jeśli chodzi o refaktoryzację, automatycznie wygenerowane testy dają nam pewien poziom bezpieczeństwa przed późniejszą refaktoryzacją i zmianami w kodzie legacy.
  • Nowy kod: IntelliTest ma dwie zasadnicze wady. Po pierwsze, wygenerowany kod nie jest idealny i ma sporo szumu. Po drugie, nie mamy gwarancji że pokrycie logiczne będzie równe 100%. W związku z tym dla nowo pisanego kodu narzędzie można wykorzystać do eksplorowania zachowań na podstawie danych we-/wyjściowych.

Przykład BMI obrazuje zachowanie IntelliTest dla dwóch parametrów wejściowych, ktróre są liczbą całkowitą. IntelliTest radzi sobie oczywiście też z innymi przypadkami, m.in. liczbami zmiennoprzecinkowymi, obiektami, pętlami, wyrażeniami regularnymi. Dla chcących sprawdzić sposób zachowania, polecam odwiedzenie wyżej wspomnianego Pex for Fun: http://pexforfun.com/.

Linki do rozszerzeń

Źródła

 

2 thoughts on “Automatyczne generowanie testów jednostkowych: IntelliTest

  1. Fajny feature, ale niestety nie mogę znaleźć jak sobie radzi z własnymi typami. Przykładowo jako argument wejściowy jest obiekt z innego projektu. Jedyne, co wygeneruje, to null zamiast tego obiektu. Jest warning bez możliwości fix’a, że nie wie co to za typ.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s