[G_PRO] Basis Programmeren en OO Programmeren
DigitAP
  • Welkom
  • Inleiding
    • Benodigdheden
    • Afspraken code
    • Afspraken oefeningen
    • Nuttige extras
    • Dankwoord
    • Mee helpen?
  • Semester 1: Programming Principles
    • H1: Werken met Visual Studio
      • Introductie tot C#
      • Visual Studio en .NET Core installeren
      • Een C# project maken in Visual Studio
      • Fouten in je code
      • Je eerste stappen in C#
      • Input/Output: ReadLine/WriteLine
      • Kleuren in Console
      • Oefeningen
    • H2: Variabelen en datatypes
      • De syntaxis van C#
      • Datatypes
      • Variabelen
      • Expressies en operators
      • Oefeningen
    • H3: Strings en hun methoden
      • Strings
      • Strings samenvoegen
      • Omzetten van en naar strings
      • Functionaliteit van strings
      • Oefeningen
    • H4: Beslissingen
      • Beslissingen intro
      • Enkelvoudige booleaanse expressies
      • If, else, else if
      • Samengestelde booleaanse expressies
      • Scope van variabelen
      • Switch
      • Oefeningen
    • H5: Loops
      • Loops intro
      • While en Do While
      • For
      • Debuggen
      • Oefeningen
    • H6: Arrays
      • Array principes
      • Alternatieve syntax
      • Werken met arrays
      • Defaultwaarden
      • List<T>
      • Oefeningen
    • H7: Methoden
      • Methoden intro
      • Parameters
      • Return waarden
      • Geavanceerde methoden
      • Oefeningen
    • Intermezzo: TextCell
    • H8: Numerieke data
      • De Math klasse
      • Random
      • Casting en conversie
      • Oefeningen
    • H9: Meerdimensionaal werken
      • N-dimensionale arrays
      • Geneste iteratie
      • Oefeningen
    • H10: Gevorderde tekstverwerking
      • Voorstelling van tekst
      • Interpolatie met formattering
      • Werken met arrays van strings
      • Input en output van tekstbestanden
      • Oefeningen
    • Afsluiter: TextCell2D
  • Semester 2 : OOP
    • H10: Klassen en objecten
      • OOP Intro
      • Klassen en objecten aanmaken
      • DateTime: leren werken met objecten
      • Enumeraties: nog een eigen datatype
      • Klassen en objecten weergeven deel 1
      • Attributen
      • Methoden
      • Access modifiers
      • Properties
      • Oefeningen
    • H11: Objecten (al dan niet) aanmaken
      • Constructors
      • Spelen met strings
      • Oefeningen
    • H12: Geheugenmanagement bij klassen
      • value en reference met eigen objecten
      • nullable value types
      • NullReference exception
      • Oefeningen
    • H13: Datastructuren
      • Foreach en var
      • List
      • Dictionary
      • Immutable datastructuren
      • Verdere datastructuren
      • Oefeningen
    • H14: Overerving
      • Overerving intro
      • Virtual en override
      • Abstract
      • Constructors bij overerving
      • Oefeningen
    • H15: Geavanceerde overerving
      • protected access modifier
      • Base keyword
      • System.Object
      • Oefeningen
    • H16: Exception handling
      • Werken met exceptions
      • Zelf uitzonderingen maken
      • Wanneer exceptions en handling gebruiken
      • Oefeningen
    • H17: Polymorfisme en interfaces
      • Polymorfisme
      • Polymorfisme in de praktijk
      • Interfaces
      • Losse koppeling
      • Oefeningen
    • H18: Testing
      • Intro Testing
      • Wat is Unit Testing
      • Waarom Unit Testing?
      • Wanneer Unit Testing?
      • Schrijven van een unit test: AAA methode
      • Eerste voorbeeld: Sum
      • Assert
      • Oefening even of oneven getal
      • TestInitialize en DataRows
      • Oefening BMI
      • Exception testing
      • Oefening BMI exception
      • Oefening SchoolAdmin test null-waarden en TestCleanup
      • Oefening SchoolAdmin test equals
      • Oefening SchoolAdmin test cursus zoeken met id
      • Dependencies bij Unit Testing
      • Mocking
      • Oefeningen Mocking
      • Test Driven Development
      • Class Library
      • Oefeningen TDD
    • H19: SOLID
      • Single Responsibility Principle (SRP)
      • Open/Closed Principle (OCP)
      • Liskov Substitution Principle (LSP)
      • Interface Segregation Principle (ISP)
      • Dependency Inversion Principle (DIP)
  • Appendix
    • Visual Studio Tips & Tricks
    • Ea-ict coding guidelines
    • Oefeningen kerkhof
  • Semester 1 appendix
    • Nice to know stuff
      • Out en Ref parameters
      • Jagged arrays
    • All-In-Projecten
      • Overzicht
      • Console Matrix
      • Ascii filmpjes maken met loops
      • Ascii filmpjes maken met methoden
      • Fun with methods: een verhaalgenerator
      • Tekst-gebaseerd Maze game
      • Conway game of life
  • Semester 2 appendix
    • Operator overloading
    • Object Initializer Syntax
    • Compositie en aggregatie
    • Nice to know stuff
      • Klassen herbruiken
      • Expression bodied members
    • All-In-Projecten
      • Overzicht
      • OO Textbased Game
      • War Simulator
      • Map Maker
      • Magic The Gathering API
      • SchoolAdmin
  • Pro (geen leerstof en/of in opbouw)
    • Bitwise operators
    • Generics en collections
      • Generics methoden en types
      • Generic classes en constraints
      • Collections
      • Labo-oefeningen
    • Events
      • Events
      • Chat server
    • Software engineering
      • SOLID
