Skip to content

11. Esercizio pratico – versione 4

L’applicazione per il calcolo delle imposte implementerà la seguente struttura a livelli:

Image

Riprenderemo gli elementi della versione 3 del paragrafo "link", modificandoli per adattarli alla nuova architettura dell’applicazione. Questo processo viene talvolta definito "refactoring". Supponiamo qui che i dati necessari all’applicazione si trovino in file di testo. Sarà il livello [Dao] a occuparsi degli scambi con questi file.

11.1. Struttura degli script

Image

11.2. Oggetti scambiati tra i livelli

Manterremo alcuni oggetti della versione 3. Li riportiamo qui di seguito a titolo di promemoria.

L’eccezione [ExceptionImpots] è l’eccezione che verrà generata dal livello [Dao] quando si verificherà un problema relativo all’accesso ai dati o alla natura dei dati (dati errati).


<?php

// spazio dei nomi
namespace Application;

class ExceptionImpots extends \RuntimeException {

  public function __construct(string $message, int $code=0) {
    parent::__construct($message, $code);
  }
}

La classe [Utilitaires] raggruppa metodi utili alla gestione dei file di testo (in questo caso un unico metodo):


<?php

// spazio dei nomi
namespace Application;

// una classe di funzioni di utilità
abstract class Utilitaires {

  public static function cutNewLinechar(string $ligne): string {
    // si rimuove il carattere di fine riga da $ligne se presente
    $longueur = strlen($ligne);  // lunghezza riga
    while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
      $ligne = substr($ligne, 0, $longueur - 1);
      $longueur--;
    }
    // fine - si restituisce la riga
    return($ligne);
  }
}

La classe [TaxAdminData] è la classe che incapsula i dati dell’amministrazione fiscale:


<?php

namespace Application;

class TaxAdminData {
  // scaglioni fiscali
  private $limites;
  private $coeffR;
  private $coeffN;
  // costanti di calcolo dell'imposta
  private $plafondQfDemiPart;
  private $plafondRevenusCelibatairePourReduction;
  private $plafondRevenusCouplePourReduction;
  private $valeurReducDemiPart;
  private $plafondDecoteCelibataire;
  private $plafondDecoteCouple;
  private $plafondImpotCouplePourDecote;
  private $plafondImpotCelibatairePourDecote;
  private $abattementDixPourcentMax;
  private $abattementDixPourcentMin;

  // inizializzazione
  public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
    // si recupera il contenuto del file dei dati fiscali
    $fileContents = \file_get_contents($taxAdminDataFilename);

    // si restituisce l'oggetto
    return $this;
  }

  private function check($value): \stdClass {

    return $result;
  }

    // toString
  public function __toString() {
    // stringa JSON dell'oggetto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

  // getter e setter
  public function getLimites() {
    return $this->limites;
  }



  public function setLimites($limites) {
    $this->limites = $limites;
    return $this;
  }


}

Aggiungiamo una nuova classe [TaxPayerData] che incapsula i dati scritti nel file dei risultati:


<?php

// spazio dei nomi
namespace Application;

// classe dei dati
class TaxPayerData {
  // dati necessari per il calcolo dell'imposta del contribuente
  private $marié;
  private $enfants;
  private $salaire;
  // risultati del calcolo dell'imposta
  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{
    // dati del contribuente necessari per il calcolo dell'imposta
    $this->marié = $marié;
    $this->enfants = $nbEnfants;
    $this->salaire = $salaireAnnuel;
    // si restituisce l'oggetto inizializzato
    return $this;
  }

  // getter e setter
  public function getMarié() {
    return $this->marié;
  }



  public function setMarié($marié) {
    $this->marié = $marié;
    return $this;
  }



    // toString
  public function __toString() {
    // stringa JSON dell'oggetto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

}

Nota: utilizzare la generazione automatica del codice per generare il costruttore, i getter e i setter (cfr. paragrafo link). Si noti che i setter sono «fluenti».

11.3. Il livello [dao]

Qui ci interessiamo al livello [1] della nostra applicazione:

Image

11.3.1. L’interfaccia [InterfaceDao]

L’interfaccia del livello [dao] sarà la seguente [InterfaceDao.php]:


<?php

// spazio dei nomi
namespace Application;

interface InterfaceDao {

  // lettura dei dati dei contribuenti
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // lettura dei dati dell'amministrazione fiscale (scaglioni d'imposta)
  public function getTaxAdminData(): TaxAdminData;

  // registrazione dei risultati
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Commenti

