Inleiding Software Engineering! Unit Testing, Contracten, Debugger!
!
13 Februari 2014!
Beknopte info over Unit Testing en Contracten kan je vinden op het einde van dit document.!
!
Eclipse beschikt over een handige debug-perspective. Maak voor dit practicum gebruik van de debug functionaliteit van Eclipse.!
!
Google C++ Testing Framework is een framework dat je in staat stelt om je code efficiënt te testen. Er bestaat geen plugin voor Eclipse voor het Google C++ Testing Framework, maar via een omweg je kan je het integreren in Eclipse.!
!
We zullen ook gebruik van maken van eenvoudige macroʼs om het werken met contracten aan te leren.!
!
Oefening 1: Installeer Het Testing Framework! Volg de stappen beschreven in SE1BAC - Laad TicTacToe in Eclipse, om je testing framework op te zetten en een nieuw project aan te maken. ! http://ansymo.ua.ac.be/inleiding-software-engineering-1e-bac/studiemateriaal/se1baclaad-tictactoe-eclipse!
!
Volg volgende tutorial: http://www.ibm.com/developerworks/aix/library/augoogletestingframework.html?ca=drs-#list1!
!
Oefening 2: Wat zou je testen?! Lees eerst de uitleg in Appendix a over Unit Testing.! a) Beschouw de volgende functie:!
!
int largest(vector
list){! ! /* code */! }!
!
Schrijf minstens 5 testen in het Google Test framework voor deze functie.! Denk aan Algemene correctheid, Rand gevallen en Fouten.!
!
Beschouw volgende (foute) implementatie van bovenstaande functie. Hoeveel van de testen die je hiervoor hebt geschreven falen hierop? In Appendix c kan je een implementatie vinden die beter werkt!
!
int largest(vector list){! int max = numeric_limits::max();! for(int i = 0; i < list.size() - 1; i++){! if(list[i] > max){! max = list[i];! }! }! return max;! }!
! ! !
b) Beschouw de volgende klasse:! Schrijf testen in het Google Test framework voor iedere methode van deze klasse.! //! // Fighterplane.cpp! //! // Created by Quinten Soetens on 13/02/14.! // Copyright (c) 2014 Quinten Soetens. All rights reserved.! //!
! !!
#include !
class Fighterplane{! private:! std::string callsign;! int x; //positional coordinate! int y; //positional coordinate! int z; //positional coordinate! int ammo; //keeps track of the number of missiles! int health; //the remaining hitpoints the plane still has. When this reaches 0 the plane is dead!! public:! //Default constructor will place a new plane with no name at location (0,0,0)! Fighterplane() {! this->callsign = "";! this->x = 0;! this->y = 0;! this->z = 0;! }! ! //constructor to set a plane on a certain location.! //ammo and healt are automaticalle initialized to 10 and 20 respectively! Fighterplane(std::string callsign, int x, int y, int z) : ammo(10), health(20){! this->callsign = callsign;! this->x = x;! this->y = y;! this->z = z;! }! ! std::string getCallSign(){! return callsign;! }! ! //Getter to obtain positional coordinate! int getCoords(){! return x;! }! //Getter to obtain positional coordinate! int getY(){! return y;! }! //Getter to obtain positional coordinate! int getZ(){! return z;!
}! ! //Method that allows a plane to move to a new location.! void moveTo(int x, int y, int z){! this->x += x;! this->y += y;! this->z += z;! }! ! //Method to attack another plane.! void attackPlane(Fighterplane* target){! this->ammo -= 1;! target->health -= 5;! }! ! //Prints all the information about this fighterplane to a file.! void printToFile(std::string filename){! std::ofstream outfile;! outfile.open (filename);! outfile << "Information about Fighterplane: \n";! outfile << "Callsign: " + callsign + "\n";! outfile << "Position: (" << x << "," << y << "," << z << ")\n";! outfile << "Health: " << health << "\n";! outfile << "Remaining ammo: " << ammo << "\n";! outfile.close();! }! };!
!! Oefening 3: Pre- en Post Condities! ! Lees de uitleg in Appendix b over Contracten! !
a) Beschouw de functie uit oefening 2a:! Bedenk geschikte pre- en postcondities voor deze functie.!
!
b) Beschouw de klasse in Oefening 2b:! Bedenk geschikte contracten (pre- en postcondities) voor iedere methode in die klasse.!
!
Gebruik de header file: http://ansymo.ua.ac.be/system/files/uploads/courses/SE1BAC/ DesignByContract.h om precondities (REQUIRE) en postcondities (ENSURE) te implementeren. !
! !
Appendix a: Unit Testing! Software Testing heeft als doel het evalueren van een bepaalde eigenschap of functionaliteit van een programma of een systeem en te verifiëren dat dit overeenkomt met het verwachtte resultaat. Er zijn verschillende soorten van Software Testing:! • Unit Testing! • Verifieert de functionaliteit van een specifiek deeltje code, meestal op een ! ! enkele klasse, of een enkele methode.! • Integration Testing! • Testen van de samenwerking van enkele modules.! • System Testing! • Testen van het volledig geïntegreerde systeem. ! • System Integration Testing! • Testen van de samenwerking tussen verschillende systemen.!
!
Een unit test is code die een heel klein specifiek gedeelte van de functionaliteit van een systeem gaat uittesten. In de meeste gevallen gaat een test één methode uitvoeren in een bepaalde context. Dit wil natuurlijk niet zeggen dat elke methode maar door één test mag getest worden. Neen, je gaat verschillende testen schrijven die eenzelfde methode oproepen in verschillende contexten.!
!
Een goede Unit Test is:! • Herhaalbaar! • Elke keer dat de test wordt uitgevoerd, moet deze hetzelfde resultaat bekomen. (vermijd randomness, het gebruik van “current time”, etc …)! Onafhankelijk! • • Test één methode per keer. ! • Verschillende testen mogen niet afhankelijk zijn van elkaar.! • Waardevol! • Het testen van eenvoudige getter/setter methodes is niet echt waardevol.! • Grondig! • Test ALLE pre/post condities, randgevallen, …! Om grondig te testen, moet je een aantal dingen nakijken:! • Algemene correctheid! • Dit zijn meestal de meest eenvoudige testen om te schrijven. Deze testen het algemeen “goed” gedrag van de use cases.! • Rand gevallen! • Orde?! • Heeft een andere volgorde een effect op het resultaat?! Bereik?! • • Hoe reageert het op nul, het minimum, het maximum, positieve waarden, negatieve waarden, …..! • Bestaat het?! • Wat als je een nullptr meegeeft als parameter?! • Wat met lege verzamelingen (lege array, lege list, lege vector, …), wat met lege Strings?! • Kardinaliteit?! • Wat is het verwachtte aantal items?! • Fouten! • Worden de juiste foutboodschappen gegeven?! • Wat met I/O issues, zoals ontbrekende bestanden, onleesbare of lege bestanden?!
!
! Appendix b: Contracten! Contracten worden gebruikt om jezelf ervan te verzekeren dat je software juist is opgebouwd. Het is een manier om formeel te documenteren welke functionaliteit een bepaalde functie of methode heeft. Je kan dit programmatisch vast leggen met behulp van asserts (of in ons geval met de macro’s ENSURE en REQUIRE, gedefinieerd in DesignByContract.h).!
!
We gebruiken REQUIRE voor precondities en ENSURE voor postcondities.! REQUIRE legt vast in welke staat het systeem zich moet bevinden alvorens deze functie of methode uit te voeren. ! ENSURE legt vast hoe de staat van het systeem verandert zal zijn na het uitvoeren van deze functie of methode.!
!
Het is de taak van de code die de functie of methode oproept om ervoor te zorgen dat het systeem zich in de juiste staat (zoals beschreven in de preconditie) bevindt. Je moet dus in de functie of methode zelf geen extra controle’s meer implementeren en foutboodschappen genereren, wanneer het vanuit de verkeerde toestand wordt opgeroepen. Deze controle’s en foutboodschappen moeten dus in de oproepende kant worden geïmplementeerd.!
!
Elke kant van een methode/functie oproep haalt voordelen uit een contract, maar moet wel zijn verplichtingen nakomen:!
!
Contract
Voordelen
Methode of functie met pre- en postcondities (de PROVIDER)
-
Code die de provider oproept (de CLIENT)
-
-
-
Verplichtingen
Geen nood om de unput waarden na te Moet er voor zorgen dat de kijken.! postconditie voldaan is. De input voldoet gegarandeerd aan de preconditie. Geen nood om de output waarden na te kijken. ! Het resultaat voldoet gegarandeerd aan de postconditie.
Moet er voor zorgen dat aan de precondities van de Provider voldaan wordt.
Appendix c: Een betere implementatie voor de functie largest!
! ! ! !
#include <exception>! #include ! using namespace std;! class IllegalArgException: public exception{! const char* message;! public:! IllegalArgException(const char * message){! this->message = message;! }!
!
virtual const char* what() const throw(){! return message;! }!
};!
!
int largest2(vector* list) {! if(list == nullptr) {! throw new IllegalArgException("list cannot be nullptr");! }else if(list->empty()){! throw new IllegalArgException("list cannot be empty");! }! int max = numeric_limits::min();! for(int i = 0; i < list->size(); i++){! if(list->at(i) > max){! max = list->at(i);! }! }! return max;! }