Võltsobjektide kasutamine ühiktestides

12.05.2010  |  Gunnar

Testidega alustamisel soovitan alati alustuseks kirjutada valmis ühiktestid. Need on kõige lihtsamad ning nende kirjutamisega kaasneb peamise keerukusena rakenduse parandamine ja täiendamine. Käesolevas näites tutvustan sellist asja nagu võltsobjekt (fake object) ja ühtlasi muudame ühe raskesti testitava meetodi lihtsa vaevaga testitavaks.

Mul on üks lihtne klass, mida ma kasutan ühes teises kannete seerias ja mille ma natukese siia sobivaks ümber kohendasin. Klass tegeleb sellega, et laseb juhuslikke arve antud vahemikust leida ning kontrollib, et reeglid oleksid täidetud.

public class Randomizer
{
    public static int GetRandomFromRangeContracted(int min, int max)
    {
        if(min>= max)
            throw new ArgumentOutOfRangeException("min");

        var rnd = new Random();
        var result = rnd.Next(min, max);

        if(result <min || result> max)
            throw new Exception("Return value is out of range");

        return result;
    }
}

Miks ei ole see klass testitav?

Kui me seda klassi vaatame, siis hakkab silma, et selle testimine polegi kuigi lihtne, sest Random tüüpi objektile ei saa me kuidagi ligi. See on konkreetne tüüp. See luuakse just seal samas ja et see kõikide eelduste kohaselt korrektselt töötab, siis ei saa me testida, kas meie klass annab korrektselt vigu vahemikku mittejääva juhusliku arvu korral.

Tähendab, me seisame vastakuti tüüpilise olukorraga – meil on üks vastik sõltuvus ja me peame sellest kuidagi jagu saama.

Klassi muutmine testitavaks

Lahendusekäik on järgmine:

  • defineerime liidese juhuslike arvude generaatorite jaoks,
  • anname Randomizer klassile selle liidese abil ette generaatori, mida ta oma töös peab kasutama,
  • loome generaatori, mis kasutab Random klassi ja kasutame seda programmi töös,
  • muudame programmi koodi selliselt, et see vastaks uutele nõuetele.

Meie peamine eesmärk on see, et me saaksime ise vajadusel generaatori ette anda. Sel teel kaob Randomizer klassist ära üks konkreetsest tüübist sõltuvus ja klass muutub lihtsamini testitavaks. Kood on peale muudatusi selline.

public interface IRandomGenerator
{
    int Next(int min, int max);
}

public class RandomGenerator : IRandomGenerator
{
    private Random _random = new Random();

    public int Next(int min, int max)
    {
        return _random.Next(min, max);
    }
}

public class Randomizer
{
    private IRandomGenerator _generator;

    private Randomizer() {}

    public Randomizer(IRandomGenerator generator)
    {
        _generator = generator;
    }

    public int GetRandomFromRangeContracted(int min, int max)
    {
        if(min>= max)
            throw new ArgumentOutOfRangeException("min");

        var result = _generator.Next(min, max);

        if(result <min || result> max)
            throw new Exception("Return value is out of range");

        return result;
    }
}

Võltsobjekt

Nüüd asume testide kallale. Meil on igas testis vaja anda Randomizer klassile ette generaator, mida meil aga paraku pole. Random põhine generaator, nagu eespool juttu oli, meid kuidagi ei aita. Aga et meil on olemas liides, siis defineerime testide projektis uue generaatori, mis meie liidest kasutab.

public class GeneratorFake : IRandomGenerator
{
    int _random;

    public GeneratorFake(int value)
    {
        _random = value;
    }

    public int Next(int min, int max)
    {
        return _random;
    }
}

See klass on generaatori võltsing, sest mingeid arve ta ei genereeri. Küll aga saame me selle klassi abil luua genereeritavate arvude osas just selliseid olukordi nagu me eeldame.

Testid

Argumentidele esitatud tingimuse testimine on triviaalne. Esimese asjana testime kohe selle ka ära, et normaalsetel tingimustel kõik ikka töötaks.

[TestMethod]
public void Should_return_random_value()
{
    var expectedResult = 10;
    IRandomGenerator generatorFake = new GeneratorFake(expectedResult);
    var randomizer = new Randomizer(generatorFake);

    var actualResult = generatorFake.Next(1, 20);

    Assert.AreEqual<int>(expectedResult, actualResult);
}

[TestMethod]
[ExpectedException(ArgumentOutOfRangeException)]
public void Should_throw_exception_if_min_is_not_less_than_max()
{
    IRandomGenerator generatorFake = new GeneratorFake(1);
    var randomizer = new Randomizer(generatorFake);

    randomizer.Next(100, 1);
}

Samuti on nüüd triviaalne leitud juhuslikule arvule esitatud tingimuste kontrollimine, sest võltsobjekt, tagastab meile vajadusel ka reegleid rikkuvaid väärtuseid. Tagastatava väärtuse kontrollide testimiseks kirjutame seega järgmised testid.

[TestMethod]
[ExpectedException(Exception)]
public void Should_throw_exception_if_result_is_less_than_min()
{
    IRandomGenerator generatorFake = new GeneratorFake(-200);
    var randomizer = new Randomizer(generatorFake);
   
    randomizer.Next(1, 20);
}

[TestMethod]
[ExpectedException(Exception)]
public void Should_throw_exception_if_result_is_more_than_min()
{
    IRandomGenerator generatorFake = new GeneratorFake(200);
    var randomizer = new Randomizer(generatorFake);
   
    randomizer.Next(1, 20);
}

Kui need testid käivitame, lähevad need kõik kenasti läbi ja me saame kogu meetodi põhjalikult läbi testitud.

Kokkuvõtteks

Käesolev kirjutis tutvustas tüüpilist probleemi klassidega, mis esinevad testideta koodis. Antud juhul saime testitavat klassi muutes võltsobjekti abil probleemid lahendatud. Muide, alati see ei õnnestu ja siis on meil vaja natukese teisi nõkse, millest teen juttu järgmisel teemanädalal.

Võltsobjektide loomine on mõistlik kuni liidesed, mida järgime, ei ole mahukad. Mahukamate liideste jaoks võltsklasside kirjutamine pole aga kuigi lihtne tegemine (kujutage ette liidest, mis defineerib 40 meetodi läbi teiste liideste laiendamise näiteks). Selle probleemi lahendame ära järgmises kandes ja seal tehtut saame kasutada ka selle näite juures tagasiulatuvalt.

Kommenteeri

sulge
Saada link e-postiga

© DT 2013 | Creative Commons Attribution-Noncommercial 3.0 License | WordPress