  • le specifiche sono le seguenti:
    • i dati dei contribuenti sono contenuti in un file di testo;
    • i risultati del calcolo delle imposte vengono salvati in un file di testo;
    • gli eventuali errori vengono salvati in un file di testo;
    • non si sa in quale formato siano disponibili i dati dell'amministrazione fiscale. Per ogni nuovo formato, l'interfaccia [InterfaceDao] dovrà essere implementata da una nuova classe;
    • i metodi dell’interfaccia che incontrano un errore irreversibile durante l’accesso ai dati devono generare un’eccezione di tipo [ExceptionImpots];
  • riga 9: il metodo che consente di ottenere i dati del contribuente [statut marital, nombre d’enfants, salaire annuel];
    • il primo parametro è il nome del file di testo in cui si trovano questi dati;
    • il secondo parametro è il nome del file di testo in cui salvare gli eventuali errori riscontrati;
  • riga 12: il metodo che consente di ottenere i dati dall’amministrazione fiscale. Qui non gli viene passato alcun parametro poiché non si sa come siano memorizzati;
  • riga 15: il metodo che consente di salvare i risultati del calcolo dell’imposta in un file di testo il cui nome viene passato come parametro;

Quando si scrive l’interfaccia [InterfaceDao], si sa che ci saranno diversi modi di scrivere il metodo [getTaxAdminData] a seconda di come saranno memorizzati i dati dell’amministrazione fiscale. L’interfaccia [InterfaceDao] sarà quindi implementata da diverse classi, ciascuna delle quali si occuperà di una specifica modalità di archiviazione di tali dati (tabelle, file di testo, database, servizio web). Queste classi derivate avranno tuttavia un codice comune, ovvero quello relativo all’implementazione dei metodi [getTaxPayersData, saveResults]. È noto che questo caso d’uso può essere implementato in due modi (cfr. paragrafo link):

  1. si crea una classe astratta C che raggruppa il codice comune alle classi derivate. La classe C implementa l’interfaccia I, ma alcuni metodi che devono essere dichiarati nelle classi derivate sono dichiarati astratti nella classe C e quindi la classe C è essa stessa astratta. Si creano quindi le classi C1 e C2 derivate da C, che implementano ciascuna a modo proprio i metodi non definiti (astratti) della loro classe padre C;
  2. si crea un tratto T quasi identico alla classe astratta C della soluzione precedente. Questo tratto non implementa l’interfaccia I poiché sintatticamente non è in grado di farlo. Si creano quindi le classi C1 e C2 che implementano l’interfaccia I e utilizzano il tratto T. A queste classi non resta che implementare i metodi dell’interfaccia I non implementati dal tratto T che importano;

Per l’esempio, useremo qui un tratto [TraitDao].

11.3.2. Il tratto [TraitDao]

Il codice del tratto [TraitDao] è il seguente [TraitDao.php]:


<?php

// spazio dei nomi
namespace Application;

trait TraitDao {

  // lettura dei dati dei contribuenti
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // tabella dei dati dei contribuenti
    $taxPayersData = [];
    // tabella degli errori
    $errors = [];
    // possono verificarsi numerosi errori durante la gestione dei file
    try {
      // lettura dei dati utente
      // ogni riga ha il formato: stato civile, numero di figli, stipendio annuo
      $taxPayersFile = fopen($taxPayersFilename, "r");
      if (!$taxPayersFile) {
        throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$taxPayersFilename]", 12);
      }
      // si elabora la riga corrente del file dei dati utente
      // che ha il formato: stato civile, numero di figli, stipendio annuo
      $num = 1;         // numero della riga corrente
      $nbErreurs = 0;   // numero di errori riscontrati
      while ($ligne = fgets($taxPayersFile, 100)) {
        // si ignorano le righe vuote
        $ligne = trim($ligne);
        if (strlen($ligne) == 0) {
          // riga successiva
          $num++;
          // si torna all'inizio
          continue;
        }
        // si rimuove l'eventuale carattere di fine riga
        $ligne = Utilitaires::cutNewLineChar($ligne);
        // si recuperano i 3 campi coniuge:figli:stipendio che formano $ligne
        list($marié, $enfants, $salaire) = explode(",", $ligne);
        // li si verifica
        // lo stato civile deve essere «sì» o «no»
        $marié = trim(strtolower($marié));
        $erreur = ($marié !== "oui" and $marié !== "non");
        if (!$erreur) {
          // il numero di figli deve essere un numero intero
          $enfants = trim($enfants);
          if (!preg_match("/^\d+$/", $enfants)) {
            $erreur = TRUE;
          } else {
            $enfants = (int) $enfants;
          }
        }
        if (!$erreur) {
          // lo stipendio è un numero intero senza i centesimi di euro
          $salaire = trim($salaire);
          if (!preg_match("/^\d+$/", $salaire)) {
            $erreur = TRUE;
          } else {
            $salaire = (int) $salaire;
          }
        }
        // errore?
        if ($erreur) {
          $errors[] = "la ligne [$num] du fichier [$taxPayersFilename] est erronée";
          $nbErreurs++;
        } else {
          // si salvano le informazioni
          $taxPayersData[] = (new TaxPayerData())->setFromParameters($marié, $enfants, $salaire);
        }
        // riga successiva
        $num++;
      }
      // si è giunti alla fine del file?
      if (!feof($taxPayersFile)) {
        // si è usciti dal ciclo a causa di un errore di lettura
        throw new ExceptionImpots("Erreur lors de la lecture de la ligne n° [$num] du fichier [$taxPayersFilename]");
      } else {
        // si è usciti dal ciclo al raggiungimento del marcatore di fine file
        // si salvano gli errori in un file di testo
        $this->saveString($errorsFilename, implode("\n", $errors));
        // risultato della funzione
        return $taxPayersData;
      }
    } finally {
      // si chiude il file se è aperto
      if ($taxPayersFile) {
        fclose($taxPayersFile);
      }
    }
  }

