Skip to content

11. Ejercicio de aplicación – versión 4

La aplicación de cálculo de impuestos implementará la siguiente estructura en capas:

Image

Vamos a retomar los elementos de la versión 3 del párrafo «enlace» y modificarlos para adaptarlos a la nueva arquitectura de la aplicación. A esto a veces se le llama «refactorización». Suponemos aquí que los datos necesarios para la aplicación se encuentran en archivos de texto. La capa [Dao] se encargará de las interacciones con estos archivos.

11.1. Estructura de los scripts

Image

11.2. Objetos intercambiados entre capas

Vamos a conservar algunos objetos de la versión 3. Los volvemos a presentar aquí a modo de recordatorio.

La excepción [ExceptionImpots] es la que lanzará la capa [Dao] cuando encuentre un problema, ya sea con el acceso a los datos o con la naturaleza de los mismos (datos incorrectos).


<?php

// espacio de nombres
namespace Application;

class ExceptionImpots extends \RuntimeException {

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

La clase [Utilitaires] reúne métodos útiles para la gestión de archivos de texto (en este caso, un solo método):


<?php

// espacio de nombres
namespace Application;

// una clase de funciones de utilidad
abstract class Utilitaires {

  public static function cutNewLinechar(string $ligne): string {
    // se elimina el marcador de fin de línea de $ligne si existe
    $longueur = strlen($ligne);  // longitud de la línea
    while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
      $ligne = substr($ligne, 0, $longueur - 1);
      $longueur--;
    }
    // fin: se devuelve la línea
    return($ligne);
  }
}

La clase [TaxAdminData] es la clase que encapsula los datos de la administración tributaria:


<?php

namespace Application;

class TaxAdminData {
  // tramos impositivos
  private $limites;
  private $coeffR;
  private $coeffN;
  // constantes para el cálculo del impuesto
  private $plafondQfDemiPart;
  private $plafondRevenusCelibatairePourReduction;
  private $plafondRevenusCouplePourReduction;
  private $valeurReducDemiPart;
  private $plafondDecoteCelibataire;
  private $plafondDecoteCouple;
  private $plafondImpotCouplePourDecote;
  private $plafondImpotCelibatairePourDecote;
  private $abattementDixPourcentMax;
  private $abattementDixPourcentMin;

  // inicialización
  public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
    // se recupera el contenido del archivo de datos fiscales
    $fileContents = \file_get_contents($taxAdminDataFilename);

    // se devuelve el objeto
    return $this;
  }

  private function check($value): \stdClass {

    return $result;
  }

    // toString
  public function __toString() {
    // cadena JSON del objeto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

  // métodos getter y setter
  public function getLimites() {
    return $this->limites;
  }



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


}

Agregamos una nueva clase [TaxPayerData] que encapsula los datos escritos en el archivo de resultados:


<?php

// espacio de nombres
namespace Application;

// la clase de datos
class TaxPayerData {
  // datos necesarios para calcular el impuesto del contribuyente
  private $marié;
  private $enfants;
  private $salaire;
  // resultados del cálculo del impuesto
  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{
    // datos del contribuyente necesarios para el cálculo del impuesto
    $this->marié = $marié;
    $this->enfants = $nbEnfants;
    $this->salaire = $salaireAnnuel;
    // se devuelve el objeto inicializado
    return $this;
  }

  // getters y setters
  public function getMarié() {
    return $this->marié;
  }



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



    // toString
  public function __toString() {
    // cadena JSON del objeto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

}

Nota: utilice la generación automática de código para generar el constructor, los getter y los setter (véase el párrafo del enlace). Tenga en cuenta que los setter son «fluidos».

11.3. La capa [dao]

Aquí nos enfocamos en la capa [1] de nuestra aplicación:

Image

11.3.1. La interfaz [InterfaceDao]

La interfaz de la capa [dao] será la siguiente: [InterfaceDao.php]:


<?php

// espacio de nombres
namespace Application;

interface InterfaceDao {

  // lectura de los datos de los contribuyentes
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // lectura de los datos de la administración tributaria (tramos impositivos)
  public function getTaxAdminData(): TaxAdminData;

  // registro de los resultados
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Comentarios

