11. Toepassingsopdracht – versie 4
De belastingberekeningsapplicatie zal de volgende gelaagde structuur implementeren:

We nemen de elementen uit versie 3 van de paragraaf ‘link’ over en passen ze aan de nieuwe architectuur van de applicatie aan. Dit wordt ook wel ‘refactoring’ genoemd. We gaan er hier vanuit dat de gegevens die de applicatie nodig heeft, in tekstbestanden staan. De laag [Dao] zorgt voor de uitwisseling met deze bestanden.
11.1. Structuur van de scripts

11.2. Objecten die tussen lagen worden uitgewisseld
We behouden bepaalde objecten uit versie 3. We vermelden ze hier ter herinnering.
De uitzondering [ExceptionImpots] is de uitzondering die de laag [Dao] zal genereren wanneer er een probleem optreedt met de toegang tot de gegevens of met de aard van de gegevens (onjuiste gegevens).
<?php
// naamruimte
namespace Application;
class ExceptionImpots extends \RuntimeException {
public function __construct(string $message, int $code=0) {
parent::__construct($message, $code);
}
}
De klasse [Utilitaires] bevat methoden die nuttig zijn voor het beheer van tekstbestanden (in dit geval één enkele methode):
<?php
// naamruimte
namespace Application;
// een klasse met hulpprogramma's
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// de regeleinde-markering van $ligne wordt verwijderd, indien deze bestaat
$longueur = strlen($ligne); // regellengte
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// einde – de regel wordt weergegeven
return($ligne);
}
}
De klasse [TaxAdminData] is de klasse die de gegevens van de belastingdienst omvat:
<?php
namespace Application;
class TaxAdminData {
// belastingtrappen
private $limites;
private $coeffR;
private $coeffN;
// constanten voor de belastingberekening
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// initialisatie
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// de inhoud van het belastinggegevensbestand wordt opgehaald
$fileContents = \file_get_contents($taxAdminDataFilename);
…
// het object wordt teruggegeven
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// JSON-string van het object
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// getters en setters
public function getLimites() {
return $this->limites;
}
…
public function setLimites($limites) {
$this->limites = $limites;
return $this;
}
…
}
We voegen een nieuwe klasse toe, [TaxPayerData], die de gegevens bevat die in het resultatenbestand worden geschreven:
<?php
// naamruimte
namespace Application;
// de gegevensklasse
class TaxPayerData {
// gegevens die nodig zijn voor de berekening van de belasting van de belastingplichtige
private $marié;
private $enfants;
private $salaire;
// resultaten van de belastingberekening
private $montant;
private $surcôte;
private $décôte;
private $réduction;
private $taux;
// setter
public function setFromParameters(string $marié, int $nbEnfants, int $salaireAnnuel) : TaxPayerData{
// gegevens van de belastingplichtige die nodig zijn voor de berekening van de belasting
$this->marié = $marié;
$this->enfants = $nbEnfants;
$this->salaire = $salaireAnnuel;
// het geïnitialiseerde object wordt teruggegeven
return $this;
}
// getters en setters
public function getMarié() {
return $this->marié;
}
…
public function setMarié($marié) {
$this->marié = $marié;
return $this;
}
…
// toString
public function __toString() {
// JSON-string van het object
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
}
Opmerking: gebruik de automatische codegeneratie om de constructor, de getters en setters te genereren (zie paragraaf ‘link’). Merk op dat de setters ‘fluent’ zijn.
11.3. De laag [dao]
We richten ons hier op de laag [1] van onze applicatie:

11.3.1. De interface [InterfaceDao]
De interface van de laag [dao] ziet er als volgt uit: [InterfaceDao.php]:
<?php
// naamruimte
namespace Application;
interface InterfaceDao {
// het uitlezen van belastinggegevens
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
// het uitlezen van gegevens van de belastingdienst (belastingschijven)
public function getTaxAdminData(): TaxAdminData;
// resultaten opslaan
public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
Opmerkingen
- De specificaties zijn als volgt:
- de gegevens van de belastingplichtigen staan in een tekstbestand;
- de resultaten van de belastingberekening worden opgeslagen in een tekstbestand;
- eventuele fouten worden opgeslagen in een tekstbestand;
- het is niet bekend in welke vorm de gegevens van de belastingdienst beschikbaar zijn. Voor elke nieuwe vorm moet de interface [InterfaceDao] door een nieuwe klasse worden geïmplementeerd;
- de methoden van de interface die een onherstelbare fout tegenkomen bij het ophalen van de gegevens, moeten een uitzondering van het type [ExceptionImpots] genereren;
- regel 9: de methode waarmee de gegevens van de belastingplichtige kunnen worden opgehaald: [statut marital, nombre d’enfants, salaire annuel];
- de eerste parameter is de naam van het tekstbestand waarin deze gegevens staan;
- de tweede parameter is de naam van het tekstbestand waarin eventuele fouten moeten worden opgeslagen;
- regel 12: de methode waarmee de gegevens van de belastingdienst kunnen worden opgehaald. Hier worden geen parameters aan doorgegeven, omdat niet bekend is hoe deze gegevens zijn opgeslagen;
- regel 15: de methode waarmee de resultaten van de belastingberekening kunnen worden opgeslagen in een tekstbestand waarvan de naam als parameter wordt doorgegeven;
Bij het schrijven van de interface [InterfaceDao] is het duidelijk dat er verschillende manieren zullen zijn om de methode [getTaxAdminData] te schrijven, afhankelijk van de manier waarop de gegevens van de belastingdienst zijn opgeslagen. De interface [InterfaceDao] zal dus worden geïmplementeerd door verschillende klassen, die elk verantwoordelijk zijn voor een specifieke opslagwijze van deze gegevens (tabellen, tekstbestanden, databases, webservices). Deze afgeleide klassen zullen echter gemeenschappelijke code bevatten, namelijk die van de implementatie van de methoden [getTaxPayersData, saveResults]. We weten dat dit gebruiksscenario op twee manieren kan worden geïmplementeerd (zie paragraaf ‘link’):
- er wordt een abstracte klasse C gemaakt die de gemeenschappelijke code van de afgeleide klassen bevat. Klasse C implementeert de interface I, maar bepaalde methoden die in de afgeleide klassen moeten worden gedeclareerd, zijn in klasse C als abstract gedeclareerd en daarom is klasse C zelf ook abstract. Vervolgens maken we de klassen C1 en C2, afgeleid van C, die elk op hun eigen manier de niet-gedefinieerde (abstracte) methoden van hun bovenliggende klasse C implementeren;
- we maken een trait T die vrijwel identiek is aan de abstracte klasse C uit de vorige oplossing. Deze trait implementeert de interface I niet, omdat dit syntactisch gezien niet mogelijk is. Vervolgens maken we de klassen C1 en C2, die de interface I implementeren en gebruikmaken van de trait T. Deze klassen hoeven alleen nog maar de methoden van de interface I te implementeren die niet zijn geïmplementeerd door de trait T die ze importeren;
Voor dit voorbeeld gebruiken we hier een trait [TraitDao].
11.3.2. De trait [TraitDao]
De code van de trait [TraitDao] is als volgt: [TraitDao.php]:
<?php
// naamruimte
namespace Application;
trait TraitDao {
// belastingplichtigengegevens inlezen
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
// tabel met belastingplichtigen
$taxPayersData = [];
// foutentabel
$errors = [];
// er kunnen nogal wat fouten optreden bij het beheren van bestanden
try {
// uitlezen van gebruikersgegevens
// elke regel heeft de vorm: burgerlijke staat, aantal kinderen, jaarsalaris
$taxPayersFile = fopen($taxPayersFilename, "r");
if (!$taxPayersFile) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$taxPayersFilename]", 12);
}
// de huidige regel uit het gebruikersgegevensbestand wordt verwerkt
// die de volgende indeling heeft: burgerlijke staat, aantal kinderen, jaarsalaris
$num = 1; // nummer van de huidige regel
$nbErreurs = 0; // aantal aangetroffen fouten
while ($ligne = fgets($taxPayersFile, 100)) {
// lege regels worden genegeerd
$ligne = trim($ligne);
if (strlen($ligne) == 0) {
// volgende regel
$num++;
// terug naar het begin
continue;
}
// eventueel einde-van-regel-teken verwijderen
$ligne = Utilitaires::cutNewLineChar($ligne);
// de 3 velden ‘getrouwd:kinderen:salaris’ worden opgehaald, die samen $ligne vormen
list($marié, $enfants, $salaire) = explode(",", $ligne);
// we controleren ze
// de burgerlijke staat moet ‘ja’ of ‘nee’ zijn
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// het aantal kinderen moet een geheel getal zijn
$enfants = trim($enfants);
if (!preg_match("/^\d+$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// het salaris is een geheel getal zonder eurocenten
$salaire = trim($salaire);
if (!preg_match("/^\d+$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// fout?
if ($erreur) {
$errors[] = "la ligne [$num] du fichier [$taxPayersFilename] est erronée";
$nbErreurs++;
} else {
// de gegevens worden opgeslagen
$taxPayersData[] = (new TaxPayerData())->setFromParameters($marié, $enfants, $salaire);
}
// volgende regel
$num++;
}
// zijn we aan het einde van het bestand?
if (!feof($taxPayersFile)) {
// de lus is verlaten vanwege een leesfout
throw new ExceptionImpots("Erreur lors de la lecture de la ligne n° [$num] du fichier [$taxPayersFilename]");
} else {
// de lus is verlaten bij het einde-van-bestand-teken
// de fouten worden opgeslagen in een tekstbestand
$this->saveString($errorsFilename, implode("\n", $errors));
// resultaat van de functie
return $taxPayersData;
}
} finally {
// het bestand wordt gesloten als het geopend is
if ($taxPayersFile) {
fclose($taxPayersFile);
}
}
}
// de resultaten worden opgeslagen
public function saveResults(string $resultsFilename, array $taxPayersData): void {
// de tabel [$taxPayersData] wordt opgeslagen in het tekstbestand [$resultsFileName]
// als het tekstbestand [$resultsFileName] niet bestaat, wordt het aangemaakt
$this->saveString($resultsFilename, implode("\n", $taxPayersData));
}
// de resultaten van een tabel worden opgeslagen in een tekstbestand
private function saveString(string $fileName, string $data): void {
// de tabel [$data] opslaan in het tekstbestand [$fileName]
// als het tekstbestand [$fileName] niet bestaat, wordt het aangemaakt
if (file_put_contents($fileName, $data) === FALSE) {
throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
}
}
}
Opmerkingen
- regel 6: we definiëren hier een trait en geen klasse;
- regels 9-89: de methode [getTaxPayersData] implementeert de gelijknamige methode van de interface [InterfaceDao]. Deze haalt uit een tekstbestand met de naam [$taxPayersFilename] de gegevens van de belastingplichtigen [statut marital, nombre d’enfants, salaire annuel] op. Deze gegevens worden weergegeven in de vorm van een tabel [$taxPayersData] met elementen van het type [TaxPayerData] (regels 67, 81);
- de methode [getTaxPayersData] lijkt sterk op de in de paragraaf ‘Koppeling’ beschreven methode [AbstractBaseImpots::executeBatchImpots], met de volgende verschillen:
- de methode [getTaxPayersData] haalt alleen de gegevens van de belastingplichtigen op. Zij voert geen belastingberekening uit. Dit is de taak van de laag [métier];
- net als de methode [executeBatchImpots] meldt deze methode fouten. Hier worden de fouten eerst opgeslagen in een array [$errors] (regel 13), die aan het einde van de verwerking in een tekstbestand wordt opgeslagen (regel 79). Afhankelijk van het geval is deze array leeg of niet;
- in het geval van een onherstelbare fout wordt een uitzondering van het type [ExceptionImpots] gegenereerd (regels 20, 75);
- regel 73: let op de verwerking die plaatsvindt bij het verlaten van de lus van de regels 26-71. De functie [fgets] heeft namelijk het nadeel dat de booleaanse waarde FALSE zowel wordt geretourneerd wanneer het lezen van de regels het einde-van-bestand-teken heeft bereikt, als wanneer het lezen niet kon worden voltooid vanwege een fout. Om deze twee gevallen van elkaar te onderscheiden, wordt met de functie [feof] getest of het einde van het bestand is bereikt. Als het einde van het bestand niet is bereikt, betekent dit dat er een fout is opgetreden en wordt er een uitzondering gegenereerd;
- regels 83-88: de functie [finally] wordt uitgevoerd, ongeacht of er al dan niet een uitzondering is opgetreden tijdens het verwerken van het bestand;
- regel 85: als het bestand is geopend, heeft de ‘handle’ [$taxPayersFile] van het bestand de booleaanse waarde TRUE, anders FALSE;
- regels 99-105: de privémethode [saveString] die op regel 79 wordt gebruikt om de foutentabel in een tekstbestand op te slaan;
- regel 99: de methode [saveString] ontvangt twee parameters:
- [string $filename], de naam van het tekstbestand waarin de gegevens worden opgeslagen;
- [string $data], de tekenreeks die in het tekstbestand moet worden opgeslagen. Deze tekenreeks bestaat uit een reeks regels die worden afgesloten met het regeleinde-teken \n;
- regel 102: de functie PHP [file_puts_contents] slaat een tekenreeks op in een tekstbestand. Deze functie zorgt ervoor dat het bestand wordt geopend, de tekenreeks erin wordt geschreven en het bestand wordt gesloten. De functie retourneert de booleaanse waarde FALSE als er een fout is opgetreden;
- regel 103: als er een fout optreedt, wordt er een uitzondering gegenereerd;
- regels 92-96: implementatie van de methode [saveResults] van de interface [InterfaceDao]. Er wordt opnieuw gebruikgemaakt van de privémethode [saveString]. Hier is de tweede parameter van [saveString] een tekenreeks die is samengesteld uit de array [$taxPayersData], waarvan de elementen van het type [TaxPayerData] zijn. Men kan zich afvragen wat het resultaat van de bewerking zal zijn:
implode("\n", $taxPayersData)
We hebben in de klasse [TaxPayerData] (paragraaf ‘link’) de volgende methode [__toString] gedefinieerd:
public function __toString() {
// JSON-string van het object
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
De bewerking
implode("\n", $taxPayersData)
voegt elk element van de array [$taxPayersData], dat door de methode [__toString] in een tekenreeks is omgezet, samen met het regeleinde-teken \n. Dit levert een tekenreeks op in de vorm:
json1\njson2\n…
Conclusie
De klasse [TraitDao] heeft twee van de methoden van de interface [InterfaceDao] geïmplementeerd, namelijk [getTaxPayersData] en [saveResults]:
<?php
// naamruimte
namespace Application;
interface InterfaceDao {
// het inlezen van belastinggegevens
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
// het inlezen van gegevens van de belastingdienst (belastingschijven)
public function getTaxAdminData(): TaxAdminData;
// resultaten opslaan
public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
We moeten nu nog de methode [getTaxAdminData] implementeren, die de gegevens van de belastingdienst ophaalt.
11.3.3. De klasse [ImpotsWithTaxAdminDataInJsonFile]
De klasse [ImpotsWithTaxAdminDataInJsonFile] implementeert de interface [InterfaceDao] als volgt:
<?php
// naamruimte
namespace Application;
// definitie van een klasse ImpotsWithDataInFile
class DaoImpotsWithTaxAdminDataInJsonFile implements InterfaceDao {
// gebruik van een eigenschap
use TraitDao;
// het object van het type TaxAdminData dat de gegevens van de belastingschijven bevat
private $taxAdminData;
// de constructor
public function __construct(string $taxAdminDataFilename) {
// we willen het attribuut [$this->taxAdminData] initialiseren
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
}
// geeft de gegevens terug waarmee de belasting kan worden berekend
public function getTaxAdminData(): TaxAdminData {
return $this->taxAdminData;
}
}
Opmerkingen
- regel 7: de klasse [ImpotsWithTaxAdminDataInJsonFile] implementeert de interface [InterfaceDao];
- regel 9: de klasse [ImpotsWithTaxAdminDataInJsonFile] maakt gebruik van de eigenschap [traitDao], waarvan bekend is dat deze de methoden [getTaxPayersData] en [saveResults] van deinterface [InterfaceDao]. De klasse [ImpotsWithTaxAdminDataInJsonFile] hoeft dus alleen nog maar de methode [getTaxAdminData] te implementeren, die de gegevens van de belastingdienst ophaalt;
- regel 11: het attribuut van het type [TaxAdminData] dat wordt geretourneerd door de methode [getTaxAdminData] uit de regels 20-22. Dit attribuut wordt geïnitialiseerd door de constructor uit de regels 14-17;
We zijn klaar met de laag [dao] van onze applicatie: we hebben een klasse die de interface [InterfaceDao], die we onszelf hebben opgelegd, volledig implementeert. We kunnen nu verdergaan met de laag [métier].
11.4. De laag [métier]
We gaan nu de laag [2] van onze architectuur implementeren:

11.4.1. De interface [InterfaceMétier]
De interface van de laag [métier] ziet er als volgt uit:
<?php
// naamruimte
namespace Application;
interface InterfaceMetier {
// berekening van de belastingen van een belastingplichtige
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// berekening van belastingen in batchmodus
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
Opmerkingen
- regel 9: de interface [InterfaceMétier] kan het belastingbedrag van een particuliere belastingplichtige berekenen, mits de volgende gegevens worden verstrekt: burgerlijke staat, aantal kinderen, jaarsalaris. De methode [calculerImpot] maakt geen gebruik van de laag [dao] en genereert daarom geen uitzonderingen;
- regel 9: de interface [InterfaceMétier] kan ook het belastingbedrag berekenen voor een groep belastingplichtigen waarvan de gegevens zijn verzameld in het tekstbestand met de naam [$taxPayersFileName]. De resultaten worden opgeslagen in een tekstbestand met de naam [$resultsFileName]. De methode [executeBatchImpots] moet een beroep doen op de laag [dao], die de toegang tot het bestandssysteem regelt. Er kunnen dan uitzonderingen worden doorgegeven vanuit de laag [dao] die de methode [executeBatchImpots] niet zal opvangen: deze laat ze door naar het hoofdscript. Niet-fatale fouten worden vastgelegd in het tekstbestand met de naam [$errorsFileName];
- regel 9: de methode [calculerImpot] is een puur [métier]-methode. Deze houdt zich niet bezig met de herkomst van de gegevens die zij gebruikt;
- regel 12: de methode [executeBatchImpots] zal de laag [dao] aanspreken om gegevens uit tekstbestanden te lezen en erin te schrijven. Ze zal herhaaldelijk de bedrijfsmethode [calculerImpot] aanroepen;
11.4.2. De klasse [Metier]
De klasse [Metier] implementeert de interface [InterfaceMetier] als volgt:
<?php
// naamruimte
namespace Application;
class Metier implements InterfaceMetier {
// Dao-laag
private $dao;
// gegevens belastingdienst
private $taxAdminData;
//---------------------------------------------
// setter-laag [dao]
public function setDao(InterfaceDao $dao) {
$this->dao = $dao;
return $this;
}
public function __construct(InterfaceDao $dao) {
// er wordt een referentie opgeslagen op de laag [dao]
$this->dao = $dao;
// de gegevens voor de berekening van de belasting worden opgehaald
// de methode [getTaxAdminData] kan een uitzondering genereren ExceptionImpots
// deze wordt vervolgens doorgegeven naar de aanroepende code
$this->taxAdminData = $this->dao->getTaxAdminData();
}
// belastingberekening
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
…
// resultaat
return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
// --------------------------------------------------------------------------
private function calculerImpot2(string $marié, int $enfants, float $salaire): array {
…
// resultaat
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=jaarsalaris-aftrek
// de vrijstelling heeft een minimum en een maximum
private function getRevenuImposable(float $salaire): float {
…
// resultaat
return floor($revenuImposable);
}
// berekent een eventuele korting
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// resultaat
return ceil($décôte);
}
// berekent een eventuele korting
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// resultaat
return ceil($réduction);
}
// berekening van belastingen in batchmodus
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
…
// resultaten opslaan
$this->dao->saveResults($resultsFileName, $results);
}
}
Opmerkingen
- regel 6: de klasse [Metier] implementeert de interface [InterfaceMetier], d.w.z. de methoden [calculerImpot] (regels 30-34) en [executeBatchImpots] (regels 66-70);
- regel 8: een verwijzing naar de laag [dao]. Dit is absoluut noodzakelijk zodat de laag [métier] weet tot wie zij zich moet wenden wanneer zij externe gegevens nodig heeft. Dit attribuut wordt geïnitialiseerd via de setter in de regels 14-17 of via de constructor in de regels 19-26;
- regel 10: het object van het type [TaxAdminData] dat de gegevens van de belastingdienst bevat. Deze gegevens zijn nodig voor de bedrijfsmethode [calculerImpot]. Dit attribuut wordt geïnitialiseerd via de constructor in de regels 19-26;
- regels 19-26: de constructor initialiseert de twee attributen van de klasse:
- het attribuut [$dao] wordt geïnitialiseerd met de referentie die als parameter aan de constructor wordt doorgegeven. Opgemerkt moet worden dat het type van deze parameter overeenkomt met dat van de interface [InterfaceDao], waardoor de klasse [Metier] kan worden geïnitialiseerd door elke klasse die deze interface implementeert;
- het attribuut [$taxAdminData] wordt geïnitialiseerd door de methode [getTaxAdminData] van de laag [dao] aan te roepen;
Hieruit volgt dat wanneer de methoden [calculerImpots] en [executeBatchImpots] worden uitgevoerd, de twee attributen [$dao] en [$taxAdminData] worden geïnitialiseerd.
De methode [calculerImpots] is als volgt:
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié: ja, nee
// $enfants: aantal kinderen
// $salaire: jaarsalaris
// $this->taxAdminData: gegevens van de belastingdienst
//
// er wordt gecontroleerd of de gegevens van de belastingdienst aanwezig zijn
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// berekening van de belasting met kinderen
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// berekening van de belasting zonder kinderen
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// toepassing van de maximering van de gezinsquotiënt
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euro voor de eerste twee kinderen
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euro voor de eerste twee kinderen, het dubbele voor de volgende
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// we nemen het hoogste belastingtarief
if ($impot1 > $impot2) {
$impot = $impot1;
$taux = $result1["taux"];
$surcôte = $result1["surcôte"];
} else {
$surcôte = $impot2 - $impot1 + $result2["surcôte"];
$impot = $impot2;
$taux = $result2["taux"];
}
// berekening van een eventuele korting
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// berekening van een eventuele belastingvermindering
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// resultaat
return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
Opmerkingen
- deze code is die van de methode [AbstractBaseImpots::calculerImpot] uit versie 3, zoals uitgelegd in de paragraaf 'link'. Hetzelfde geldt voor de privé-methoden [calculerImpot2, getDecôte, getRéduction, getRevenuImposable];
De methode [Metier::executeBatchImpots] is als volgt:
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// we laten de uitzonderingen die afkomstig zijn van de laag [dao] door
// de belastingplichtigengegevens worden opgehaald
$taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
// resultatenoverzicht
$results = [];
// we verwerken deze
foreach ($taxPayersData as $taxPayerData) {
// de belasting wordt berekend
$result = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
// we vullen [$taxPayerData] aan
$taxPayerData->setMontant($result["impôt"]);
$taxPayerData->setDécôte($result["décôte"]);
$taxPayerData->setSurCôte($result["surcôte"]);
$taxPayerData->setTaux($result["taux"]);
$taxPayerData->setRéduction($result["réduction"]);
// het resultaat wordt in de resultatenlijst geplaatst
$results [] = $taxPayerData;
}
// de resultaten worden opgeslagen
$this->dao->saveResults($resultsFileName, $results);
}
Opmerkingen
- regel 1: de methode moet herhaaldelijk de methode [calculerImpot] aanroepen voor elke belastingplichtige die wordt aangetroffen in het tekstbestand met de naam [$taxPayersFileName]. De resultaten moeten worden opgeslagen in het tekstbestand met de naam [$resultsFileName]. De niet-fatale fouten die worden aangetroffen, worden vastgelegd in het tekstbestand met de naam [$errorsFileName]. De methode genereert zelf geen uitzonderingen, maar laat de uitzonderingen door die door de laag [dao] worden gegenereerd;
- regel 4: de gegevens van de belastingplichtigen worden opgevraagd bij de laag [dao]. Deze retourneert een array met elementen van het type [TaxPayerData], wat een klasse van attributen [marié, nbEnfants, salaire, montant, décôte, réduction, surcôte, taux] is (zie paragraaf ‘link’). Als hier een uitzondering optreedt, wordt deze automatisch doorgegeven aan de aanroepende code, aangezien deze niet wordt opgevangen door een catch. Dit betekent dat bij een uitzondering regel 6 niet wordt uitgevoerd;
- regel 6: de resultatenarray van het type [TaxPayerData];
- regels 8-22: de belasting wordt berekend voor elk element van de belastingplichtigenlijst [$taxPayersData]. Hiervoor wordt de interne methode [calculerImpot] aangeroepen (regel 10);
- regels 15-19: het verkregen resultaat wordt gebruikt om de attributen van [TaxPayerData] te initialiseren die nog niet waren geïnitialiseerd;
- regel 21: het verkregen resultaat wordt opgeteld in de resultatentabel [$results];
- regel 24: zodra de belasting voor alle belastingplichtigen is berekend, worden de resultaten opgeslagen in een tekstbestand. Dit gebeurt door de laag [dao];
Conclusie
Over het algemeen is de laag [métier] vrij eenvoudig te schrijven, omdat deze zich richt tot de laag [dao], die op haar beurt de toegang tot de gegevens regelt, inclusief de bijbehorende foutafhandeling.
11.5. Het hoofdscript
We gaan nu het script schrijven voor de laag [3] van onze architectuur:

Het hoofdscript is het volgende: [main.php]:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// foutbeheer door PHP
//ini_set("display_errors", "0");
// opname van interface en klassen
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . "/TaxPayerData.php";
require_once __DIR__ . "/ExceptionImpots.php";
require_once __DIR__ . "/Utilitaires.php";
require_once __DIR__ . "/InterfaceDao.php";
require_once __DIR__ . "/TraitDao.php";
require_once __DIR__ . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once __DIR__ . "/InterfaceMetier.php";
require_once __DIR__ . "/Metier.php";
// test -----------------------------------------------------
// definitie van constanten
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// aanmaken van de laag [métier]
$métier = new Metier($dao);
// berekening van de belasting in batchmodus
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// de fout wordt weergegeven
print $ex->getMessage() . "\n";
}
// einde
print "Terminé\n";
exit;
Opmerkingen
- regel 24: de naam van het bestand met de belastinggegevens;
- regel 25: de naam van het resultatenbestand;
- regel 26: de naam van het foutbestand;
- regel 27: de naam van het bestand jSON met de gegevens van de belastingdienst;
- regel 31: aanmaken van de laag [dao];
- regel 33: aanmaken van de laag [métier] op basis van deze laag [dao];
- regel 35: uitvoering van de methode [executeBatchImpots] van de laag [métier];
- regels 36-39: we hebben gezien dat de laag [métier] uitzonderingen kan doorgeven. Deze worden hier opgevangen;
11.6. Visuele tests
11.6.1. Test nr. 1
Met het volgende belastingplichtigenbestand [taxpayersdata.txt]:
oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000
krijgt men het volgende foutenbestand [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
en het volgende resultatenbestand [resultats.txt]:
11.6.2. Test nr. 2
In het hoofdscript geven we het bestand met de belastingplichtigen een bestandsnaam die niet bestaat:
De resultaten die dan in de console worden weergegeven, zijn als volgt:
Warning: fopen(taxpayersdata2.txt): failed to open stream: No such file or directory in C:\Data\st-2019\dev\php7\poly\scripts-console\impots\version-04\TraitDao.php on line 18
Impossible d'ouvrir en lecture les déclarations des contribuables [taxpayersdata2.txt]
Terminé
Done.
- regel 1: waarschuwingen (warning) van de interpreter PHP;
- regel 2: de foutmelding van de uitzondering die door de laag [dao] is gegenereerd;
Het is mogelijk om de foutmeldingen van de interpreter PHP te onderdrukken:

Regel 21 van de bovenstaande code zorgt ervoor dat de fouten PHP niet worden weergegeven. Tijdens de ontwikkelingsfase is het noodzakelijk dat ze worden weergegeven. In de productiemodus moeten ze worden verborgen.
De uitvoerresultaten zijn dan als volgt:
Impossible d'ouvrir en lecture les déclarations des contribuables [taxpayersdata2.txt]
Terminé
11.7. Tests [Codeception]
De visuele tests zijn zeer ontoereikend:
- meestal beperken we ons tot een paar tests;
- men is min of meer aandachtig tijdens deze visuele controle en details kunnen ons ontgaan;
In de praktijk van professionele ontwikkeling worden de tests geschreven door gespecialiseerde medewerkers voor wie dit de hoofdtaak is. Zij streven er dan naar om de tests zo volledig mogelijk uit te voeren. Hiervoor maken ze gebruik van testframeworks.
We gaan hier het Codeception-framework [https://codeception.com/] gebruiken, omdat dit in NetBeans kan worden geïntegreerd. Het is een framework met een breed scala aan mogelijkheden. We gaan er slechts enkele gebruiken. Het idee is om na elke nieuwe versie van de applicatieoefening snel te kunnen controleren of deze werkt. Het bestaan van geslaagde tests geeft de ontwikkelaar vertrouwen in de code die hij heeft geschreven. Dit is een belangrijke factor.
11.7.1. Installatie van het [Codeception]-framework
Net als veel andere bibliotheken PHP wordt het framework [Codeception] samen met [Composer] geïnstalleerd. We openen daarom een Laragon-terminal (zie paragraaf ‘link’).
Eerst moeten we het testframework PHPUnit [https://phpunit.de/] installeren. Codeception maakt namelijk achter de schermen gebruik van het framework PHPUnit:

Vervolgens installeren we het Codeception-framework:

Dat is alles. Laten we nu eens kijken naar de integratie van [Codeception] in NetBeans.
11.7.2. Integratie van [CodeCeption] in NetBeans

- in [1-2] openen we de projecteigenschappen;
- in [3-4] maken we van [Codeception] een van de testframeworks van het project;


- in [5-8] wordt het framework [Codeception] voor het project geïnitialiseerd;

- in [9] is een map [tests] aangemaakt, evenals een configuratiebestand [codeception.yml] in [10-11]. Het bestand [11] is hetzelfde als het bestand [10]. Codeception heeft simpelweg een map [Important Files] aangemaakt om het bestand [10] een specifieke betekenis te geven;
- in [12-13] keren we terug naar de projecteigenschappen;

- in [14-16] wordt de map [tests] [16] aangeduid als de testmap van het project;
- in [16] verschijnt de map [tests] vervolgens onder de nieuwe naam [Test Files]. De aanwezigheid van deze map in een project PHP geeft aan dat dit project een framework voor geprogrammeerde tests bevat;
- we zullen onze tests aanmaken in de map [unit] [17];
11.7.3. Tests van de laag [dao]

- we gaan al onze tests aanmaken in de map [unit] [1];
- de namen van de testklassen [Codeception] moeten eindigen op het trefwoord [Test], anders worden de klassen niet herkend als testklassen ;
Onze testklassen [Codeception] zullen de volgende vorm hebben: [https://codeception.com/docs/05-UnitTests]:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// het laden van de testomgeving
…
class DaoTest extends \Codeception\Test\Unit {
// testattributen
private $attribut1;
public function __construct() {
parent::__construct();
// initialisatie van de testomgeving
…
}
// tests
public function testTaxAdminData() {
// tests
$this->assertEquals($expected, $actual);
$this->assertEqualsWithDelta($expected, $actual, $delta);
$this->assertTrue($actual);
$this->assertFalse($actual);
$this->assertNull($actual);
$this->assertEmpty($actual);
$this→assertSame($expected, $actual);
…
}
}
Opmerkingen
- regel 7: de testklassen bevinden zich in dezelfde naamruimte als de geteste applicatie;
- regels 9-10: hier staan de bewerkingen [require] om de geteste klassen en interfaces te laden;
- regel 12: de naam van de testklasse moet verplicht eindigen op het sleutelwoord [Test]. Deze klasse moet de klasse [\Codeception\Test\Unit] uitbreiden;
- regels 16-20: met de constructor kunnen we de testomgeving initialiseren;
- regel 23: de namen van de testmethoden moeten verplicht beginnen met het sleutelwoord [test];
- regels 25-31: er kunnen diverse testmethoden worden gebruikt;
De testklasse [DaoTest] ziet er als volgt uit:
<?php
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// constanten
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// opname van interfaces en klassen
require_once ROOT . "/TaxAdminData.php";
require_once ROOT . "/TaxPayerData.php";
require_once ROOT . "/ExceptionImpots.php";
require_once ROOT . "/Utilitaires.php";
require_once ROOT . "/InterfaceDao.php";
require_once ROOT . "/TraitDao.php";
require_once ROOT . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once ROOT . "/InterfaceMetier.php";
require_once ROOT . "/Metier.php";
require_once VENDOR. "/autoload.php";;
// test -----------------------------------------------------
// definitie van constanten
const TAXADMINDATA_FILENAME = "taxadmindata.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
…
}
}
Opmerkingen
Om de tests voor een versie van de toepassingsopdracht op te stellen, gebruiken we een omgeving die identiek is aan die welke door het hoofdscript van de versie wordt gebruikt. Die van versie 04 is het volgende script [main.php]:
<?php
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// foutbeheer door PHP
ini_set("display_errors", "0");
// opname van interface en klassen
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . "/TaxPayerData.php";
require_once __DIR__ . "/ExceptionImpots.php";
require_once __DIR__ . "/Utilitaires.php";
require_once __DIR__ . "/InterfaceDao.php";
require_once __DIR__ . "/TraitDao.php";
require_once __DIR__ . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once __DIR__ . "/InterfaceMetier.php";
require_once __DIR__ . "/Metier.php";
// test -----------------------------------------------------
// definitie van constanten
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// aanmaken van de laag [métier]
$métier = new Metier($dao);
// berekening van de belasting in batchmodus
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// de fout wordt weergegeven
print $ex->getMessage() . "\n";
}
// einde
print "Terminé\n";
exit;
Om de laag [dao] te testen, nemen we in de testklasse:
- nemen we de omgeving over uit de regels 13-27 van [main.php];
- in de constructor van de testklasse bouwen we de laag [dao] zoals in regel 31;
- schrijven we de testmethoden;
We gaan op deze manier te werk voor alle testklassen.
Laten we terugkeren naar de volledige code van de testklasse:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// constanten
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// opname van interfaces en klassen
require_once ROOT . "/TaxAdminData.php";
require_once ROOT . "/TaxPayerData.php";
require_once ROOT . "/ExceptionImpots.php";
require_once ROOT . "/Utilitaires.php";
require_once ROOT . "/InterfaceDao.php";
require_once ROOT . "/TraitDao.php";
require_once ROOT . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once ROOT . "/InterfaceMetier.php";
require_once ROOT . "/Metier.php";
require_once VENDOR. "/autoload.php";;
// test -----------------------------------------------------
// definitie van constanten
const TAXADMINDATA_FILENAME = "taxadmindata.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
// berekeningsconstanten
$this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());
$this->assertEquals(21037, $this->taxAdminData->getPlafondRevenusCelibatairePourReduction());
$this->assertEquals(42074, $this->taxAdminData->getPlafondRevenusCouplePourReduction());
$this->assertEquals(3797, $this->taxAdminData->getValeurReducDemiPart());
$this->assertEquals(1196, $this->taxAdminData->getPlafondDecoteCelibataire());
$this->assertEquals(1970, $this->taxAdminData->getPlafondDecoteCouple());
$this->assertEquals(1595, $this->taxAdminData->getPlafondImpotCelibatairePourDecote());
$this->assertEquals(2627, $this->taxAdminData->getPlafondImpotCouplePourDecote());
$this->assertEquals(12502, $this->taxAdminData->getAbattementDixPourcentMax());
$this->assertEquals(437, $this->taxAdminData->getAbattementDixPourcentMin());
// belastingschijven
$this->assertSame([9964.0, 27519.0, 73779.0, 156244.0, 0.0], $this->taxAdminData->getLimites());
$this->assertSame([0.0, 0.14, 0.30, 0.41, 0.45], $this->taxAdminData->getCoeffR());
$this->assertSame([0.0, 1394.96, 5798.0, 13913.69, 20163.45], $this->taxAdminData->getCoeffN());
}
}
Opmerkingen
- regels 10-25: laden van de benodigde testomgeving en definiëren van constanten;
- regels 31-36: aanmaken van de laag [dao] (regel 34), gevolgd door het initialiseren van het attribuut [$taxAdminData] (regel 29). Dit attribuut bevat de gegevens van de belastingdienst;
- regels 39-55: de enige testmethode. Deze bestaat uit het controleren of de inhoud van het attribuut [$taxAdminData] overeenkomt met wat verwacht wordt;
- regels 41-50: controles van de constanten voor de belastingberekening;
- regels 52-55: controles van de belastingschijven. De methode [assertSame] controleert of twee entiteiten PHP, in dit geval tabellen, identiek zijn;
Om deze testklasse uit te voeren, gaat men als volgt te werk:

- in [1-2] voert men de test uit;
- [3]: het venster met de testresultaten;
- [4]: de uitgevoerde testklasse;
- [5]: de resultaten. Hier is de enige testmethode geslaagd;
- [6]: wanneer de test mislukt of, wat vaker voorkomt, wanneer er geen test is uitgevoerd, moet men het venster [6] raadplegen. Meestal is het laden van de testomgeving mislukt, waardoor er geen test kon worden uitgevoerd. De fouten die in [6] worden weergegeven, zijn dezelfde als die bij het uitvoeren van een klassiek PHP-script;
Laten we een voorbeeld van een foutieve test bekijken:
In de testklasse introduceren we een fout in de definitie van een constante:
// constanten
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04x");
Vervolgens voeren we de test uit. Het resultaat is als volgt:

In het venster [4]:

11.7.4. Tests van de laag [métier]
De testklasse [MetierTest] volgt dezelfde opbouwregels als de klasse [DaoTest], maar er zijn meer testmethoden:
<?php
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// constanten
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// opname van interfaces en klassen
require_once ROOT . "/TaxAdminData.php";
require_once ROOT . "/TaxPayerData.php";
require_once ROOT . "/ExceptionImpots.php";
require_once ROOT . "/Utilitaires.php";
require_once ROOT . "/InterfaceDao.php";
require_once ROOT . "/TraitDao.php";
require_once ROOT . "/DaoImpotsWithTaxAdminDataInJsonFile.php";
require_once ROOT . "/InterfaceMetier.php";
require_once ROOT . "/Metier.php";
require_once VENDOR. "/autoload.php";;
// test -----------------------------------------------------
// definitie van constanten
const TAXADMINDATA_FILENAME = "taxadmindata.json";
class MetierTest extends \Codeception\Test\Unit {
// bedrijfslaag
private $métier;
public function __construct() {
parent::__construct();
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
// aanmaken van de laag [métier]
$this->métier = new Metier($dao);
}
// tests
public function test1() {
$result = $this->métier->calculerImpot("oui", 2, 55555);
$this->assertEqualsWithDelta(2815, $result["impôt"], 1);
$this->assertEqualsWithDelta(0, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.14, $result["taux"]);
}
public function test2() {
$result = $this->métier->calculerImpot("oui", 2, 50000);
$this->assertEqualsWithDelta(1385, $result["impôt"], 1);
$this->assertEqualsWithDelta(0, $result["surcôte"], 1);
$this->assertEqualsWithDelta(384, $result["décôte"], 1);
$this->assertEqualsWithDelta(347, $result["réduction"], 1);
$this->assertEquals(0.14, $result["taux"]);
}
public function test3() {
$result = $this->métier->calculerImpot("oui", 3, 50000);
$this->assertEqualsWithDelta(0, $result["impôt"], 1);
$this->assertEqualsWithDelta(0, $result["surcôte"], 1);
$this->assertEqualsWithDelta(720, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.14, $result["taux"]);
}
public function test4() {
$result = $this->métier->calculerImpot("non", 2, 100000);
$this->assertEqualsWithDelta(19884, $result["impôt"], 1);
$this->assertEqualsWithDelta(4480, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.41, $result["taux"]);
}
public function test5() {
$result = $this->métier->calculerImpot("non", 3, 100000);
$this->assertEqualsWithDelta(16782, $result["impôt"], 1);
$this->assertEqualsWithDelta(7176, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.41, $result["taux"]);
}
public function test6() {
$result = $this->métier->calculerImpot("oui", 3, 100000);
$this->assertEqualsWithDelta(9200, $result["impôt"], 1);
$this->assertEqualsWithDelta(2180, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.3, $result["taux"]);
}
public function test7() {
$result = $this->métier->calculerImpot("oui", 5, 100000);
$this->assertEqualsWithDelta(4230, $result["impôt"], 1);
$this->assertEqualsWithDelta(0, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.14, $result["taux"]);
}
public function test8() {
$result = $this->métier->calculerImpot("non", 0, 100000);
$this->assertEqualsWithDelta(22986, $result["impôt"], 1);
$this->assertEqualsWithDelta(0, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.41, $result["taux"]);
}
public function test9() {
$result = $this->métier->calculerImpot("oui", 2, 30000);
$this->assertEqualsWithDelta(0, $result["impôt"], 1);
$this->assertEqualsWithDelta(0, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0, $result["taux"]);
}
public function test10() {
$result = $this->métier->calculerImpot("non", 0, 200000);
$this->assertEqualsWithDelta(64210, $result["impôt"], 1);
$this->assertEqualsWithDelta(7498, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.45, $result["taux"]);
}
public function test11() {
$result = $this->métier->calculerImpot("oui", 3, 200000);
$this->assertEqualsWithDelta(42842, $result["impôt"], 1);
$this->assertEqualsWithDelta(17283, $result["surcôte"], 1);
$this->assertEqualsWithDelta(0, $result["décôte"], 1);
$this->assertEqualsWithDelta(0, $result["réduction"], 1);
$this->assertEquals(0.41, $result["taux"]);
}
}
Opmerkingen
- regels 10-25: het laden van de bestanden die de testomgeving definiëren. Deze is dezelfde als voor de laag [dao];
- regels 31-37: instantiëren van de lagen [dao] en [métier];
- regels 40-47: een test voor de berekening van de belasting;
- regel 41: er wordt een bepaalde belastingberekening uitgevoerd met de laag [métier];
- regels 42-46: er wordt gecontroleerd of de verkregen resultaten overeenkomen met die van de simulator van de belastingdienst [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm];
- regels 23-26: de gelijkheidstests worden uitgevoerd met een afwijking van maximaal 1 euro. We hebben namelijk gezien dat afrondingsproblemen ervoor zorgden dat het algoritme in het document de verwachte resultaten op 1 euro nauwkeurig opleverde;
- regel 27: het belastingtarief wordt berekend zonder foutmarge;
- regels 49-137: dit soort tests wordt 10 keer herhaald, telkens met een andere configuratie van de belastingplichtige;
De tests leveren de volgende resultaten op:

11.7.5. Tests van de volgende versies
In het vervolg zullen de tests van de lagen [dao] en [métier] identiek zijn aan die van versie 04. Alleen de testomgeving zal veranderen. We zullen daarom alleen deze omgeving en de testresultaten presenteren.