  // salvataggio dei risultati
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // salvataggio della tabella [$taxPayersData] nel file di testo [$resultsFileName]
    // se il file di testo [$resultsFileName] non esiste, viene creato
    $this->saveString($resultsFilename, implode("\n", $taxPayersData));
  }

  // salvataggio dei risultati di una tabella in un file di testo
  private function saveString(string $fileName, string $data): void {
    // salvataggio della tabella [$data] nel file di testo [$fileName]
    // se il file di testo [$fileName] non esiste, viene creato
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Commenti

  • riga 6: qui definiamo un tratto e non una classe;
  • righe 9-89: il metodo [getTaxPayersData] implementa il metodo omonimo dell’interfaccia [InterfaceDao]. Esso recupera da un file di testo denominato [$taxPayersFilename] i dati dei contribuenti [statut marital, nombre d’enfants, salaire annuel]. Li restituisce sotto forma di una tabella [$taxPayersData] composta da elementi di tipo [TaxPayerData] (righe 67, 81);
  • il metodo [getTaxPayersData] è molto simile al metodo [AbstractBaseImpots::executeBatchImpots] descritto nel paragrafo «Collegamento», con le seguenti differenze:
    • il metodo [getTaxPayersData] si limita a recuperare i dati dei contribuenti. Non effettua alcun calcolo dell’imposta. In questo caso, tale funzione spetta al livello [métier];
    • come faceva il metodo [executeBatchImpots], segnala gli errori. In questo caso gli errori vengono prima memorizzati in un array [$errors] (riga 13), array che viene salvato in un file di testo al termine dell’elaborazione (riga 79). A seconda dei casi, può essere vuoto o meno;
    • in caso di errore irreversibile, viene generata un’eccezione di tipo [ExceptionImpots] (righe 20, 75);
  • riga 73: si noti l’elaborazione effettuata all’uscita dal ciclo delle righe 26-71. Infatti, la funzione [fgets] presenta l’inconveniente di impostare il valore booleano FALSE sia quando la lettura delle righe ha raggiunto il marcatore di fine file, sia quando tale lettura non è andata a buon fine a causa di un errore. Per distinguere i due casi, si verifica se si è giunti alla fine del file con la funzione [feof]. Se non si è giunti alla fine del file, significa che si è verificato un errore e quindi si genera un'eccezione;
  • righe 83-88: la funzione [finally] viene eseguita indipendentemente dal fatto che si sia verificata o meno un’eccezione durante l’elaborazione del file;
  • riga 85: se il file è stato aperto, l’«handle» [$taxPayersFile] del file assume il valore booleano TRUE, altrimenti FALSE;
  • righe 99-105: il metodo privato [saveString] utilizzato alla riga 79 per salvare la tabella degli errori in un file di testo;
  • riga 99: il metodo [saveString] accetta due parametri:
    • [string $filename], che è il nome del file di testo utilizzato per salvare i dati;
    • [string $data], che è la stringa da salvare nel file di testo. Questa stringa sarà costituita da una serie di righe terminate dal carattere di fine riga \n;
  • riga 102: la funzione PHP [file_puts_contents] salva una stringa di caratteri in un file di testo. Si occupa di aprire il file, di scriverci la stringa e di chiudere il file. Restituisce il valore booleano FALSE se si è verificato un errore;
  • riga 103: se si verifica un errore, viene generata un’eccezione;
  • righe 92-96: implementazione del metodo [saveResults] dell’interfaccia [InterfaceDao]. Si utilizza nuovamente il metodo privato [saveString]. Qui il secondo parametro di [saveString] è una stringa costruita a partire dall’array [$taxPayersData], i cui elementi sono di tipo [TaxPayerData]. Ci si può chiedere quale sarà il risultato dell’operazione:

implode("\n", $taxPayersData)

Nella classe [TaxPayerData] (paragrafo «link») abbiamo definito il seguente metodo [__toString]:


public function __toString() {
    // stringa JSON dell'oggetto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

L’operazione


implode("\n", $taxPayersData)

concatenerà ogni elemento dell’array [$taxPayersData], trasformato in stringa di caratteri dal suo metodo [__toString], con il carattere di fine riga \n. Il risultato sarà una stringa di caratteri della forma:

json1\njson2\n…

Conclusione

Il tratto [TraitDao] ha implementato due dei metodi dell’interfaccia [InterfaceDao], [getTaxPayersData] e [saveResults]:


<?php

// spazio dei nomi
namespace Application;

interface InterfaceDao {

  // lettura dei dati dei contribuenti
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // lettura dei dati dell'amministrazione fiscale (scaglioni d'imposta)
  public function getTaxAdminData(): TaxAdminData;

  // registrazione dei risultati
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Resta da implementare il metodo [getTaxAdminData] che recupera i dati dall’amministrazione fiscale.

11.3.3. La classe [ImpotsWithTaxAdminDataInJsonFile]

La classe [ImpotsWithTaxAdminDataInJsonFile] implementa l’interfaccia [InterfaceDao] nel modo seguente:


<?php

// spazio dei nomi
namespace Application;

// definizione di una classe ImpotsWithDataInFile
class DaoImpotsWithTaxAdminDataInJsonFile implements InterfaceDao {
  // utilizzo di una caratteristica
  use TraitDao;
  // l'oggetto di tipo TaxAdminData che contiene i dati delle fasce di imposta
  private $taxAdminData;

  // il costruttore
  public function __construct(string $taxAdminDataFilename) {
    // si desidera inizializzare l'attributo [$this->taxAdminData]
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // restituisce i dati che consentono il calcolo dell'imposta
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }
}

Commenti

  • riga 7: la classe [ImpotsWithTaxAdminDataInJsonFile] implementa l’interfaccia [InterfaceDao];
  • riga 9: la classe [ImpotsWithTaxAdminDataInJsonFile] utilizza il tratto [traitDao] che, come sappiamo, implementa i metodi [getTaxPayersData] e [saveResults] dell’interfaccia [InterfaceDao]. Alla classe [ImpotsWithTaxAdminDataInJsonFile] non resta quindi che implementare il metodo [getTaxAdminData] che recupera i dati dall’amministrazione fiscale;
  • riga 11: l’attributo di tipo [TaxAdminData] restituito dal metodo [getTaxAdminData] delle righe 20-22. Questo attributo viene inizializzato dal costruttore delle righe 14-17;

Abbiamo terminato con il livello [dao] della nostra applicazione: disponiamo di una classe che implementa completamente l’interfaccia [InterfaceDao] che ci siamo imposti. Ora possiamo passare al livello [métier].

11.4. Il livello [métier]

Ora implementeremo il livello [2] della nostra architettura:

Image

11.4.1. L'interfaccia [InterfaceMétier]

L’interfaccia del livello [métier] sarà la seguente:


<?php

// spazio dei nomi
namespace Application;

interface InterfaceMetier {

  // calcolo delle imposte di un contribuente
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // calcolo delle imposte in modalità batch
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}

Commenti

  • riga 9: l’interfaccia [InterfaceMétier] è in grado di calcolare l’importo dell’imposta di un contribuente privato, purché le vengano fornite le seguenti informazioni: stato civile, numero di figli, stipendio annuo. Il metodo [calculerImpot] non utilizza il livello [dao], pertanto non genera eccezioni;
  • riga 9: l’interfaccia [InterfaceMétier] è in grado di calcolare l’importo dell’imposta anche per un insieme di contribuenti i cui dati sono raccolti nel file di testo denominato [$taxPayersFileName]. Essa salva i risultati in un file di testo denominato [$resultsFileName]. Il metodo [executeBatchImpots] deve rivolgersi al livello [dao], che gestisce gli accessi al sistema di file. È quindi possibile che le eccezioni vengano propagate dal livello [dao] senza essere intercettate dal metodo [executeBatchImpots]: quest’ultimo le lascerà propagare allo script principale. Gli errori non fatali vengono registrati nel file di testo denominato [$errorsFileName];
  • riga 9: il metodo [calculerImpot] è un metodo puramente [métier]. Non si preoccupa della provenienza dei dati che utilizza;
  • riga 12: il metodo [executeBatchImpots] si rivolgerà al livello [dao] per leggere e scrivere dati nei file di testo. Chiamerà ripetutamente il metodo di business [calculerImpot];

11.4.2. La classe [Metier]

La classe [Metier] implementa l’interfaccia [InterfaceMetier] nel modo seguente:


<?php

// spazio dei nomi
namespace Application;

class Metier implements InterfaceMetier {
  // livello DAO
  private $dao;
  // dati dell'amministrazione fiscale
  private $taxAdminData;

  //---------------------------------------------
  // setter del livello [dao]
  public function setDao(InterfaceDao $dao) {
    $this->dao = $dao;
    return $this;
  }

  public function __construct(InterfaceDao $dao) {
    // si memorizza un riferimento sul livello [dao]
    $this->dao = $dao;
    // si recuperano i dati necessari per il calcolo dell'imposta
    // il metodo [getTaxAdminData] può generare un'eccezione ExceptionImpots
    // si lascia quindi che l'eccezione venga propagata al codice chiamante
    $this->taxAdminData = $this->dao->getTaxAdminData();
  }

// calcolo dell'imposta
// --------------------------------------------------------------------------
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {

    // risultato
    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 {

    // risultato
    return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
  }

  // revenuImposable=stipendioAnnuale-detrazione
  // la detrazione ha un valore minimo e uno massimo
  private function getRevenuImposable(float $salaire): float {

    // risultato
    return floor($revenuImposable);
  }

// calcola un'eventuale riduzione
  private function getDecôte(string $marié, float $salaire, float $impots): float {

    // risultato
    return ceil($décôte);
  }

// calcola un'eventuale riduzione
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

    // risultato
    return ceil($réduction);
  }

  // calcolo delle imposte in modalità batch
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {

    // registrazione dei risultati
    $this->dao->saveResults($resultsFileName, $results);
  }

}

Commenti

  • riga 6: la classe [Metier] implementa l’interfaccia [InterfaceMetier], ovvero i metodi [calculerImpot] (righe 30-34) e [executeBatchImpots] (righe 66-70);
  • riga 8: un riferimento al livello [dao]. Ne è necessario almeno uno affinché il livello [métier] sappia a chi rivolgersi quando richiede dati esterni. Questo attributo verrà inizializzato tramite il setter delle righe 14-17 o tramite il costruttore delle righe 19-26;
  • riga 10: l’oggetto di tipo [TaxAdminData] che incapsula i dati dell’amministrazione fiscale. Questi dati sono necessari al metodo di business [calculerImpot]. Questo attributo viene inizializzato tramite il costruttore alle righe 19-26;
  • righe 19-26: il costruttore inizializza i due attributi della classe:
    • l’attributo [$dao] viene inizializzato con il riferimento passato come parametro al costruttore. Si noti che il tipo di questo parametro è quello dell’interfaccia [InterfaceDao], consentendo così alla classe [Metier] di essere inizializzata da qualsiasi classe che implementi tale interfaccia;
    • l’attributo [$taxAdminData] viene inizializzato richiamando il metodo [getTaxAdminData] del livello [dao];

Se ne deduce che, quando vengono eseguiti i metodi [calculerImpots] e [executeBatchImpots], vengono inizializzati entrambi gli attributi [$dao] e [$taxAdminData].

Il metodo [calculerImpots] è il seguente:


public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    // $marié: sì, no
    // $enfants: numero di figli
    // $salaire: stipendio annuo
    // $this->taxAdminData: dati dell'amministrazione fiscale
    //
    // si verifica la presenza dei dati dell'amministrazione fiscale
    if ($this->taxAdminData === NULL) {
      $this->taxAdminData = $this->getTaxAdminData();
    }
    // calcolo dell’imposta con figli
    $result1 = $this->calculerImpot2($marié, $enfants, $salaire);
    $impot1 = $result1["impôt"];
    // calcolo dell'imposta senza figli
    if ($enfants != 0) {
      $result2 = $this->calculerImpot2($marié, 0, $salaire);
      $impot2 = $result2["impôt"];
      // applicazione del limite massimo del quoziente familiare
      $plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
      if ($enfants < 3) {
        // $PLAFOND_QF_DEMI_PART euro per i primi 2 figli
        $impot2 = $impot2 - $enfants * $plafonDemiPart;
      } else {
        // $PLAFOND_QF_DEMI_PART euro per i primi 2 figli, il doppio per i successivi
        $impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
      }
    } else {
      $impot2 = $impot1;
      $result2 = $result1;
    }
    // si applica l’imposta più elevata
    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"];
    }
    // calcolo di un'eventuale riduzione
    $décôte = $this->getDecôte($marié, $salaire, $impot);
    $impot -= $décôte;
    // calcolo di un'eventuale riduzione delle imposte
    $réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
    $impot -= $réduction;
    // risultato
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }

Commenti

  • questo codice corrisponde al metodo [AbstractBaseImpots::calculerImpot] della versione 3, spiegato nel paragrafo "link". Lo stesso vale per i metodi privati [calculerImpot2, getDecôte, getRéduction, getRevenuImposable];

Il metodo [Metier::executeBatchImpots] è il seguente:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // si lasciano risalire le eccezioni provenienti dal livello [dao]
    // si recuperano i dati dei contribuenti
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabella dei risultati
    $results = [];
    // si analizzano
    foreach ($taxPayersData as $taxPayerData) {
      // si calcola l'imposta
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // si completa [$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"]);
      // si inserisce il risultato nella tabella dei risultati
      $results [] = $taxPayerData;
    }
    // registrazione dei risultati
    $this->dao->saveResults($resultsFileName, $results);
  }

