|

Unit tests in de middelbare school

Een tijdje geleden was ik mijn neefje aan het helpen met het vak "programmeren" aan de middelbare school.
Wat me opviel was, dat leerkrachten bij het geven van oefeningen nog steeds werken met het "uitvoeren van het programma via de console".
In deze post wil ik graag toelichten hoe en waarom er beter zou overgeschakeld worden op het gebruik van unit tests. Laat ons de Java programmeertaal als voorbeeld nemen.
Stel dat we een programma moeten schrijven met als doel het berekenen van de (tussentijdse) totaalscore bij een spelletje bowling.
We willen de scores van enkele individuele worpen als argumenten aan het programma meegeven.
Via het gebruik van de command line, zou je je programma dan typisch als volgt uitvoeren:

java Bowling 7 9 0 5 9 

Met het oog op "leren programmeren", lijkt me dit niet de ideale manier...

Focus

Bij het aanleren van de eerste stappen in het programmeren, vermoed ik dat leerkrachten graag willen focussen op de fundamentals: variables, conditionals, loops, enz...
De boiler plate code die nodig is om een programma via de console uit te voeren, zorgt voor extra ruis en leidt de aandacht af van deze fundamentals.

  • Je moet een main methode voorzien, waarbinnen alles plaats vindt. Ik kan me voorstellen dat hier dan bij wordt verteld dat het nog niet nodig is om te weten wat dit exact betekent, maar dat het gewoon zo moet.
public class Bowling {

    public static void main(String[] args) {
        // code komt hier
    }

}

  • Je moet code toevoegen om alle benodigde argumenten in te lezen en om te zetten naar het gewenste type.
    Integer worp1 = Integer.parseInt(args[0]);
    Integer worp2 = Integer.parseInt(args[1]);
    ...

  • Je moet code toevoegen om je resultaat weg te schrijven naar de console. Vaak hoort hier dan een zinnetje bij om het resultaat tastbaarder te maken of wat aan te kleden.
    Integer totaleScore = ...;
    System.out.println("De totale score van alle worpen is: " + totaleScore);

Bovendien moet er steeds geswitcht worden tussen ontwikkelomgeving en command line.

Dit wordt allemaal overbodig, als we simpelweg zouden werken met functies:

public class Bowling {

    Integer berekenTotaal(Integer... worpen) {
        // code komt hier
    }

}

  • Je hoeft geen "vreemde main" methode te introduceren
  • Je kan de nodige argumenten meteen correct typeren
  • Je hoeft niets te kennen over System streams om een resultaat terug te geven

Maar hoe kunnen leerlingen zo'n functie dan oproepen?

Unit tests

Volgens mij kan het gebruik van unit tests hier helpen.
Een unit test is een geautomatiseerde test waarmee je een klein stukje geïsoleerde broncode kan uitvoeren en aftesten.
Vanuit een unit test kan je dan rechtstreeks de gewenste functie van een klasse aanroepen.

Elke moderne programmeertaal biedt een goede ondersteuning voor unit testing, vaak dmv libraries.
In Java is JUnit de meest gekende unit testing library. Een JUnit test ziet er dan als volgt uit:

public class BowlingTest {

    @Test
    public void testBerekenTotaal() {
        new Bowling().berekenTotaal(7, 9, 0, 5, 9);
    }

}

Unit tests schrijf je gewoon in dezelfde ontwikkelomgeving als waar je je broncode schrijft.
Unit tests kan je ook uitvoeren vanuit deze ontwikkelomgeving, dus je hoeft niet te switchen tussen ontwikkelomgeving en command line. Belangrijker nog dan het wegvallen van veel ruis, zijn de voordelen die het werken met unit tests ons biedt:

Validatie van correctheid

Bij het gebruik van de command line kan je slechts visueel en tijdelijk de correctheid van je code valideren.
Het is een momentopname, waarbij de leerling letterlijk de output van zijn of haar programma naleest en interpreteert of deze output al dan niet correct is.

"De totale score van alle worpen is: 30"

