11. Übungsaufgabe – Version 4
Die Steuerberechnungsanwendung wird die folgende mehrschichtige Architektur implementieren:

Wir werden die Elemente aus Version 3 des verlinkten Abschnitts wiederverwenden und sie so anpassen, dass sie der neuen Architektur der Anwendung entsprechen. Dies wird manchmal als „Refactoring“ bezeichnet. Hier gehen wir davon aus, dass die von der Anwendung benötigten Daten in Textdateien gespeichert sind. Die [Dao]-Schicht wird die Interaktionen mit diesen Dateien übernehmen.
11.1. Skriptbaum

11.2. Zwischen den Schichten ausgetauschte Objekte
Wir werden bestimmte Objekte aus Version 3 beibehalten. Wir listen sie hier zur Erinnerung auf.
Die Ausnahme [ExceptionImpots] ist die Ausnahme, die die [Dao]-Schicht auslöst, wenn sie auf ein Problem beim Datenzugriff oder mit der Beschaffenheit der Daten (falsche Daten) stößt.
<?php
// namespace
namespace Application;
class ExceptionImpots extends \RuntimeException {
public function __construct(string $message, int $code=0) {
parent::__construct($message, $code);
}
}
Die Klasse [Utilities] enthält Methoden, die für die Verwaltung von Textdateien nützlich sind (in diesem Fall eine einzige Methode):
<?php
// namespace
namespace Application;
// a class of utility functions
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// delete the end-of-line mark from $ligne if it exists
$longueur = strlen($ligne); // line length
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// end - return the line
return($ligne);
}
}
Die Klasse [TaxAdminData] ist die Klasse, die Steuerverwaltungsdaten kapselt:
<?php
namespace Application;
class TaxAdminData {
// tax brackets
private $limites;
private $coeffR;
private $coeffN;
// tax calculation constants
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// initialization
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// retrieve the contents of the tax data file
$fileContents = \file_get_contents($taxAdminDataFilename);
…
// we return the object
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// object's Json string
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// getters and setters
public function getLimites() {
return $this->limites;
}
…
public function setLimites($limites) {
$this->limites = $limites;
return $this;
}
…
}
Wir fügen eine neue Klasse [TaxPayerData] hinzu, die die in die Ergebnisdatei geschriebenen Daten kapselt:
<?php
// namespace
namespace Application;
// data class
class TaxPayerData {
// data required to calculate the taxpayer's tax liability
private $marié;
private $enfants;
private $salaire;
// tax calculation results
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{
// taxpayer data required for tax calculation
$this->marié = $marié;
$this->enfants = $nbEnfants;
$this->salaire = $salaireAnnuel;
// initialize the object
return $this;
}
// getters and setters
public function getMarié() {
return $this->marié;
}
…
public function setMarié($marié) {
$this->marié = $marié;
return $this;
}
…
// toString
public function __toString() {
// object's Json string
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
}
Hinweis: Verwenden Sie die automatische Codegenerierung, um den Konstruktor, die Getter und die Setter zu generieren (siehe den verlinkten Abschnitt). Beachten Sie, dass die Setter „fluent“ sind.
11.3. Die [DAO]-Schicht
Hier konzentrieren wir uns auf die [1]-Schicht unserer Anwendung:

11.3.1. Die [InterfaceDao]-Schnittstelle
Die Schnittstelle für die [DAO]-Schicht sieht wie folgt aus [InterfaceDao.php]:
<?php
// namespace
namespace Application;
interface InterfaceDao {
// reading taxpayer data
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
// reading tax data (tax brackets)
public function getTaxAdminData(): TaxAdminData;
// recording results
public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
Kommentare
- Die Anforderungen lauten wie folgt:
- Die Steuerzahlerdaten werden in einer Textdatei gespeichert;
- die Ergebnisse der Steuerberechnung werden in einer Textdatei gespeichert;
- Etwaige Fehler werden in einer Textdatei gespeichert;
- es ist nicht bekannt, in welchem Format die Daten der Steuerbehörde vorliegen. Für jedes neue Format muss die Schnittstelle [InterfaceDao] durch eine neue Klasse implementiert werden;
- Methoden der Schnittstelle, bei denen beim Zugriff auf Daten ein schwerwiegender Fehler auftritt, müssen eine Ausnahme vom Typ [TaxException] auslösen;
- Zeile 9: Die Methode, die die Daten des Steuerzahlers [Familienstand, Anzahl der Kinder, Jahresgehalt] abruft;
- Der erste Parameter ist der Name der Textdatei, die diese Daten enthält;
- der zweite Parameter ist der Name der Textdatei, in der auftretende Fehler protokolliert werden sollen;
- Zeile 12: Die Methode, die Daten von der Steuerbehörde abruft. Hier werden keine Parameter übergeben, da wir nicht wissen, wie die Daten gespeichert sind;
- Zeile 15: Die Methode, mit der die Ergebnisse der Steuerberechnung in einer Textdatei gespeichert werden, deren Name als Parameter übergeben wird;
Beim Schreiben der Schnittstelle [InterfaceDao] wissen wir, dass es je nach Speichermethode der Steuerverwaltungsdaten verschiedene Möglichkeiten geben wird, die Methode [getTaxAdminData] zu implementieren. Die Schnittstelle [InterfaceDao] wird daher von verschiedenen Klassen implementiert, von denen jede eine bestimmte Speichermethode für diese Daten verwendet (Arrays, Textdateien, Datenbanken, Webdienste). Diese abgeleiteten Klassen werden dennoch gemeinsamen Code nutzen, insbesondere die Implementierung der Methoden [getTaxPayersData] und [saveResults]. Wir wissen, dass dieser Anwendungsfall auf zwei Arten implementiert werden kann (siehe verlinkten Absatz):
- Wir erstellen eine abstrakte Klasse C, die den gemeinsamen Code der abgeleiteten Klassen enthält. Die Klasse C implementiert die Schnittstelle I, aber bestimmte Methoden, die in den abgeleiteten Klassen deklariert werden müssen, werden in der Klasse C als abstrakt deklariert, weshalb die Klasse C selbst abstrakt ist. Anschließend erstellen wir die von C abgeleiteten Klassen C1 und C2, von denen jede die undefinierten (abstrakten) Methoden ihrer übergeordneten Klasse C auf ihre eigene Weise implementiert;
- wir erstellen ein Trait T, das nahezu identisch mit der abstrakten Klasse C aus der vorherigen Lösung ist. Dieses Trait implementiert die Schnittstelle I nicht, da es dies syntaktisch nicht kann. Anschließend erstellen wir die Klassen C1 und C2, die die Schnittstelle I implementieren und das Trait T verwenden. Für diese Klassen bleibt nur noch die Implementierung der Methoden der Schnittstelle I, die nicht durch das von ihnen importierte Trait T implementiert werden;
Für dieses Beispiel verwenden wir hier das Trait [TraitDao].
11.3.2. Das Trait [TraitDao]
Der Code für das Trait [TraitDao] lautet wie folgt [TraitDao.php]:
<?php
// namespace
namespace Application;
trait TraitDao {
// reading taxpayer data
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
// taxpayer data table
$taxPayersData = [];
// error table
$errors = [];
// many errors can occur when managing files
try {
// reading user data
// each line has the form marital status, number of children, annual salary
$taxPayersFile = fopen($taxPayersFilename, "r");
if (!$taxPayersFile) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$taxPayersFilename]", 12);
}
// the current line of the user data file is used
// in the form of marital status, number of children, annual salary
$num = 1; // n° current line
$nbErreurs = 0; // number of errors encountered
while ($ligne = fgets($taxPayersFile, 100)) {
// empty lines are neglected
$ligne = trim($ligne);
if (strlen($ligne) == 0) {
// next line
$num++;
// we loop again
continue;
}
// remove any end-of-line marker
$ligne = Utilitaires::cutNewLineChar($ligne);
// we retrieve the 3 fields married:children:salary which form $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// we check them
// marital status must be yes or no
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// the number of children must be an integer
$enfants = trim($enfants);
if (!preg_match("/^\d+$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// the salary is a whole number without the euro cents
$salaire = trim($salaire);
if (!preg_match("/^\d+$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// mistake?
if ($erreur) {
$errors[] = "la ligne [$num] du fichier [$taxPayersFilename] est erronée";
$nbErreurs++;
} else {
// memorize information
$taxPayersData[] = (new TaxPayerData())->setFromParameters($marié, $enfants, $salaire);
}
// next line
$num++;
}
// are we at the end of the file?
if (!feof($taxPayersFile)) {
// we're out of the loop on a read error
throw new ExceptionImpots("Erreur lors de la lecture de la ligne n° [$num] du fichier [$taxPayersFilename]");
} else {
// we're out of the loop at the end-of-file mark
// save errors in a text file
$this->saveString($errorsFilename, implode("\n", $errors));
// function result
return $taxPayersData;
}
} finally {
// close the file if it is open
if ($taxPayersFile) {
fclose($taxPayersFile);
}
}
}
// recording results
public function saveResults(string $resultsFilename, array $taxPayersData): void {
// save table [$taxPayersData] in text file [$resultsFileName]
// if text file [$resultsFileName] does not exist, it is created
$this->saveString($resultsFilename, implode("\n", $taxPayersData));
}
// saving table results in a text file
private function saveString(string $fileName, string $data): void {
// save table [$data] in text file [$fileName]
// if text file [$fileName] does not exist, it is created
if (file_put_contents($fileName, $data) === FALSE) {
throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
}
}
}
Kommentare
- Zeile 6: Hier definieren wir ein Trait, keine Klasse;
- Zeilen 9–89: Die Methode [getTaxPayersData] implementiert die gleichnamige Methode aus der Schnittstelle [InterfaceDao]. Sie ruft Steuerzahlerdaten [Familienstand, Anzahl der Kinder, Jahres es Gehalt] aus einer Textdatei namens [$taxPayersFilename] ab. Sie gibt diese Daten als Array [$taxPayersData] mit Elementen vom Typ [TaxPayerData] zurück (Zeilen 67, 81);
- Die Methode [getTaxPayersData] ist der im verlinkten Abschnitt beschriebenen Methode [AbstractBaseImpots::executeBatchImpots] sehr ähnlich, weist jedoch folgende Unterschiede auf:
- Die Methode [getTaxPayersData] ruft lediglich Steuerzahlerdaten ab. Sie führt keine Steuerberechnungen durch. Dies ist hier die Aufgabe der [business]-Schicht;
- Genau wie die Methode [executeBatchImpots] meldet sie Fehler. Hier werden Fehler zunächst in einem Array [$errors] gespeichert (Zeile 13), das dann am Ende des Prozesses in einer Textdatei gespeichert wird (Zeile 79). Je nach Situation kann dieses Array leer sein oder auch nicht;
- Im Falle eines schwerwiegenden Fehlers wird eine [ExceptionImpots]-Ausnahme ausgelöst (Zeilen 20, 75);
- Zeile 73: Beachten Sie die Verarbeitung, die beim Verlassen der Schleife in den Zeilen 26–71 durchgeführt wird. Tatsächlich hat die Funktion [fgets] den Nachteil, dass sie den booleschen Wert FALSE zurückgibt, sowohl wenn beim Lesen von Zeilen die Dateiende-Markierung erreicht wird, als auch wenn das Lesen aufgrund eines Fehlers fehlschlägt. Um zwischen den beiden Fällen zu unterscheiden, prüfen wir mit der Funktion [feof], ob wir das Ende der Datei erreicht haben. Wenn wir das Ende der Datei noch nicht erreicht haben, bedeutet dies, dass ein Fehler aufgetreten ist, und wir lösen dann eine Ausnahme aus;
- Zeilen 83–88: Der [finally]-Block wird unabhängig davon ausgeführt, ob während der Dateiverarbeitung eine Ausnahme aufgetreten ist;
- Zeile 85: Wenn die Datei geöffnet wurde, hat der „Handle“ der Datei [$taxPayersFile] den booleschen Wert TRUE; andernfalls ist er FALSE;
- Zeilen 99–105: Die private Methode [saveString] wird in Zeile 79 verwendet, um das Fehlerarray in einer Textdatei zu speichern;
- Zeile 99: Die Methode [saveString] nimmt zwei Parameter entgegen:
- [string $filename], der Name der Textdatei, in der die Daten gespeichert werden;
- [string $data], also die Zeichenkette, die in der Textdatei gespeichert werden soll. Diese Zeichenkette besteht aus einer Folge von Zeilen, die durch das Zeilenumbruchzeichen \n beendet werden;
- Zeile 102: Die PHP-Funktion [file_puts_contents] schreibt eine Zeichenkette in eine Textdatei. Sie öffnet die Datei, schreibt die Zeichenkette hinein und schließt die Datei. Sie gibt FALSE zurück, wenn ein Fehler auftritt;
- Zeile 103: Tritt ein Fehler auf, wird eine Ausnahme ausgelöst;
- Zeilen 92–96: Implementierung der Methode [saveResults] der Schnittstelle [InterfaceDao]. Die private Methode [saveString] wird erneut verwendet. Hier ist der zweite Parameter von [saveString] eine Zeichenkette, die aus dem Array [$taxPayersData] gebildet wird, dessen Elemente vom Typ [TaxPayerData] sind. Man könnte sich fragen, was das Ergebnis dieser Operation sein wird:
implode("\n", $taxPayersData)
Wir haben die folgende [__toString]-Methode in der Klasse [TaxPayerData] definiert (siehe verlinkten Abschnitt):
public function __toString() {
// chaîne Json de l'objet
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
Die Operation
implode("\n", $taxPayersData)
verknüpft jedes Element des Arrays [$taxPayersData] – das durch seine [__toString]-Methode in eine Zeichenkette umgewandelt wird – mit dem Zeilenumbruchzeichen \n. Dies führt zu einer Zeichenkette der Form:
json1\njson2\n…
Fazit
Das Trait [TraitDao] hat zwei der Methoden aus der Schnittstelle [InterfaceDao] implementiert, nämlich [getTaxPayersData] und [saveResults]:
<?php
// namespace
namespace Application;
interface InterfaceDao {
// reading taxpayer data
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
// reading tax data (tax brackets)
public function getTaxAdminData(): TaxAdminData;
// recording results
public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
Wir müssen noch die Methode [getTaxAdminData] implementieren, die Daten von der Steuerverwaltung abruft.
11.3.3. Die Klasse [ImpotsWithTaxAdminDataInJsonFile]
Die Klasse [ImpotsWithTaxAdminDataInJsonFile] implementiert die Schnittstelle [InterfaceDao] wie folgt:
<?php
// namespace
namespace Application;
// definition of a ImpotsWithDataInFile class
class DaoImpotsWithTaxAdminDataInJsonFile implements InterfaceDao {
// use of a line
use TraitDao;
// the TaxAdminData object containing tax bracket data
private $taxAdminData;
// the manufacturer
public function __construct(string $taxAdminDataFilename) {
// we want to initialize the [$this->taxAdminData] attribute
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
}
// returns data for tax calculation
public function getTaxAdminData(): TaxAdminData {
return $this->taxAdminData;
}
}
Kommentare
- Zeile 7: Die Klasse [ImpotsWithTaxAdminDataInJsonFile] implementiert die Schnittstelle [InterfaceDao];
- Zeile 9: Die Klasse [ImpotsWithTaxAdminDataInJsonFile] verwendet das Trait [traitDao], das, wie wir wissen, die Methoden [getTaxPayersData] und [saveResults] der Schnittstelle [InterfaceDao] implementiert. Es bleibt also nur noch, dass die Klasse [ImpotsWithTaxAdminDataInJsonFile] die Methode [getTaxAdminData] implementiert, die Daten von der Steuerverwaltung abruft;
- Zeile 11: das Attribut vom Typ [TaxAdminData], das von der Methode [getTaxAdminData] in den Zeilen 20–22 zurückgegeben wird. Dieses Attribut wird vom Konstruktor in den Zeilen 14–17 initialisiert;
Wir sind nun mit der [DAO]-Schicht unserer Anwendung fertig: Wir haben eine Klasse, die die von uns definierte Schnittstelle [InterfaceDao] vollständig implementiert. Wir können nun zur [business]-Schicht übergehen.
11.4. Die [business]-Schicht
Wir werden nun die Ebene [2] unserer Architektur implementieren:

11.4.1. Die Schnittstelle [InterfaceMétier]
Die Schnittstelle für die [Business]-Schicht sieht wie folgt aus:
<?php
// namespace
namespace Application;
interface InterfaceMetier {
// calculating a taxpayer's taxes
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// batch mode tax calculation
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
Kommentare
- Zeile 9: Die Schnittstelle [BusinessInterface] kann den Steuerbetrag für einen einzelnen Steuerzahler berechnen, sofern folgende Informationen vorliegen: Familienstand, Anzahl der Kinder, Jahresgehalt. Die Methode [calculateTax] verwendet die [DAO]-Schicht nicht, daher löst sie keine Ausnahmen aus;
- Zeile 9: Die Schnittstelle [BusinessInterface] kann auch den Steuerbetrag für eine Gruppe von Steuerzahlern berechnen, deren Daten in der Textdatei mit dem Namen [$taxPayersFileName] gesammelt sind. Sie schreibt die Ergebnisse in eine Textdatei mit dem Namen [$resultsFileName]. Die Methode [executeBatchImpots] muss mit der [dao]-Schicht kommunizieren, die den Zugriff auf das Dateisystem verwaltet. Ausnahmen können dann von der [dao]-Schicht weitergeleitet werden, die die Methode [executeBatchImpots] nicht abfängt: Sie lässt zu, dass sie an das Hauptskript weitergeleitet werden. Nicht schwerwiegende Fehler werden in der Textdatei mit dem Namen [$errorsFileName] protokolliert;
- Zeile 9: Die Methode [calculateTax] ist eine reine [business]-Methode. Sie befasst sich nicht mit der Quelle der von ihr verwendeten Daten;
- Zeile 12: Die Methode [executeBatchImpots] interagiert mit der [dao]-Schicht, um Daten aus Textdateien zu lesen und in diese zu schreiben. Sie ruft wiederholt die Business-Methode [calculerImpot] auf;
11.4.2. Die Klasse [Business]
Die Klasse [Metier] implementiert die Schnittstelle [InterfaceMetier] wie folgt:
<?php
// namespace
namespace Application;
class Metier implements InterfaceMetier {
// dao layer
private $dao;
// tax administration data
private $taxAdminData;
//---------------------------------------------
// setter couche [dao]
public function setDao(InterfaceDao $dao) {
$this->dao = $dao;
return $this;
}
public function __construct(InterfaceDao $dao) {
// a reference is stored on the [dao] layer
$this->dao = $dao;
// recover data for tax calculation
// method [getTaxAdminData] may throw a ExceptionImpots exception
// we then let it go back to the calling code
$this->taxAdminData = $this->dao->getTaxAdminData();
}
// tAX CALCULATION
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
…
// result
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 {
…
// result
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=annualwage-discount
// the allowance has a minimum and a maximum
private function getRevenuImposable(float $salaire): float {
…
// result
return floor($revenuImposable);
}
// calculates any discount
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// result
return ceil($décôte);
}
// calculates any reduction
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// result
return ceil($réduction);
}
// batch mode tax calculation
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
…
// recording results
$this->dao->saveResults($resultsFileName, $results);
}
}
Kommentare
- Zeile 6: Die Klasse [Business] implementiert die Schnittstelle [BusinessInterface], d. h. die Methoden [calculateTax] (Zeilen 30–34) und [executeBatchTaxes] (Zeilen 66–70);
- Zeile 8: Ein Verweis auf die [dao]-Schicht. Dies ist erforderlich, damit die [business]-Schicht weiß, wo sie nach externen Daten suchen muss. Dieses Attribut wird über den Setter in den Zeilen 14–17 oder über den Konstruktor in den Zeilen 19–26 initialisiert;
- Zeile 10: das Objekt vom Typ [TaxAdminData], das die Steuerverwaltungsdaten kapselt. Diese Daten werden von der Geschäftsmethode [calculateTax] benötigt. Dieses Attribut wird über den Konstruktor in den Zeilen 19–26 initialisiert;
- Zeilen 19–26: Der Konstruktor initialisiert die beiden Attribute der Klasse:
- Das Attribut [$dao] wird mit der Referenz initialisiert, die als Parameter an den Konstruktor übergeben wird. Beachten Sie, dass der Typ dieses Parameters dem der Schnittstelle [InterfaceDao] entspricht, wodurch die Klasse [Metier] von jeder Klasse initialisiert werden kann, die diese Schnittstelle implementiert;
- das Attribut [$taxAdminData] wird durch Aufruf der Methode [getTaxAdminData] der [dao]-Schicht initialisiert;
Wir kommen zu dem Schluss, dass bei der Ausführung der Methoden [calculateTaxes] und [executeBatchTaxes] beide Attribute [$dao] und [$taxAdminData] initialisiert werden.
Die Methode [calculateTaxes] lautet wie folgt:
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié : oui, non
// $enfants : nombre d'enfants
// $salaire : salaire annuel
// $this->taxAdminData : données de l'administration fiscale
//
// on vérifie qu'on a bien les données de l'administration fiscale
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// calcul de l'impôt avec enfants
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// calcul de l'impôt sans les enfants
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// application du plafonnement du quotient familial
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euros pour les 2 premiers enfants
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euros pour les 2 premiers enfants, le double pour les suivants
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// on prend l'impôt le plus fort
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"];
}
// calcul d'une éventuelle décôte
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// calcul d'une éventuelle réduction d'impôts
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// résultat
return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
Kommentare
- Dieser Code stammt aus der Methode [AbstractBaseImpots::calculateTax] in Version 3, die im verlinkten Abschnitt erläutert wird. Dasselbe gilt für die privaten Methoden [calculateTax2, getDiscount, getReduction, getTaxableIncome];
Die Methode [Metier::executeBatchImpots] lautet wie folgt:
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// we let the exceptions coming from the [dao] layer flow upwards
// retrieve taxpayer data
$taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
// results table
$results = [];
// we exploit them
foreach ($taxPayersData as $taxPayerData) {
// tax calculation
$result = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
// complete [$taxPayerData]
$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"]);
// put the result in the results table
$results [] = $taxPayerData;
}
// recording results
$this->dao->saveResults($resultsFileName, $results);
}
Kommentare
- Zeile 1: Die Methode muss die Methode [calculateTax] wiederholt für jeden Steuerzahler aufrufen, der in der Textdatei mit dem Namen [$taxPayersFileName] gefunden wird. Sie muss die Ergebnisse in die Textdatei mit dem Namen [$resultsFileName] schreiben. Aufgetretene nicht schwerwiegende Fehler werden in der Textdatei mit dem Namen [$errorsFileName] protokolliert. Die Methode löst selbst keine Ausnahmen aus, lässt jedoch die von der [dao]-Schicht ausgelösten Ausnahmen weiterleiten;
- Zeile 4: Steuerzahlerdaten werden von der [dao]-Schicht angefordert. Dies gibt ein Array von Elementen des Typs [TaxPayerData] zurück, bei dem es sich um eine Klasse mit den Attributen [married, numberOfChildren, salary, amount, deduction, reduction, surcharge, rate] handelt (siehe verlinkten Absatz). Tritt hier eine Ausnahme auf, wird diese automatisch an den aufrufenden Code weitergeleitet, da sie nicht von einem catch-Block abgefangen wird. Das bedeutet, dass im Falle einer Ausnahme Zeile 6 nicht ausgeführt wird;
- Zeile 6: das Ergebnis-Array vom Typ [TaxPayerData];
- Zeilen 8–22: Die Steuer wird für jedes Element des Steuerzahler-Arrays [$taxPayersData] berechnet. Dazu wird die interne Methode [calculateTax] aufgerufen (Zeile 10);
- Zeilen 15–19: Das Ergebnis wird verwendet, um die noch nicht initialisierten Attribute von [TaxPayerData] zu initialisieren;
- Zeile 21: Das Ergebnis wird dem Ergebnis-Array [$results] hinzugefügt;
- Zeile 24: Sobald die Steuer für alle Steuerzahler berechnet wurde, werden die Ergebnisse in einer Textdatei gespeichert. Diese Aufgabe übernimmt die [dao]-Ebene;
Fazit
Im Allgemeinen ist die [Business]-Schicht relativ einfach zu schreiben, da sie mit der [DAO]-Schicht kommuniziert, die den Datenzugriff sowie die damit verbundene Fehlerbehandlung verwaltet.
11.5. Das Hauptskript
Wir werden nun das Skript für die Ebene [3] unserer Architektur schreiben:

Das Hauptskript lautet wie folgt [main.php]:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// error handling by PHP
//ini_set("display_errors", "0");
// interface and class inclusion
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 -----------------------------------------------------
// definition of constants
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// creation of the [business] layer
$métier = new Metier($dao);
// tax calculation in batch mode
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// error is displayed
print $ex->getMessage() . "\n";
}
// end
print "Terminé\n";
exit;
Kommentare
- Zeile 24: Name der Steuerzahler-Datendatei;
- Zeile 25: Name der Ergebnisdatei;
- Zeile 26: Name der Fehlerdatei;
- Zeile 27: Name der JSON-Datei, die die Daten der Steuerbehörde enthält;
- Zeile 31: Erstellung der [dao]-Schicht;
- Zeile 33: Erstellung der [business]-Schicht auf Basis dieser [dao]-Schicht;
- Zeile 35: Ausführung der Methode [executeBatchImpots] der [business]-Schicht;
- Zeilen 36–39: Wir haben gesehen, dass die [business]-Schicht Ausnahmen auslösen kann. Diese werden hier abgefangen;
11.6. Visuelle Tests
11.6.1. Test Nr. 1
Mit der folgenden Steuerzahlerdatei [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
Wir erhalten die folgende Fehlerdatei [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
sowie die folgende Ergebnisdatei [resultats.txt]:
11.6.2. Test Nr. 2
Im Hauptskript weisen wir der Steuerzahlerdatei einen Dateinamen zu, der nicht existiert:
Die in der Konsole angezeigten Ergebnisse lauten wie folgt:
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.
- Zeile 1: Warnungen des PHP-Interpreters;
- Zeile 2: Fehlermeldung der von der [dao]-Schicht ausgelösten Ausnahme;
Es ist möglich, Fehlermeldungen des PHP-Interpreters zu unterdrücken:

Zeile 21 des obigen Codes weist das System an, keine PHP-Fehler anzuzeigen. Während der Entwicklungsphase ist es notwendig, dass diese angezeigt werden. Im Produktionsmodus müssen sie ausgeblendet werden.
Die Ausführungsergebnisse lauten dann wie folgt:
Impossible d'ouvrir en lecture les déclarations des contribuables [taxpayersdata2.txt]
Terminé
11.7. Tests [Codeception]
Visuelle Tests sind völlig unzureichend:
- Wir beschränken uns in der Regel auf nur wenige Tests;
- wir achten bei dieser Sichtprüfung möglicherweise nicht genau genug darauf, und Details können uns entgehen;
In der Praxis der Softwareentwicklung werden Tests von engagierten Fachleuten verfasst, für die dies ihre Hauptaufgabe ist. Sie sind bestrebt, die Tests so umfassend wie möglich zu gestalten. Dazu nutzen sie Test-Frameworks.
Hier verwenden wir das Codeception-Framework [https://codeception.com/], da es in NetBeans integriert werden kann. Es handelt sich um ein Framework mit einem breiten Spektrum an Funktionen. Wir werden nur einige davon nutzen. Die Idee dahinter ist, nach jeder neuen Version der Anwendungsübung schnell überprüfen zu können, ob sie funktioniert. Das Vorhandensein erfolgreicher Tests gibt dem Entwickler Vertrauen in den von ihm geschriebenen Code. Dies ist ein wichtiger Faktor.
11.7.1. Installation des [Codeception]-Frameworks
Wie viele PHP-Bibliotheken wird das [Codeception]-Framework mit [Composer] installiert. Dazu öffnen wir ein Laragon-Terminal (siehe Link im Absatz).
Zunächst müssen wir das PHPUnit-Testframework [https://phpunit.de/] installieren. Der Grund dafür ist, dass Codeception im Hintergrund das PHPUnit-Framework nutzt:

Als Nächstes installieren wir das Codeception-Framework:

Das war's. Schauen wir uns nun an, wie man [Codeception] in NetBeans integriert.
11.7.2. Integration von [Codeception] in NetBeans

- Rufen Sie in [1-2] die Projekteigenschaften auf;
- In [3-4] haben wir [Codeception] als eines der Test-Frameworks des Projekts festgelegt;


- In [5-8] initialisieren Sie das [Codeception]-Framework für das Projekt;

- In [9] wurde ein Ordner [tests] erstellt, zusammen mit einer Konfigurationsdatei [codeception.yml] in [10-11]. Die Datei [11] ist identisch mit der Datei [10]. Codeception hat lediglich einen Ordner [Important Files] erstellt, um der Datei [10] eine besondere Kennzeichnung zu geben;
- In [12-13] kehren wir zu den Projekteigenschaften zurück;

- in [14-16] wird der Ordner [tests] [16] als Testordner des Projekts festgelegt;
- in [16] erscheint der Ordner [tests] dann unter dem neuen Namen [Test Files]. Das Vorhandensein dieses Ordners in einem PHP-Projekt zeigt an, dass das Projekt ein Unit-Testing-Framework enthält;
- Wir werden unsere Tests im Ordner [unit] [17] erstellen;
11.7.3. Tests für die [dao]-Schicht

- Wir werden alle unsere Tests im Ordner [unit] [1] erstellen;
- Die Namen von [Codeception]-Testklassen müssen mit dem Schlüsselwort [Test] enden, andernfalls werden die Klassen nicht als Testklassen erkannt;
Unsere [Codeception]-Testklassen haben das folgende Format [https://codeception.com/docs/05-UnitTests]:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// loading the test environment
…
class DaoTest extends \Codeception\Test\Unit {
// test attributes
private $attribut1;
public function __construct() {
parent::__construct();
// test environment initialization
…
}
// 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);
…
}
}
Kommentare
- Zeile 7: Die Testklassen befinden sich im selben Namespace wie die zu testende Anwendung;
- Zeilen 9–10: Hier stehen die [require]-Anweisungen zum Laden der zu testenden Klassen und Schnittstellen;
- Zeile 12: Der Name der Testklasse muss mit dem Schlüsselwort [Test] enden. Diese Klasse muss die Klasse [\Codeception\Test\Unit] erweitern;
- Zeilen 16–20: Der Konstruktor ermöglicht es uns, die Testumgebung zu initialisieren;
- Zeile 23: Die Namen der Testmethoden müssen mit dem Schlüsselwort [test] beginnen;
- Zeilen 25–31: Es können verschiedene Testmethoden verwendet werden;
Die Testklasse [DaoTest] sieht wie folgt aus:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// constants
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
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 -----------------------------------------------------
// definition of constants
const TAXADMINDATA_FILENAME = "taxadmindata.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
…
}
}
Kommentare
Um die Tests für eine Version der Anwendungsübung zu erstellen, verwenden wir eine Umgebung, die mit der des Hauptskripts dieser Version identisch ist. Für Version 04 ist dies das folgende [main.php]-Skript:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// error handling by PHP
ini_set("display_errors", "0");
// interface and class inclusion
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 -----------------------------------------------------
// definition of constants
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// creation of the [business] layer
$métier = new Metier($dao);
// tax calculation in batch mode
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// error is displayed
print $ex->getMessage() . "\n";
}
// end
print "Terminé\n";
exit;
Um die [dao]-Schicht zu testen, verwenden wir in der Testklasse:
- verwenden wir die Umgebung aus den Zeilen 13–27 von [main.php];
- im Konstruktor der Testklasse instanziieren wir die [dao]-Schicht wie in Zeile 31;
- wir schreiben die Testmethoden;
Wir werden bei allen Testklassen auf diese Weise vorgehen.
Kehren wir zum vollständigen Code für die Testklasse zurück:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// constants
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
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 -----------------------------------------------------
// definition of constants
const TAXADMINDATA_FILENAME = "taxadmindata.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
// calculation constants
$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());
// tax brackets
$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());
}
}
Kommentare
- Zeilen 10–25: Laden der für den Test erforderlichen Umgebung und Definieren von Konstanten;
- Zeilen 31–36: Aufbau der [dao]-Schicht (Zeile 34), gefolgt von der Initialisierung des Attributs [$taxAdminData] (Zeile 29). Dieses Attribut enthält Daten zur Steuerverwaltung;
- Zeilen 39–55: die einzige Testmethode. Diese besteht darin, zu überprüfen, ob der Inhalt des Attributs [$taxAdminData] den Erwartungen entspricht;
- Zeilen 41–50: Überprüfung der Konstanten für die Steuerberechnung;
- Zeilen 52–55: Überprüfung der Steuerklassen. Die Methode [assertSame] überprüft, ob zwei PHP-Entitäten – in diesem Fall Arrays – identisch sind;
Um diese Testklasse auszuführen, gehen Sie wie folgt vor:

- Führen Sie in [1-2] den Test aus;
- [3]: das Fenster mit den Testergebnissen;
- [4]: die ausgeführte Testklasse;
- [5]: die Ergebnisse. Hier hat die einzelne Testmethode bestanden;
- [6]: Wenn der Test fehlschlägt oder, was häufiger vorkommt, wenn kein Test ausgeführt wurde, überprüfen Sie Fenster [6]. Meistens konnte die Testumgebung nicht geladen werden, sodass kein Test ausgeführt werden konnte. Die in [6] angezeigten Fehler entsprechen denen, die Sie beim Ausführen eines Standard-PHP-Skripts sehen würden;
Sehen wir uns ein Beispiel für einen fehlgeschlagenen Test an:
In der Testklasse fügen wir einen Fehler in die Definition einer Konstante ein:
// constantes
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04x");
Anschließend führen wir den Test aus. Das Ergebnis lautet wie folgt:

In Fenster [4]:

11.7.4. Tests der [Business]-Schicht
Die Testklasse [MetierTest] folgt denselben Konstruktionsregeln wie die Klasse [DaoTest], verfügt jedoch über mehr Testmethoden:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// constants
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
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 -----------------------------------------------------
// definition of constants
const TAXADMINDATA_FILENAME = "taxadmindata.json";
class MetierTest extends \Codeception\Test\Unit {
// business layer
private $métier;
public function __construct() {
parent::__construct();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
// creation of the [business] layer
$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"]);
}
}
Kommentare
- Zeilen 10–25: Laden der Dateien, die die Testumgebung definieren. Dies entspricht der Vorgehensweise für die [dao]-Schicht;
- Zeilen 31–37: Instanziierung der [dao]- und [business]-Schichten;
- Zeilen 40–47: Ein Test zur Steuerberechnung;
- Zeile 41: Eine spezifische Steuerberechnung wird unter Verwendung der [business]-Schicht durchgeführt;
- Zeilen 42–46: Überprüfung, ob die erhaltenen Ergebnisse mit denen des Simulators der Steuerbehörde übereinstimmen [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm];
- Zeilen 23–26: Es werden Gleichheitstests mit einer Genauigkeit von 1 Euro durchgeführt. Tatsächlich haben wir festgestellt, dass Rundungsprobleme dazu führten, dass der Algorithmus des Dokuments die erwarteten Ergebnisse mit einer Genauigkeit von 1 Euro lieferte;
- Zeile 27: Der Steuersatz wird ohne Fehlermarge berechnet;
- Zeilen 49–137: Diese Art von Test wird zehnmal wiederholt, jedes Mal mit einer anderen Steuerzahlerkonfiguration;
Die Tests liefern folgende Ergebnisse:

11.7.5. Tests für zukünftige Versionen
In Zukunft werden die Tests für die [dao]- und [business]-Schichten identisch mit denen in Version 04 sein. Lediglich die Testumgebung wird sich ändern. Wir werden daher nur diese Umgebung und die Testergebnisse präsentieren.