Commenti

  • riga 1: il metodo deve richiamare ripetutamente il metodo [calculerImpot] per ciascuno dei contribuenti presenti nel file di testo denominato [$taxPayersFileName]. Deve inserire i risultati nel file di testo denominato [$resultsFileName]. Gli errori non fatali riscontrati vengono registrati nel file di testo denominato [$errorsFileName]. Il metodo non genera eccezioni autonomamente, ma lascia che vengano propagate quelle generate dal livello [dao];
  • riga 4: i dati dei contribuenti vengono richiesti al livello [dao]. Quest’ultimo restituisce un array di elementi di tipo [TaxPayerData], che è una classe di attributi [marié, nbEnfants, salaire, montant, décôte, réduction, surcôte, taux] (cfr. paragrafo «link»). Se qui si verifica un'eccezione, poiché non viene intercettata da un catch, essa verrà automaticamente segnalata al codice chiamante. Ciò significa che, in caso di eccezione, la riga 6 non viene eseguita;
  • riga 6: l’array dei risultati di tipo [TaxPayerData];
  • righe 8-22: si calcola l’imposta per ciascuno degli elementi dell’array dei contribuenti [$taxPayersData]. A tal fine, si ricorre al metodo interno [calculerImpot] (riga 10);
  • righe 15-19: il risultato ottenuto viene utilizzato per inizializzare gli attributi di [TaxPayerData] che non erano ancora stati inizializzati;
  • riga 21: il risultato ottenuto viene accumulato nella tabella dei risultati [$results];
  • riga 24: una volta calcolata l’imposta per tutti i contribuenti, i risultati vengono salvati in un file di testo. È il livello [dao] a svolgere questa operazione;