  • Los requisitos técnicos son los siguientes:
    • los datos de los contribuyentes se encuentran en un archivo de texto;
    • los resultados del cálculo de impuestos se guardan en un archivo de texto;
    • los posibles errores se guardan en un archivo de texto;
    • no se sabe en qué formato están disponibles los datos de la administración tributaria. Para cada nuevo formato, la interfaz [InterfaceDao] deberá implementarse mediante una nueva clase;
    • los métodos de la interfaz que encuentren un error irrecuperable al acceder a los datos deben lanzar una excepción de tipo [ExceptionImpots];
  • línea 9: el método que permite obtener los datos del contribuyente [statut marital, nombre d’enfants, salaire annuel];
    • el primer parámetro es el nombre del archivo de texto en el que se encuentran estos datos;
    • el segundo parámetro es el nombre del archivo de texto en el que se registrarán los posibles errores que surjan;
  • línea 12: el método que permite obtener los datos de la administración tributaria. Aquí no se le pasa ningún parámetro porque no se sabe cómo están almacenados;
  • línea 15: el método que permite guardar los resultados del cálculo del impuesto en un archivo de texto cuyo nombre se pasa como parámetro;

Al escribir la interfaz [InterfaceDao], sabemos que habrá diferentes formas de escribir el método [getTaxAdminData], dependiendo de cómo se almacenen los datos de la administración tributaria. Por lo tanto, la interfaz [InterfaceDao] será implementada por diferentes clases, cada una de las cuales se encargará de un almacenamiento específico de estos datos (tablas, archivos de texto, base de datos, servicio web). No obstante, estas clases derivadas tendrán un código común: el de la implementación de los métodos [getTaxPayersData, saveResults]. Sabemos que este caso de uso puede implementarse de dos maneras (véase el párrafo del enlace):

  1. se crea una clase abstracta C que agrupa el código común a las clases derivadas. La clase C implementa la interfaz I, pero ciertos métodos que deben declararse en las clases derivadas se declaran como abstractos en la clase C y, por lo tanto, la clase C es en sí misma abstracta. Luego se crean las clases C1 y C2, derivadas de C, las cuales implementan cada una a su manera los métodos no definidos (abstractos) de su clase padre C;
  2. se crea un rasgo T casi idéntico a la clase abstracta C de la solución anterior. Este rasgo no implementa la interfaz I porque, sintácticamente, no puede hacerlo. A continuación, se crean las clases C1 y C2, que implementan la interfaz I y utilizan el rasgo T. A estas clases solo les queda implementar los métodos de la interfaz I que no han sido implementados por el rasgo T que importan;

Para el ejemplo, utilizaremos aquí un rasgo [TraitDao].

11.3.2. El rasgo [TraitDao]

El código del rasgo [TraitDao] es el siguiente [TraitDao.php]:


<?php

// espacio de nombres
namespace Application;

trait TraitDao {

  // lectura de datos de contribuyentes
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // tabla de datos de contribuyentes
    $taxPayersData = [];
    // tabla de errores
    $errors = [];
    // pueden ocurrir bastantes errores al manejar archivos
    try {
      // lectura de datos de usuario
      // cada línea tiene el formato: estado civil, número de hijos, salario anual
      $taxPayersFile = fopen($taxPayersFilename, "r");
      if (!$taxPayersFile) {
        throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$taxPayersFilename]", 12);
      }
      // se procesa la línea actual del archivo de datos de usuario
      // que tiene el formato: estado civil, número de hijos, salario anual
      $num = 1;         // N.º de la línea actual
      $nbErreurs = 0;   // número de errores encontrados
      while ($ligne = fgets($taxPayersFile, 100)) {
        // se omiten las líneas vacías
        $ligne = trim($ligne);
        if (strlen($ligne) == 0) {
          // línea siguiente
          $num++;
          // se vuelve al principio del bucle
          continue;
        }
        // se elimina la posible marca de fin de línea
        $ligne = Utilitaires::cutNewLineChar($ligne);
        // se recuperan los 3 campos «casado:hijos:salario» que forman $ligne
        list($marié, $enfants, $salaire) = explode(",", $ligne);
        // se verifican
        // el estado civil debe ser «sí» o «no»
        $marié = trim(strtolower($marié));
        $erreur = ($marié !== "oui" and $marié !== "non");
        if (!$erreur) {
          // el número de hijos debe ser un número entero
          $enfants = trim($enfants);
          if (!preg_match("/^\d+$/", $enfants)) {
            $erreur = TRUE;
          } else {
            $enfants = (int) $enfants;
          }
        }
        if (!$erreur) {
          // el salario es un número entero sin centavos de euro
          $salaire = trim($salaire);
          if (!preg_match("/^\d+$/", $salaire)) {
            $erreur = TRUE;
          } else {
            $salaire = (int) $salaire;
          }
        }
        // ¿Error?
        if ($erreur) {
          $errors[] = "la ligne [$num] du fichier [$taxPayersFilename] est erronée";
          $nbErreurs++;
        } else {
          // se guarda la información
          $taxPayersData[] = (new TaxPayerData())->setFromParameters($marié, $enfants, $salaire);
        }
        // línea siguiente
        $num++;
      }
      // ¿Estamos al final del archivo?
      if (!feof($taxPayersFile)) {
        // se salió del bucle por un error de lectura
        throw new ExceptionImpots("Erreur lors de la lecture de la ligne n° [$num] du fichier [$taxPayersFilename]");
      } else {
        // se salió del bucle al llegar a la marca de fin de archivo
        // se guardan los errores en un archivo de texto
        $this->saveString($errorsFilename, implode("\n", $errors));
        // Resultado de la función
        return $taxPayersData;
      }
    } finally {
      // se cierra el archivo si está abierto
      if ($taxPayersFile) {
        fclose($taxPayersFile);
      }
    }
  }