Powered by GitBook
On this page
Export as PDF
  1. Semester 2 : OOP
  2. H18: Testing

Mocking

We bouwen verder op het voorbeeld dat we hebben gezien in het vorige hoofdstuk

1. Voeg een nieuwe interface toe met de naam IDie

2. We werken deze interface als volgt uit:

public interface IDie
{
    int Roll();
}

3. We passen nu onze bestaande Die klasse aan en implementeren op deze klasse de IDie interface. Verder moeten we hier niets aanpassen, omdat deze klasse al een int Roll() heeft.

public class Die : IDie //<--
{
    private static Random random = new Random();

    public int Roll()
    {
        return random.Next(6) + 1;
    }
}

4. Mijn NumberGame klasse heeft nu een "tight coupling" met Die. Deze Die klasse geeft een random getal terug. Ik wil de NumberGame klasse kunnen testen met een IDie waarbij ik zelf kan kiezen welk resultaat deze terug geeft. Ik maak gebruik van dependency injection en pas mijn NumberGame klasse als volgt aan:

public class NumberGame
{
    private IDie die; //<--
    public NumberGame(IDie die) //<--
    {
        this.die = die; //<--
    }

    public int RateGuess(int guess)
    {
        int result = die.Roll();
        if (result == guess)
        {
            return 2;
        }
        if (result - 1 == guess || result + 1 == guess)
        {
            return 1;
        }
        return 0;
    }
}

We kunnen nu via de constructor onze NumberGame klasse met eender welke dobbelsteen laten werken die IDie implementeert. Dit principe noemen we Dependency Injection.

Misschien willen we een dobbelsteen met een waarde van 1 tot 12? Of één die we visueel op het scherm zien rollen? Ik maak mijn code dus niet enkel testbaar, ik werk ook dependencies weg en zorg zo voor code die makkelijker aanpasbaar is.

5. Als we nu proberen compileren krijgen we de volgende foutmelding: "There is no argument given that corresponds to the required formal parameter 'die' of 'NumberGame.NumberGame(IDie)'"

In onze Main methode moeten we nu een IDie meegeven aan onze NumberGame klasse. Ik gebruik in mijn code de Die klasse. Alles werkt dan weer zoals het was, met dat verschil dat ik nu via de constructor van NumberGame kan bepalen welke IDie ik wil gebruiken.

public class Program
{
    static void Main(string[] args)
    {
        var numberGame = new NumberGame(new Die()); //<--
        int score = numberGame.RateGuess(5);
        Console.WriteLine($"Uw score: {score}");
    }
}

6. Mijn test werkt nu ook niet meer. Hier moet ik aan de NumberGame constructor ook een IDie meegeven. We zullen hiervoor een nieuwe klasse aanmaken.

Voeg een nieuwe klasse toe en geef deze de naam DieMock.

Deze klasse laten we IDie implementeren en moet dus een Roll methode hebben. We werken deze als volgt uit:

public class DieMock : IDie
{
    private int result;

    public DieMock(int result)
    {
        this.result = result;
    }

    public int Roll()
    {
        return result;
    }
}

Deze code lijkt op het eerste zicht misschien een beetje vreemd maar dit is exact wat we nodig hebben. Als ik een nieuwe instance wil maken van deze klasse moet in ik de constructor een getal meegeven. Dit is het resultaat dat de dobbelsteen steeds zal teruggeven.

7. Ik kan mijn test nu als volgt implementeren:

[TestMethod]
public void RateGuess_Returns_2_When_Guess_Is_Correct()
{
       var numberGame = new NumberGame(new DieMock(5)); //<--
       int result = numberGame.RateGuess(5);
       Assert.AreEqual(2, result);
 }

Nu weet ik dat de dobbelsteen 5 zal teruggeven. Nu zal de test dus steeds correct werken. Als de dobbelsteen 5 teruggeeft en ik raad 5, dan krijg ik 2 punten.

Ik kan nu ook de volgende test implementeren:

[TestMethod]
public void RateGuess_Returns_1_When_Guess_Is_Only_1_Wrong()
{
       var numberGame = new NumberGame(new DieMock(5));
       int result = numberGame.RateGuess(4);
       Assert.AreEqual(1, result);
}