Conclusione

In generale, il livello [métier] è abbastanza semplice da scrivere poiché si rivolge al livello [dao], che a sua volta gestisce l’accesso ai dati con la relativa gestione degli errori.

11.5. Lo script principale

Ora scriviamo lo script del livello [3] della nostra architettura:

Image

Lo script principale è il seguente: [main.php]:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// gestione degli errori tramite PHP
//ini_set("display_errors", "0");

// inclusione di interfacce e classi
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 -----------------------------------------------------
// definizione delle costanti
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // creazione del livello [dao]
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // creazione del livello [métier]
  $métier = new Metier($dao);
  // calcolo delle imposte in modalità batch
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // viene visualizzato l'errore
  print $ex->getMessage() . "\n";
}
// fine
print "Terminé\n";
exit;


Commenti

  • riga 24: il nome del file contenente i dati dei contribuenti;
  • riga 25: il nome del file dei risultati;
  • riga 26: il nome del file degli errori;
  • riga 27: il nome del file jSON contenente i dati dell'amministrazione fiscale;
  • riga 31: creazione del livello [dao];
  • riga 33: creazione del livello [métier] basato su questo livello [dao];
  • riga 35: esecuzione del metodo [executeBatchImpots] del livello [métier];
  • righe 36-39: abbiamo visto che il livello [métier] può generare delle eccezioni. Queste vengono intercettate qui;