Deze validatie kan je nu vastleggen in een unit test:

    @Test
    public void testBerekenTotaal() {
        Integer totaleScore = new Bowling().berekenTotaal(7, 9, 0, 5, 9);
        assertThat(totaleScore).isEqualTo(30);
    }

De unit test kan makkelijk meermaals worden uitgevoerd.
Je ontwikkelomgeving geeft je feedback over het al dan niet slagen van de test.


![unit test failed](/assets/uploads/blog/2020/unit-tests-in-de-middelbare-school/unit-test-failed.png "unit test failed")

De leerkracht kan ook zelf een testset opbouwen om de opdracht van de leerlingen op correctheid te valideren.
Of de leerkracht kan als hulpmiddel een vooraf opgebouwde testset aanbieden aan de leerlingen.
Het kan hen helpen om te weten of ze hun opdracht correct hebben afgewerkt.
Of het kan een manier zijn om een groter probleem op te delen in deelproblemen en deze dan één voor één aan te pakken.

Regressie

Wat ik ook merk is dat het vaak misloopt wanneer er in de opdrachten verschillende requirements verwerkt zitten.

Als een speler een strike gooit, wordt de score van de volgende 2 worpen verdubbeld.
Als een speler een spare gooit, ....

Wat er dan vaak gebeurt is dat een leerling code schrijft tot er voldaan is aan de eerste vereiste.
Hij of zij voert het programma eenmalig uit met de gepaste argumenten en valideert het resultaat.

> java Bowling 10 6 3
"De totale score van alle worpen is: 28"

Daarna wordt de code aangepast om het juiste resultaat te bekomen voor een andere vereiste.
Ook nu weer wordt het programma uitgevoerd en het resultaat visueel gevalideerd.

> java Bowling 6 4 6 3
"De totale score van alle worpen is: 25"

Maar de leerling vergeet nu om te testen of het programma nog wel correct werkt voor de eerste vereiste!
Als de code niet meer correct werkt, noemen we dit "regressie".
Met behulp van unit tests echter, kan de leerling stap voor stap een testset opbouwen, die als vangnet dient in het geval dat regressie zou optreden.

    @Test
    public void testBerekenTotaal_AlsErEenStrikeGeworpenIs_DanVerdubbeltDeScoreVanDeVolgendeTweeWorpen() {
        Integer totaleScore = new Bowling().berekenTotaal(10, 6, 3);
        assertThat(totaleScore).isEqualTo(28);
    }

    @Test
    public void testBerekenTotaal_AlsErEenSpareGeworpenIs_DanVerdubbeltDeScoreVanDeVolgendeWorp() {
        Integer totaleScore = new Bowling().berekenTotaal(6, 4, 6, 3);
        assertThat(totaleScore).isEqualTo(25);
    }

Functies, functies, functies

Van leerkrachten hoor ik dat het introduceren van functies wel eens wat stroef verloopt.
Het is weer iets nieuws en vreemds dat er bovenop komt en dat de leerlingen moeten kunnen plaatsen.
Door onmiddellijk met functies te starten, zal het voor leerlingen als iets heel natuurlijks aanvoelen, en is het principe van functie-aanroepen en argumenten doorgeven veel sneller te vatten.

Test Driven Development

Test Driven Development (TDD) is een bekende methodiek waarbij requirements gecapteerd worden in geautomatiseerde tests.
De tests worden éérst geschreven, en de ontwikkeling wordt op die manier gedreven vanuit de testset.
Later dienen deze tests ook als documentatie voor het gedrag van je code.

We merken dat het voor pas afgestudeerden vaak lastig is om op deze manier te denken, zeker als ze nog geen ervaring hebben met het schrijven van testen.

Hoe kan je immers testen schrijven voor code die er nog niet is?

Door leerlingen reeds vertrouwd te maken met geautomatiseerde tests, kan de stap naar TDD alvast verkleind worden.

Hulp nodig?

Wil je deze aanpak graag uittesten in je klas, maar heb je graag wat hulp?
Aarzel niet om ons te contacteren, dan komen we er samen vast wel uit!

Je kan ons vinden op twitter, facebook, linkedin of instagram.

Veel testplezier!

Know more?

Would you like to know more about us? Do you want to try a new idea with us?

Contact us

Share this post