En de laatste test:

[TestMethod]
public void RateGuess_Returns_0_When_Guess_Is_Wrong()
{
       var numberGame = new NumberGame(new DieMock(5));
       int result = numberGame.RateGuess(3);
       Assert.AreEqual(0, result);
 }

"Moqing"

In het bovenstaande voorbeeld maakten we een DieMock klasse. Deze klasse zal de dobbelsteen vervangen als we het systeem willen testen. Over deze klasse hebben we zelf de controle. Het zou ons heel wat werk kosten om voor elke klasse die we maken ook een Mock klasse te voorzien en in deze klasse al het nodige gedrag te coderen. Gelukkig zijn er frameworks die deze taak voor ons uitvoeren. Wij zullen Moq gebruiken.

1. Om te beginnen voegen we via NuGet de Moq package toe:

  1. Onder je test project rechter klik je op Dependencies (of rechtstreeks op je project)

  2. Klik op "Manage NuGet Packages..."

  3. Klik op browse

  4. Zoek Moq

  5. Selecteer Moq

  6. Klik op install

  7. Klik op OK

  8. De Package is nu toegevoegd aan ons test project

In plaats van onze DieMock klasse te gebruiken zullen we nu het Moq framework gebruiken om dit object aan te maken:

2. We werken onze test nu als volg uit:

[TestMethod]
public void RateGuess_Returns_2_When_Guess_Is_Correct_Moq()
{
       var die = new Mock<IDie>();
       die.Setup(x => x.Roll()).Returns(5); //<-- dit is nieuw!
       var numberGame = new NumberGame(die.Object);
       int result = numberGame.RateGuess(5);
       Assert.AreEqual(2, result);
 }

De gemarkeerde coderegel is wat vreemd gestructureerd. We gebruiken hier voor het eerst een lambda expressie. De structuur van een lambda ziet er als volgt uit (x => x.iets) of (x => iets(x)). Je moet niet x gebruiken, je kan hier eenders welke variabele voor kiezen, maar net zoals we in een for-loop meestal int i laten staan, gebruiken we x in lambda expressies. Even een voorbeeld van iets waarmee wel al bekend zijn. We kunnen op List<T> verschillende methoden aanroepen waaraan we een lambda expressie kunnen meegeven. Hier zie je een voorbeeld van code die hetzelfde doet.

List<int> numbers = new List<int>{ 1, 2, 3, 4, 5 };
//'normale' manier van foreach
foreach (var number in numbers)
{
    Console.WriteLine(number);
}
//herschreven met lambda notatie
numbers.ForEach(x => Console.WriteLine(x));
numbers.ForEach(number => Console.WriteLine(number));

Omdat die nu een Mock variabele is kunnen we Setup aanroepen. Deze methode vraagt ook voor een lambda expressie. Setup zorgt ervoor dat we een methode kunnen mocken. Om dit te doen moeten we eerst ons element selecteren: We zetten dus eerst x => x om aan te geven dat we iets willen uitvoeren.

Als we er een punt achter zetten zien we dat we alle methoden van Die te zie krijgen.

We willen Roll mocken zodat we steeds 5 terugkrijgen ipv een random waarde. Dit doen we door achter Roll().Returns(5) aan te roepen.

Dit zorgt ervoor dat als we, tijdens de test, ergens in de code Roll() aanroepen, we steeds de waarde 5 terugkrijgen!

3. Onze NumberGame klasse moet de dobbelsteen exact één keer werpen als ik de RateGuess methode aanroep. De Roll methode van de Die klasse moet dus exact één keer worden aangeroepen. We implementeren deze tests als volgt:

[TestMethod]
public void Roll_Method_Rolls_The_Die_Exactly_Once()
{
       var die = new Mock<IDie>();
       var numberGame = new NumberGame(die.Object);
       numberGame.RateGuess(5);
       die.Verify(x => x.Roll(), Times.Once);
}

Verify zorgt ervoor of onze Roll methode wordt uitgevoerd. Je kan als extra parameter meegeven of dit verschillende keren gebeurt of (minstens) 1 keer.

Times heeft verschillende opties waaruit je kan kiezen.

Je kan nog zoveel meer doen met Moq dan wat we hier laten zien (bvb in je Setup vastleggen dat je een Exception werpt wanneer je een methode aanroept om te kijken of je exceptionhandling goed is). Probeer zelf wat uit en zoek online op.

//Exception voorbeeld
die.Setup(x => x.Roll()).Throws(new Exception());
PreviousDependencies bij Unit TestingNextOefeningen Mocking

Last updated 1 year ago

We gaan niet te diep in op lambda expressions. Je moet ze wel begrijpen en kunnen toepassen in de context van Moq, maar we verwachten niet dat je nu alle code met lambda expressies gaat schrijven. Wil je meer info? Kijk gerust even , maar let erop; dit is wel wat complexer en geen verplichte leerstof!

hier