11.6. Test visivi

11.6.1. Test n. 1

Con il file dei contribuenti [taxpayersdata.txt] seguente:


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

si ottiene il seguente file degli errori [errors.txt]:


la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée

e il seguente file dei risultati [resultats.txt]:

1
2
3
4
5
6
7
8
9
{"marié":"oui","enfants":2,"salaire":55555,"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":2,"salaire":100000,"impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":3,"salaire":100000,"impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"non","enfants":0,"salaire":100000,"impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":2,"salaire":30000,"impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":0,"salaire":200000,"impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":3,"salaire":200000,"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}

11.6.2. Test n. 2

Nello script principale, si assegna al file dei contribuenti un nome che non esiste:

const TAXPAYERS_DATA_FILENAME = "taxpayersdata2.txt";

I risultati ottenuti nella console sono quindi i seguenti:


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.
  • riga 1: avvisi (warning) dell'interprete PHP;
  • riga 2: messaggio di errore dell’eccezione generata dal livello [dao];

È possibile disattivare i messaggi di errore dell’interprete PHP:

Image

La riga 21 del codice sopra riportato richiede che gli errori PHP non vengano visualizzati. Durante la fase di sviluppo è necessario che vengano visualizzati. In modalità produzione, devono essere nascosti.