  // almacenamiento de los resultados
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // Almacenamiento de la tabla [$taxPayersData] en el archivo de texto [$resultsFileName]
    // si el archivo de texto [$resultsFileName] no existe, se crea
    $this->saveString($resultsFilename, implode("\n", $taxPayersData));
  }

  // registro de los resultados de una tabla en un archivo de texto
  private function saveString(string $fileName, string $data): void {
    // Guardar la tabla [$data] en el archivo de texto [$fileName]
    // si el archivo de texto [$fileName] no existe, se crea
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Comentarios

  • línea 6: aquí definimos un rasgo y no una clase;
  • líneas 9-89: el método [getTaxPayersData] implementa el método del mismo nombre de la interfaz [InterfaceDao]. Recupera de un archivo de texto llamado [$taxPayersFilename] los datos de los contribuyentes [statut marital, nombre d’enfants, salaire annuel]. Los presenta en forma de una tabla [$taxPayersData] con elementos de tipo [TaxPayerData] (líneas 67, 81);
  • el método [getTaxPayersData] es muy similar al método [AbstractBaseImpots::executeBatchImpots] descrito en el párrafo anterior, con las siguientes diferencias:
    • el método [getTaxPayersData] solo recupera los datos de los contribuyentes. No realiza ningún cálculo de impuestos. Aquí esa función la cumple la capa [métier];
    • al igual que lo hacía el método [executeBatchImpots], señala los errores. Aquí los errores se almacenan primero en un array [$errors] (línea 13), el cual se guarda en un archivo de texto al final del procesamiento (línea 79). Dependiendo del caso, puede estar vacío o no;
    • en caso de un error irrecuperable, se lanza una excepción de tipo [ExceptionImpots] (líneas 20, 75);
  • línea 73: cabe destacar el procesamiento que se realiza al salir del bucle de las líneas 26 a 71. De hecho, la función [fgets] tiene el inconveniente de devolver el valor booleano FALSE tanto cuando la lectura de las líneas ha llegado al marcador de fin de archivo como cuando dicha lectura no ha podido completarse debido a un error. Para diferenciar ambos casos, se comprueba si se ha llegado al final del archivo con la función [feof]. Si no se ha llegado al final del archivo, significa que se ha producido un error y, por lo tanto, se lanza una excepción;
  • líneas 83-88: se ejecuta [finally] independientemente de si se produjo una excepción o no durante el procesamiento del archivo;
  • línea 85: si se ha abierto el archivo, entonces el «handle» [$taxPayersFile] del archivo tiene el valor booleano TRUE; de lo contrario, FALSE;
  • líneas 99-105: el método privado [saveString] utilizado en la línea 79 para guardar la tabla de errores en un archivo de texto;
  • línea 99: el método [saveString] recibe dos parámetros:
    • [string $filename], que es el nombre del archivo de texto utilizado para guardar los datos;
    • [string $data], que es la cadena de caracteres que se guardará en el archivo de texto. Esta cadena será un conjunto de líneas que terminan con el carácter de fin de línea \n;
  • línea 102: la función PHP [file_puts_contents] guarda una cadena de caracteres en un archivo de texto. Se encarga de abrir el archivo, escribir la cadena en él y cerrar el archivo. Devuelve el valor booleano FALSE si se ha producido un error;
  • línea 103: si ocurre un error, se lanza una excepción;
  • líneas 92-96: implementación del método [saveResults] de la interfaz [InterfaceDao]. Se vuelve a utilizar el método privado [saveString]. Aquí, el segundo parámetro de [saveString] es una cadena construida a partir del array [$taxPayersData], cuyos elementos son de tipo [TaxPayerData]. Cabe preguntarse cuál será el resultado de la operación:

implode("\n", $taxPayersData)

Hemos definido en la clase [TaxPayerData] (párrafo «enlace») el siguiente método [__toString]:


public function __toString() {
    // cadena JSON del objeto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

La operación


implode("\n", $taxPayersData)

concatenará cada elemento de la matriz [$taxPayersData], transformado en una cadena de caracteres mediante su método [__toString], con el carácter de fin de línea \n. Esto dará como resultado una cadena de caracteres con el siguiente formato:

json1\njson2\n…

Conclusión

El rasgo [TraitDao] ha implementado dos de los métodos de la interfaz [InterfaceDao]: [getTaxPayersData] y [saveResults]:


<?php

// espacio de nombres
namespace Application;

interface InterfaceDao {

  // lectura de los datos de los contribuyentes
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // lectura de los datos de la administración tributaria (tramos impositivos)
  public function getTaxAdminData(): TaxAdminData;

  // registro de los resultados
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Nos queda por implementar el método [getTaxAdminData], que recupera los datos de la administración tributaria.

11.3.3. La clase [ImpotsWithTaxAdminDataInJsonFile]

La clase [ImpotsWithTaxAdminDataInJsonFile] implementa la interfaz [InterfaceDao] de la siguiente manera:


<?php

// espacio de nombres
namespace Application;

// definición de una clase ImpotsWithDataInFile
class DaoImpotsWithTaxAdminDataInJsonFile implements InterfaceDao {
  // uso de una característica
  use TraitDao;
  // el objeto de tipo TaxAdminData que contiene los datos de los tramos impositivos
  private $taxAdminData;

  // el constructor
  public function __construct(string $taxAdminDataFilename) {
    // se desea inicializar el atributo [$this->taxAdminData]
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // devuelve los datos que permiten calcular el impuesto
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }
}

Comentarios

  • línea 7: la clase [ImpotsWithTaxAdminDataInJsonFile] implementa la interfaz [InterfaceDao];
  • línea 9: la clase [ImpotsWithTaxAdminDataInJsonFile] utiliza el rasgo [traitDao], que, como sabemos, implementa los métodos [getTaxPayersData] y [saveResults] de lainterfaz [InterfaceDao]. Por lo tanto, a la clase [ImpotsWithTaxAdminDataInJsonFile] solo le queda implementar el método [getTaxAdminData], que recupera los datos de la administración tributaria;
  • línea 11: el atributo de tipo [TaxAdminData] que devuelve el método [getTaxAdminData] de las líneas 20-22. Este atributo es inicializado por el constructor de las líneas 14-17;

Hemos terminado con la capa [dao] de nuestra aplicación: contamos con una clase que implementa por completo la interfaz [InterfaceDao] que nos hemos impuesto. Ahora podemos pasar a la capa [métier].

11.4. La capa [métier]

Ahora vamos a implementar la capa [2] de nuestra arquitectura:

Image

11.4.1. La interfaz [InterfaceMétier]

La interfaz de la capa [métier] será la siguiente:


<?php

// espacio de nombres
namespace Application;

interface InterfaceMetier {

  // cálculo de los impuestos de un contribuyente
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // cálculo de impuestos en modo por lotes
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}

Comentarios

  • línea 9: la interfaz [InterfaceMétier] puede calcular el monto del impuesto de un contribuyente particular siempre que se le proporcione la siguiente información: estado civil, número de hijos y salario anual. El método [calculerImpot] no utiliza la capa [dao], por lo que no genera excepciones;
  • línea 9: la interfaz [InterfaceMétier] también puede calcular el monto del impuesto de un grupo de contribuyentes cuyos datos se encuentran reunidos en el archivo de texto denominado [$taxPayersFileName]. Guarda los resultados en un archivo de texto denominado [$resultsFileName]. El método [executeBatchImpots] debe comunicarse con la capa [dao], que se encarga de los accesos al sistema de archivos. En ese caso, pueden surgir excepciones desde la capa [dao] que el método [executeBatchImpots] no interceptará: las dejará pasar al script principal. Los errores no fatales se registran en el archivo de texto llamado [$errorsFileName];
  • línea 9: el método [calculerImpot] es un método puramente [métier]. No se preocupa por el origen de los datos que utiliza;
  • línea 12: el método [executeBatchImpots] se comunicará con la capa [dao] para leer y escribir datos en archivos de texto. Llamará repetidamente al método de negocio [calculerImpot];

11.4.2. La clase [Metier]

La clase [Metier] implementa la interfaz [InterfaceMetier] de la siguiente manera:


<?php

// espacio de nombres
namespace Application;

class Metier implements InterfaceMetier {
  // capa DAO
  private $dao;
  // datos de la administración tributaria
  private $taxAdminData;

  //---------------------------------------------
  // configurador de la capa [dao]
  public function setDao(InterfaceDao $dao) {
    $this->dao = $dao;
    return $this;
  }

  public function __construct(InterfaceDao $dao) {
    // se almacena una referencia en la capa [dao]
    $this->dao = $dao;
    // se recuperan los datos necesarios para calcular el impuesto
    // el método [getTaxAdminData] puede generar una excepción ExceptionImpots
    // luego se permite que se transmita al código que la invocó
    $this->taxAdminData = $this->dao->getTaxAdminData();
  }

// cálculo del impuesto
// --------------------------------------------------------------------------
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {

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

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

  // revenuImposable=salarioAnual-deducción
  // la deducción tiene un mínimo y un máximo
  private function getRevenuImposable(float $salaire): float {

    // resultado
    return floor($revenuImposable);
  }

// calcula una posible reducción
  private function getDecôte(string $marié, float $salaire, float $impots): float {

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

// calcula una posible reducción
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

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

  // cálculo de impuestos en modo por lotes
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {

    // registro de los resultados
    $this->dao->saveResults($resultsFileName, $results);
  }

}

Comentarios

  • línea 6: la clase [Metier] implementa la interfaz [InterfaceMetier], es decir, los métodos [calculerImpot] (líneas 30-34) y [executeBatchImpots] (líneas 66-70);
  • línea 8: una referencia a la capa [dao]. Es obligatorio contar con una para que la capa [métier] sepa a quién dirigirse cuando necesite datos externos. Este atributo se inicializará mediante el setter de las líneas 14-17 o mediante el constructor de las líneas 19-26;
  • línea 10: el objeto de tipo [TaxAdminData] que encapsula los datos de la administración tributaria. Estos datos son necesarios para el método de negocio [calculerImpot]. Este atributo se inicializa mediante el constructor de las líneas 19 a 26;
  • líneas 19-26: el constructor inicializa los dos atributos de la clase:
    • el atributo [$dao] se inicializa con la referencia pasada como parámetro al constructor. Cabe señalar que el tipo de este parámetro es el de la interfaz [InterfaceDao], lo que permite que la clase [Metier] sea inicializada por cualquier clase que implemente dicha interfaz;
    • el atributo [$taxAdminData] se inicializa llamando al método [getTaxAdminData] de la capa [dao];

De ello se deduce que, cuando se ejecutan los métodos [calculerImpots] y [executeBatchImpots], se inicializan los dos atributos [$dao] y [$taxAdminData].

El método [calculerImpots] es el siguiente:


public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    // $marié: sí, no
    // $enfants: número de hijos
    // $salaire: salario anual
    // $this->taxAdminData: datos de la administración tributaria
    //
    // se verifica que se cuente con los datos de la administración tributaria
    if ($this->taxAdminData === NULL) {
      $this->taxAdminData = $this->getTaxAdminData();
    }
    // cálculo del impuesto con hijos
    $result1 = $this->calculerImpot2($marié, $enfants, $salaire);
    $impot1 = $result1["impôt"];
    // cálculo del impuesto sin hijos
    if ($enfants != 0) {
      $result2 = $this->calculerImpot2($marié, 0, $salaire);
      $impot2 = $result2["impôt"];
      // aplicación del límite máximo del coeficiente familiar
      $plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
      if ($enfants < 3) {
        // $PLAFOND_QF_DEMI_PART euros para los dos primeros hijos
        $impot2 = $impot2 - $enfants * $plafonDemiPart;
      } else {
        // $PLAFOND_QF_DEMI_PART euros para los dos primeros hijos, el doble para los siguientes
        $impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
      }
    } else {
      $impot2 = $impot1;
      $result2 = $result1;
    }
    // se toma el impuesto más alto
    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"];
    }
    // cálculo de una posible deducción
    $décôte = $this->getDecôte($marié, $salaire, $impot);
    $impot -= $décôte;
    // cálculo de una posible reducción de impuestos
    $réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
    $impot -= $réduction;
    // resultado
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }

Comentarios

  • Este código corresponde al método [AbstractBaseImpots::calculerImpot] de la versión 3, explicado en el párrafo del enlace. Lo mismo ocurre con los métodos privados [calculerImpot2, getDecôte, getRéduction, getRevenuImposable];

El método [Metier::executeBatchImpots] es el siguiente:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // se permiten las excepciones que provienen de la capa [dao]
    // se recuperan los datos de los contribuyentes
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tabla de resultados
    $results = [];
    // se analizan
    foreach ($taxPayersData as $taxPayerData) {
      // se calcula el impuesto
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // se 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"]);
      // se ingresa el resultado en la tabla de resultados
      $results [] = $taxPayerData;
    }
    // almacenamiento de los resultados
    $this->dao->saveResults($resultsFileName, $results);
  }

Comentarios

  • línea 1: el método debe llamar repetidamente al método [calculerImpot] para cada uno de los contribuyentes encontrados en el archivo de texto denominado [$taxPayersFileName]. Debe guardar los resultados en el archivo de texto denominado [$resultsFileName]. Los errores no fatales que se presenten se registran en el archivo de texto denominado [$errorsFileName]. El método no lanza excepciones por sí mismo, sino que permite que se propaguen aquellas que lanza la capa [dao];
  • línea 4: se solicitan los datos de los contribuyentes a la capa [dao]. Esta devuelve un array de elementos de tipo [TaxPayerData], que es una clase de atributos [marié, nbEnfants, salaire, montant, décôte, réduction, surcôte, taux] (véase el párrafo «enlace»). Si se produce una excepción aquí, dado que no es interceptada por un catch, se propagará automáticamente al código que la invocó. Esto significa que, en caso de excepción, la línea 6 no se ejecuta;
  • línea 6: la tabla de resultados de tipo [TaxPayerData];
  • líneas 8-22: se calcula el impuesto para cada uno de los elementos de la tabla de contribuyentes [$taxPayersData]. Para ello, se invoca el método interno [calculerImpot] (línea 10);
  • líneas 15-19: el resultado obtenido se utiliza para inicializar los atributos de [TaxPayerData] que aún no lo estaban;
  • línea 21: el resultado obtenido se acumula en la tabla de resultados [$results];
  • línea 24: una vez calculado el impuesto para todos los contribuyentes, los resultados se guardan en un archivo de texto. La capa [dao] se encarga de esta tarea;

Conclusión

En general, la capa [métier] es bastante sencilla de escribir, ya que se comunica con la capa [dao], la cual se encarga de gestionar el acceso a los datos y la gestión de errores correspondiente.

11.5. El script principal

Ahora escribimos el script de la capa [3] de nuestra arquitectura:

Image

El script principal es el siguiente: [main.php]:


<?php

// Se respetan estrictamente los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// gestión de errores mediante PHP
//ini_set("display_errors", "0");

// Inclusión de interfaz y clases
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";
// prueba -----------------------------------------------------
// definición de constantes
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // creación de la capa [dao]
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // creación de la capa [métier]
  $métier = new Metier($dao);
  // cálculo de impuestos en modo por lotes
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // se muestra el error
  print $ex->getMessage() . "\n";
}
// fin
print "Terminé\n";
exit;


Comentarios

  • línea 24: el nombre del archivo de datos de los contribuyentes;
  • línea 25: el nombre del archivo de resultados;
  • línea 26: el nombre del archivo de errores;
  • línea 27: el nombre del archivo jSON que contiene los datos de la administración tributaria;
  • línea 31: creación de la capa [dao];
  • línea 33: creación de la capa [métier] basada en esta capa [dao];
  • línea 35: ejecución del método [executeBatchImpots] de la capa [métier];
  • líneas 36-39: ya vimos que la capa [métier] podía generar excepciones. Estas se interceptan aquí;

11.6. Pruebas visuales

11.6.1. Prueba n.º 1

Con el archivo de contribuyentes [taxpayersdata.txt] a continuación:


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

se obtiene el siguiente archivo de errores [errors.txt]:


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

y el siguiente archivo de resultados [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. Prueba n.º 2

En el script principal, se le asigna al archivo de contribuyentes un nombre que no existe:

const TAXPAYERS_DATA_FILENAME = "taxpayersdata2.txt";

Los resultados obtenidos en la consola son los siguientes:


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.
  • línea 1: advertencias (warning) del intérprete PHP;
  • línea 2: el mensaje de error de la excepción lanzada por la capa [dao];

Es posible silenciar los mensajes de error del intérprete PHP:

Image

La línea 21 del código anterior indica que no se muestren los errores PHP. Durante la fase de desarrollo es necesario que se muestren. En modo de producción, deben ocultarse.

Los resultados de la ejecución son entonces los siguientes:


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

11.7. Pruebas [Codeception]

Las pruebas visuales son muy insuficientes:

  • por lo general, nos limitamos a unas pocas pruebas;
  • se presta más o menos atención durante esta verificación visual y pueden pasarnos por alto algunos detalles;

En la práctica del desarrollo profesional, las pruebas las redactan personas dedicadas a ello, para quienes esta es su función principal. Por lo tanto, buscan realizar pruebas lo más completas posible. Para ello, utilizan marcos de pruebas.

Aquí utilizaremos el marco de trabajo Codeception [https://codeception.com/], ya que se puede integrar en NetBeans. Es un marco de trabajo con una amplia gama de posibilidades. Solo utilizaremos algunas de ellas. La idea es contar con una forma rápida, después de cada nueva versión del ejercicio de la aplicación, de verificar que esta funcione. La existencia de pruebas exitosas le da al desarrollador confianza en el código que ha escrito. Este es un factor importante.

11.7.1. Instalación del marco de trabajo [Codeception]

Al igual que muchas bibliotecas PHP, el marco [Codeception] se instala junto con [Composer]. Por lo tanto, abrimos un terminal de Laragon (véase el párrafo del enlace).

En primer lugar, debemos instalar el marco de pruebas PHPUnit [https://phpunit.de/]. De hecho, Codeception utiliza en segundo plano el marco PHPUnit:

Image

A continuación, instalamos el marco de trabajo Codeception:

Image

Eso es todo. Ahora veamos cómo integrar [Codeception] en NetBeans.

11.7.2. Integración de [CodeCeption] en NetBeans

Image

  • En [1-2], accedemos a las propiedades del proyecto;
  • en [3-4], se establece [Codeception] como uno de los marcos de prueba del proyecto;

Image

Image

  • En [5-8], se inicializa el marco [Codeception] para el proyecto;

Image

  • En [9], se creó una carpeta [tests], así como un archivo de configuración [codeception.yml] en [10-11]. El archivo [11] es igual al archivo [10]. Codeception simplemente creó una carpeta [Important Files] para darle un significado especial al archivo [10];
  • en [12-13], volvemos a las propiedades del proyecto;

Image

  • en [14-16], se designa la carpeta [tests] [16] como la carpeta de pruebas del proyecto;
  • en [16], la carpeta [tests] aparece entonces con el nuevo nombre [Test Files]. La presencia de esta carpeta en un proyecto PHP indica que dicho proyecto incluye un marco de pruebas programadas;
  • crearemos nuestras pruebas en la carpeta [unit] [17];

11.7.3. Pruebas de la capa [dao]

Image

  • vamos a crear todas nuestras pruebas en la carpeta [unit] [1];
  • Los nombres de las clases de prueba [Codeception] deben terminar con la palabra clave [Test]; de lo contrario, las clases no se reconocerán como clases de prueba;

Nuestras clases de prueba [Codeception] tendrán el siguiente formato: [https://codeception.com/docs/05-UnitTests]:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// carga del entorno de prueba


class DaoTest extends \Codeception\Test\Unit {
  // atributos de la prueba
  private $attribut1;

  public function __construct() {
    parent::__construct();
    // inicialización del entorno de prueba

  }

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

  }

}

Comentarios

  • línea 7: las clases de prueba estarán en el mismo espacio de nombres que la aplicación que se está probando;
  • líneas 9-10: aquí se encuentran las operaciones [require] para cargar las clases e interfaces a probar;
  • línea 12: el nombre de la clase de prueba debe terminar obligatoriamente con la palabra clave [Test]. Esta clase debe extender la clase [\Codeception\Test\Unit];
  • líneas 16-20: el constructor nos permitirá inicializar el entorno de prueba;
  • línea 23: los nombres de los métodos de prueba deben comenzar obligatoriamente con la palabra clave [test];
  • líneas 25-31: se pueden utilizar diversos métodos de prueba;

La clase de prueba [DaoTest] será la siguiente:


<?php

// cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// constantes
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// Inclusión de interfaces y clases
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";;
// prueba -----------------------------------------------------
// definición de constantes
const TAXADMINDATA_FILENAME = "taxadmindata.json";

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

  public function __construct() {
    parent::__construct();
    // creación de la capa [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // pruebas
  public function testTaxAdminData() {

  }
}

Comentarios

Para crear las pruebas de una versión del ejercicio de aplicación, utilizaremos un entorno idéntico al que utiliza el script principal de la versión. El de la versión 04 es el siguiente script [main.php]:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// gestión de errores mediante PHP
ini_set("display_errors", "0");

// Inclusión de interfaces y clases
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";
// prueba -----------------------------------------------------
// definición de constantes
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // creación de la capa [dao]
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // creación de la capa [métier]
  $métier = new Metier($dao);
  // cálculo de impuestos en modo por lotes
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // se muestra el error
  print $ex->getMessage() . "\n";
}
// fin
print "Terminé\n";
exit;

Para probar la capa [dao], en la clase de prueba:

  • tomamos el entorno de las líneas 13 a 27 de [main.php];
  • en el constructor de la clase de prueba, creamos la capa [dao] como en la línea 31;
  • escribimos los métodos de prueba;

Procederemos de esta manera para todas las clases de prueba.

Volvamos al código completo de la clase de prueba:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// constantes
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// Inclusión de interfaces y clases
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";;
// prueba -----------------------------------------------------
// definición de constantes
const TAXADMINDATA_FILENAME = "taxadmindata.json";

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

  public function __construct() {
    parent::__construct();
    // creación de la capa [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // pruebas
  public function testTaxAdminData() {
    // constantes de cálculo
    $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());
    // tramos impositivos
    $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());
  }

}

Comentarios

  • líneas 10-25: carga del entorno necesario para las pruebas y definiciones de constantes;
  • líneas 31-36: construcción de la capa [dao], línea 34, y luego inicialización del atributo [$taxAdminData] de la línea 29. Este atributo contiene los datos de la administración tributaria;
  • líneas 39-55: el único método de prueba. Este consiste en verificar que el contenido del atributo [$taxAdminData] coincida con lo esperado;
  • líneas 41-50: verificaciones de las constantes del cálculo del impuesto;
  • líneas 52-55: verificaciones de los tramos impositivos. El método [assertSame] verifica que dos entidades PHP, en este caso tablas, sean idénticas;

Para ejecutar esta clase de prueba, se procede de la siguiente manera:

Image

  • en [1-2], se ejecuta la prueba;
  • [3]: la ventana de resultados de las pruebas;
  • [4]: la clase de prueba ejecutada;
  • [5]: los resultados. Aquí, el único método de prueba se ha ejecutado con éxito;
  • [6]: cuando la prueba falla o, más frecuentemente, cuando no se ha ejecutado ninguna prueba, hay que consultar la ventana [6]. En la mayoría de los casos, es la carga del entorno de prueba la que ha fallado y, por lo tanto, no se ha podido ejecutar ninguna prueba. Los errores que aparecen en [6] son los mismos que se presentarían al ejecutar un script PHP convencional;

Veamos un ejemplo de prueba con error:

En la clase de prueba, introducimos un error en la definición de una constante:


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

y luego ejecutamos la prueba. El resultado obtenido es el siguiente:

Image

En la ventana [4]:

Image

11.7.4. Pruebas de la capa [métier]

La clase de prueba [MetierTest] sigue las mismas reglas de construcción que la clase [DaoTest], pero cuenta con más métodos de prueba:


<?php

// cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// constantes
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// inclusión de interfaces y clases
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";;
// prueba -----------------------------------------------------
// definición de constantes
const TAXADMINDATA_FILENAME = "taxadmindata.json";

class MetierTest extends \Codeception\Test\Unit {
  // capa de negocio
  private $métier;

  public function __construct() {
    parent::__construct();
    // creación de la capa [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    // creación de la capa [métier]
    $this->métier = new Metier($dao);
  }

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

Comentarios

  • líneas 10-25: carga de los archivos que definen el entorno de prueba. Este es el mismo que para la capa [dao];
  • líneas 31-37: instanciación de las capas [dao] y [métier];
  • líneas 40-47: una prueba de cálculo de impuestos;
  • línea 41: se realiza un cálculo de impuestos con la capa [métier];
  • líneas 42-46: se verifica que los resultados obtenidos coincidan con los del simulador de la administración tributaria [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm];
  • líneas 23-26: las pruebas de igualdad se realizan con una precisión de 1 euro. De hecho, se observó que, debido a problemas de redondeo, el algoritmo del documento arrojaba los resultados esperados con una precisión de 1 euro;
  • línea 27: el tipo impositivo se calcula sin margen de error;
  • líneas 49-137: se repiten este tipo de pruebas 10 veces, cada una con una configuración diferente del contribuyente;

Las pruebas arrojan los siguientes resultados:

Image

11.7.5. Pruebas de las próximas versiones

En lo sucesivo, las pruebas de las capas [dao] y [métier] serán idénticas a las de la versión 04. Solo cambiará el entorno de la prueba. Por lo tanto, solo presentaremos este y los resultados de las pruebas.