I risultati dell’esecuzione sono quindi i seguenti:


Impossible d'ouvrir en lecture les déclarations des contribuables [taxpayersdata2.txt]
Terminé

11.7. Test [Codeception]

I test visivi sono del tutto insufficienti:

  • in genere ci si limita a pochi test;
  • si presta più o meno attenzione durante questa verifica visiva e alcuni dettagli possono sfuggirci;

Nella realtà dello sviluppo professionale, i test vengono redatti da persone specializzate per cui questa è la funzione principale. Esse cercano quindi di effettuare test il più completi possibile. A tal fine utilizzano framework di test.

In questa sede utilizzeremo il framework Codeception [https://codeception.com/] poiché può essere integrato in NetBeans. Si tratta di un framework con un’ampia gamma di possibilità. Ne utilizzeremo solo alcune. L’idea è quella di disporre di un metodo rapido, dopo ogni nuova versione dell’esercizio applicativo, per verificare che funzioni correttamente. L’esistenza di test superati infonde nello sviluppatore fiducia nel codice che ha scritto. Si tratta di un fattore importante.

11.7.1. Installazione del framework [Codeception]

Come molte librerie PHP, il framework [Codeception] si installa insieme a [Composer]. Apriamo quindi un terminale Laragon (vedi paragrafo link).

Per prima cosa dobbiamo installare il framework di test PHPUnit [https://phpunit.de/]. Infatti, Codeception utilizza in background il framework PHPUnit:

Image

Successivamente, installiamo il framework Codeception:

Image

Questo è tutto. Ora vediamo come integrare [Codeception] in NetBeans.

11.7.2. Integrazione di [CodeCeption] in NetBeans

Image

  • in [1-2], si accede alle proprietà del progetto;
  • in [3-4], si imposta [Codeception] come uno dei framework di test del progetto;

Image

Image

  • in [5-8], si inizializza il framework [Codeception] per il progetto;

Image

  • in [9], è stata creata una cartella [tests], oltre a un file di configurazione [codeception.yml] in [10-11]. Il file [11] è identico al file [10]. Codeception ha semplicemente creato una cartella [Important Files] per attribuire un significato particolare al file [10];
  • in [12-13], si torna alle proprietà del progetto;

Image

  • in [14-16], si indica la cartella [tests] [16] come cartella dei test del progetto;
  • in [16], la cartella [tests] appare quindi con il nuovo nome [Test Files]. La presenza di questa cartella in un progetto PHP indica che tale progetto integra un framework di test programmati;
  • creeremo i nostri test nella cartella [unit] [17];

11.7.3. Test del livello [dao]

Image

  • creeremo tutti i nostri test nella cartella [unit] [1];
  • i nomi delle classi di test [Codeception] devono terminare con la parola chiave [Test], altrimenti le classi non saranno riconosciute come classi di test;

Le nostre classi di test [Codeception] avranno la seguente struttura [https://codeception.com/docs/05-UnitTests]:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// caricamento dell'ambiente di test


class DaoTest extends \Codeception\Test\Unit {
  // attributi del test
  private $attribut1;

  public function __construct() {
    parent::__construct();
    // inizializzazione dell'ambiente di test

  }

  // test
  public function testTaxAdminData() {
    // test
    $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);

  }

}

Commenti

  • riga 7: le classi di test si troveranno nello stesso spazio dei nomi dell’applicazione sottoposta a test;
  • righe 9-10: qui si troveranno le operazioni [require] per caricare le classi e le interfacce sottoposte a test;
  • riga 12: il nome della classe di test deve obbligatoriamente terminare con la parola chiave [Test]. Questa classe deve estendere la classe [\Codeception\Test\Unit];
  • righe 16-20: il costruttore ci consentirà di inizializzare l’ambiente di test;
  • riga 23: i nomi dei metodi di test devono obbligatoriamente iniziare con la parola chiave [test];
  • righe 25-31: è possibile utilizzare vari metodi di test;

La classe di test [DaoTest] sarà la seguente:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// costanti
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// inclusione di interfacce e classi
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 -----------------------------------------------------
// definizione delle costanti
const TAXADMINDATA_FILENAME = "taxadmindata.json";

class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;

  public function __construct() {
    parent::__construct();
    // creazione del livello [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // test
  public function testTaxAdminData() {

  }
}

Commenti

Per creare i test di una versione dell’esercizio applicativo, utilizzeremo un ambiente identico a quello utilizzato dallo script principale della versione. Quello della versione 04 è il seguente script [main.php]:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// gestione degli errori tramite PHP
ini_set("display_errors", "0");

// inclusione di interfacce e classi
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 -----------------------------------------------------
// definizione delle costanti
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // creazione del livello [dao]
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // creazione del livello [métier]
  $métier = new Metier($dao);
  // calcolo delle imposte in modalità batch
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // viene visualizzato l'errore
  print $ex->getMessage() . "\n";
}
// fine
print "Terminé\n";
exit;

Per testare il livello [dao], nella classe di test:

  • riprendiamo l’ambiente delle righe 13-27 di [main.php];
  • nel costruttore della classe di test, creiamo il livello [dao] come alla riga 31;
  • scriviamo i metodi di test;

Procederemo in questo modo per tutte le classi di test.

Torniamo al codice completo della classe di test:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// costanti
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// inclusione di interfacce e classi
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 -----------------------------------------------------
// definizione delle costanti
const TAXADMINDATA_FILENAME = "taxadmindata.json";

class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;

  public function __construct() {
    parent::__construct();
    // creazione del livello [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // test
  public function testTaxAdminData() {
    // costanti di calcolo
    $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());
    // scaglioni d'imposta
    $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());
  }

}

Commenti

  • righe 10-25: caricamento dell'ambiente necessario per i test e definizione delle costanti;
  • righe 31-36: creazione del livello [dao], riga 34, seguita dall’inizializzazione dell’attributo [$taxAdminData] della riga 29. Questo attributo contiene i dati dell’amministrazione fiscale;
  • righe 39-55: l’unico metodo di test. Consiste nel verificare che il contenuto dell’attributo [$taxAdminData] corrisponda a quanto previsto;
  • righe 41-50: verifiche delle costanti relative al calcolo dell’imposta;
  • righe 52-55: verifiche delle fasce di imposta. Il metodo [assertSame] verifica che due entità PHP, in questo caso tabelle, siano identiche;

Per eseguire questa classe di test, si procede come segue:

Image

  • in [1-2], si esegue il test;
  • [3]: la finestra dei risultati dei test;
  • [4]: la classe di test eseguita;
  • [5]: i risultati. In questo caso l’unico metodo di test è stato superato;
  • [6]: quando il test fallisce o, più frequentemente, quando non è stato eseguito alcun test, è necessario consultare la finestra [6]. Nella maggior parte dei casi, è il caricamento dell’ambiente di test ad aver fallito e quindi non è stato possibile eseguire alcun test. Gli errori visualizzati in [6] sono quelli che si otterrebbero con l’esecuzione di uno script PHP classico;

Vediamo un esempio di test errato:

Nella classe di test, inseriamo un errore nella definizione di una costante:


// costanti
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04x");

quindi eseguiamo il test. Il risultato ottenuto è il seguente:

Image

Nella finestra [4]:

Image

11.7.4. Test del livello [métier]

La classe di test [MetierTest] segue le stesse regole di costruzione della classe [DaoTest], ma presenta un numero maggiore di metodi di test:


<?php

// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare (strict_types=1);

// spazio dei nomi
namespace Application;

// costanti
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// inclusione di interfacce e classi
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 -----------------------------------------------------
// definizione delle costanti
const TAXADMINDATA_FILENAME = "taxadmindata.json";

class MetierTest extends \Codeception\Test\Unit {
  // livello business
  private $métier;

  public function __construct() {
    parent::__construct();
    // creazione del livello [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    // creazione del livello [métier]
    $this->métier = new Metier($dao);
  }

  // test
  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"]);
  }
}

Commenti

  • righe 10-25: caricamento dei file che definiscono l'ambiente di test. Questo è lo stesso utilizzato per il livello [dao];
  • righe 31-37: istanziazione dei livelli [dao] e [métier];
  • righe 40-47: un test di calcolo delle imposte;
  • riga 41: viene effettuato un determinato calcolo delle imposte con il livello [métier];
  • righe 42-46: si verifica che i risultati ottenuti corrispondano a quelli del simulatore dell’amministrazione fiscale [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm];
  • righe 23-26: i test di uguaglianza vengono effettuati con una tolleranza di 1 euro. Infatti, si è constatato che, a causa di problemi di arrotondamento, l’algoritmo del documento forniva i risultati attesi con una tolleranza di 1 euro;
  • riga 27: l’aliquota fiscale viene calcolata senza margine di errore;
  • righe 49-137: si ripetono questi test 10 volte, ogni volta con una configurazione diversa del contribuente;

I test danno i seguenti risultati:

Image

11.7.5. Test delle prossime versioni

Di seguito, i test dei livelli [dao] e [métier] saranno identici a quelli della versione 04. Cambierà solo l’ambiente di test. Presenteremo quindi solo quest’ultimo e i risultati dei test.