Skip to content

11. Uygulama alıştırması – sürüm 4

Vergi hesaplama uygulaması aşağıdaki katman yapısını uygulayacaktır:

Image

"Bağlantı" başlıklı paragrafın 3. sürümündeki öğeleri, uygulamanın yeni mimarisine uyacak şekilde değiştirerek kullanacağız. Buna bazen "refactoring" denir. Burada, uygulama için gerekli verilerin metin dosyalarında olduğunu varsayıyoruz. Bu dosyalarla veri alışverişini [Dao] katmanı üstlenecektir.

11.1. Komut dosyalarının dizin yapısı

Image

11.2. Katmanlar arasında aktarılan nesneler

Sürüm 3’teki bazı nesneleri koruyacağız. Hatırlatma amacıyla bunları burada tekrar veriyoruz.

[ExceptionImpots] istisnası, [Dao] katmanı, verilere erişimde veya verilerin niteliğinde (hatalı veriler) bir sorunla karşılaştığında tetikleyeceği istisnadır.


<?php

// ad alanı
namespace Application;

class ExceptionImpots extends \RuntimeException {

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

[Utilitaires] sınıfı, metin dosyalarının yönetimi için yararlı yöntemleri (burada tek bir yöntem) bir araya getirir:


<?php

// ad alanı
namespace Application;

// bir yardımcı işlev sınıfı
abstract class Utilitaires {

  public static function cutNewLinechar(string $ligne): string {
    // varsa, $ligne'teki satır sonu işaretini siler
    $longueur = strlen($ligne);  // satır uzunluğu
    while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
      $ligne = substr($ligne, 0, $longueur - 1);
      $longueur--;
    }
    // son - satırı döndürür
    return($ligne);
  }
}

[TaxAdminData] sınıfı, vergi idaresi verilerini kapsayan sınıftır:


<?php

namespace Application;

class TaxAdminData {
  // vergi dilimleri
  private $limites;
  private $coeffR;
  private $coeffN;
  // vergi hesaplama sabitleri
  private $plafondQfDemiPart;
  private $plafondRevenusCelibatairePourReduction;
  private $plafondRevenusCouplePourReduction;
  private $valeurReducDemiPart;
  private $plafondDecoteCelibataire;
  private $plafondDecoteCouple;
  private $plafondImpotCouplePourDecote;
  private $plafondImpotCelibatairePourDecote;
  private $abattementDixPourcentMax;
  private $abattementDixPourcentMin;

  // başlatma
  public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
    // vergi verileri dosyasının içeriği alınır
    $fileContents = \file_get_contents($taxAdminDataFilename);

    // nesneyi döndürür
    return $this;
  }

  private function check($value): \stdClass {

    return $result;
  }

    // toString
  public function __toString() {
    // nesnenin JSON dizesi
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

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



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


}

Sonuç dosyasına yazılan verileri kapsayan yeni bir [TaxPayerData] sınıfı ekliyoruz:


<?php

// ad alanı
namespace Application;

// veri sınıfı
class TaxPayerData {
  // vergi mükellefinin vergisinin hesaplanması için gerekli veriler
  private $marié;
  private $enfants;
  private $salaire;
  // vergi hesaplama sonuçları
  private $montant;
  private $surcôte;
  private $décôte;
  private $réduction;
  private $taux;

  // ayarlayıcı
  public function setFromParameters(string $marié, int $nbEnfants, int $salaireAnnuel) : TaxPayerData{
    // vergi hesaplaması için gerekli vergi mükellefi verileri
    $this->marié = $marié;
    $this->enfants = $nbEnfants;
    $this->salaire = $salaireAnnuel;
    // nesneyi başlatır
    return $this;
  }

  // alıcı ve ayarlayıcılar
  public function getMarié() {
    return $this->marié;
  }



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



    // toString
  public function __toString() {
    // nesnenin JSON dizesi
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

}

Not: Oluşturucu, getter ve setter'ları oluşturmak için otomatik kod üretme özelliğini kullanın (bkz. bağlantı paragrafı). Setter'ların ‘akıcı’ olduğunu unutmayın.

11.3. [dao] katmanı

Burada uygulamamızın [1] katmanıyla ilgileniyoruz:

Image

11.3.1. [InterfaceDao] arayüzü

[dao] katmanının arayüzü şu şekilde olacaktır: [InterfaceDao.php]:


<?php

// ad alanı
namespace Application;

interface InterfaceDao {

  // vergi mükellefi verilerinin okunması
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // vergi idaresi verilerinin okunması (vergi dilimleri)
  public function getTaxAdminData(): TaxAdminData;

  // sonuçların kaydedilmesi
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Yorumlar

  • Şartname şu şekildedir:
    • Vergi mükelleflerinin verileri bir metin dosyasında bulunur;
    • vergi hesaplama sonuçları bir metin dosyasına kaydedilir;
    • olası hatalar bir metin dosyasına kaydedilir;
    • Vergi idaresinin verilerinin hangi formatta mevcut olduğu bilinmemektedir. Her yeni format için, [InterfaceDao] arayüzü yeni bir sınıf tarafından uygulanmalıdır;
    • verilere erişim sırasında düzeltilemez bir hatayla karşılaşan arayüz yöntemleri, [ExceptionImpots] türünde bir istisna atmalıdır;
  • 9. satır: vergi mükellefinin verilerini almayı sağlayan yöntem [statut marital, nombre d’enfants, salaire annuel];
    • 1. parametre, bu verilerin bulunduğu metin dosyasının adıdır;
    • ikinci parametre, karşılaşılan olası hataların kaydedileceği metin dosyasının adıdır;
  • 12. satır: Vergi dairesinden verileri almayı sağlayan yöntemdir. Verilerin nasıl depolandığı bilinmediğinden, buraya herhangi bir parametre aktarılmaz;
  • 15. satır: vergi hesaplama sonuçlarını, adı parametre olarak geçirilen bir metin dosyasına kaydetmeyi sağlayan yöntem;

[InterfaceDao] arayüzünü yazarken, vergi idaresi verilerinin nasıl depolanacağına bağlı olarak [getTaxAdminData] yöntemini yazmanın farklı yolları olacağını biliyoruz. Dolayısıyla, [InterfaceDao] arayüzü, her biri bu verilerin belirli bir depolama biçimini (tablolar, metin dosyaları, veritabanı, web hizmeti) işleyen farklı sınıflar tarafından uygulanacaktır. Bununla birlikte, bu türetilmiş sınıflar, [getTaxPayersData, saveResults] yöntemlerinin uygulanmasına ait ortak bir koda sahip olacaktır. Bu kullanım senaryosunun iki şekilde uygulanabileceği bilinmektedir (bkz. bağlantı paragrafı):

  1. Türetilmiş sınıflara ortak olan kodu bir araya getiren C adlı soyut bir sınıf oluşturulur. C sınıfı, I arayüzünü uygular; ancak türetilmiş sınıflarda tanımlanması gereken bazı yöntemler, C sınıfında soyut olarak tanımlandığından, C sınıfı da kendiliğinden soyut bir sınıf haline gelir. Ardından, C sınıfından türetilen C1 ve C2 sınıfları oluşturulur; bu sınıfların her biri, üst sınıfları olan C'nin tanımlanmamış (soyut) yöntemlerini kendi yöntemleriyle uygular;
  2. önceki çözümdeki C soyut sınıfıyla neredeyse aynı olan T özelliği oluşturulur. Bu özellik, sözdizimsel olarak bunu yapamadığı için I arayüzünü uygulamaz. Ardından, I arayüzünü uygulayan ve T trait'ini kullanan C1 ve C2 sınıfları oluşturulur. Bu sınıfların yapması gereken tek şey, içe aktardıkları T trait'i tarafından uygulanmayan I arayüzünün yöntemlerini uygulamaktır;

Örnek olarak burada [TraitDao] özelliğini kullanacağız.

11.3.2. [TraitDao] özelliği

[TraitDao] trait'inin kodu şu şekildedir: [TraitDao.php]:


<?php

// ad alanı
namespace Application;

trait TraitDao {

  // vergi mükellefi verilerinin okunması
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // vergi mükellefi verileri tablosu
    $taxPayersData = [];
    // hata tablosu
    $errors = [];
    // dosyaları işlerken pek çok hata meydana gelebilir
    try {
      // kullanıcı verilerinin okunması
      // her satır medeni durum, çocuk sayısı, yıllık maaş şeklinde düzenlenmiştir
      $taxPayersFile = fopen($taxPayersFilename, "r");
      if (!$taxPayersFile) {
        throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$taxPayersFilename]", 12);
      }
      // kullanıcı verileri dosyasının geçerli satırı işlenir
      // bu satır, medeni durum, çocuk sayısı, yıllık maaş biçimindedir
      $num = 1;         // geçerli satır numarası
      $nbErreurs = 0;   // karşılaşılan hata sayısı
      while ($ligne = fgets($taxPayersFile, 100)) {
        // boş satırlar göz ardı edilir
        $ligne = trim($ligne);
        if (strlen($ligne) == 0) {
          // sonraki satır
          $num++;
          // döngüye geri dönülür
          continue;
        }
        // varsa satır sonu işaretini kaldır
        $ligne = Utilitaires::cutNewLineChar($ligne);
        // $ligne'i oluşturan "evli:çocuk sayısı:maaş" şeklindeki 3 alanı alınır
        list($marié, $enfants, $salaire) = explode(",", $ligne);
        // bunlar kontrol ediliyor
        // medeni durum "evli" veya "evli değil" olmalıdır
        $marié = trim(strtolower($marié));
        $erreur = ($marié !== "oui" and $marié !== "non");
        if (!$erreur) {
          // Çocuk sayısı bir tamsayı olmalıdır
          $enfants = trim($enfants);
          if (!preg_match("/^\d+$/", $enfants)) {
            $erreur = TRUE;
          } else {
            $enfants = (int) $enfants;
          }
        }
        if (!$erreur) {
          // maaş, euro sentleri hariç bir tam sayı olmalıdır
          $salaire = trim($salaire);
          if (!preg_match("/^\d+$/", $salaire)) {
            $erreur = TRUE;
          } else {
            $salaire = (int) $salaire;
          }
        }
        // hata mı?
        if ($erreur) {
          $errors[] = "la ligne [$num] du fichier [$taxPayersFilename] est erronée";
          $nbErreurs++;
        } else {
          // bilgiler kaydediliyor
          $taxPayersData[] = (new TaxPayerData())->setFromParameters($marié, $enfants, $salaire);
        }
        // sonraki satır
        $num++;
      }
      // dosyanın sonuna geldik mi?
      if (!feof($taxPayersFile)) {
        // okuma hatası nedeniyle döngüden çıkıldı
        throw new ExceptionImpots("Erreur lors de la lecture de la ligne n° [$num] du fichier [$taxPayersFilename]");
      } else {
        // dosya sonu işaretinde döngüden çıkıldı
        // hatalar bir metin dosyasına kaydediliyor
        $this->saveString($errorsFilename, implode("\n", $errors));
        // fonksiyonun sonucu
        return $taxPayersData;
      }
    } finally {
      // dosya açıksa kapatılıyor
      if ($taxPayersFile) {
        fclose($taxPayersFile);
      }
    }
  }

  // sonuçların kaydedilmesi
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // [$taxPayersData] tablosunun [$resultsFileName] metin dosyasına kaydedilmesi
    // [$resultsFileName] metin dosyası yoksa, oluşturulur
    $this->saveString($resultsFilename, implode("\n", $taxPayersData));
  }

  // bir tablodaki sonuçların bir metin dosyasına kaydedilmesi
  private function saveString(string $fileName, string $data): void {
    // [$data] tablosunun [$fileName] metin dosyasına kaydedilmesi
    // [$fileName] metin dosyası yoksa oluşturulur
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Açıklamalar

  • 6. satır: Burada bir sınıf değil, bir özellik tanımlıyoruz;
  • 9-89. satırlar: [getTaxPayersData] yöntemi, [InterfaceDao] arayüzündeki aynı adlı yöntemi uygular. Bu yöntem, [$taxPayersFilename] adlı bir metin dosyasından [statut marital, nombre d’enfants, salaire annuel] vergi mükelleflerinin verilerini alır. Bu verileri, [TaxPayerData] türündeki öğelerden oluşan bir [$taxPayersData] tablosu biçiminde döndürür (satır 67, 81);
  • [getTaxPayersData] yöntemi, “bağlantı” paragrafında açıklanan [AbstractBaseImpots::executeBatchImpots] yöntemine çok benzemektedir, ancak aşağıdaki farklılıklar vardır:
    • [getTaxPayersData] yöntemi yalnızca vergi mükelleflerinin verilerini alır. Vergi hesaplaması yapmaz. Burada bu görev [métier] katmanına aittir;
    • [executeBatchImpots] yöntemi gibi hataları bildirir. Burada hatalar önce [$errors] dizisine kaydedilir (13. satır); bu dizi, işlemin sonunda bir metin dosyasına kaydedilir (79. satır). Duruma göre bu dizi boş olabilir veya olmayabilir;
    • düzeltilemez bir hata durumunda, [ExceptionImpots] türünde bir istisna atılır (satır 20, 75);
  • 73. satır: 26-71. satırlardaki döngüden çıkıldığında yapılan işleme dikkat edilmelidir. Aslında [fgets] işlevinin bir dezavantajı, satır okuma işlemi dosya sonu işaretine ulaştığında da, bir hata nedeniyle okuma işlemi tamamlanamadığında da FALSE boole değerini döndürmesidir. Bu iki durumu birbirinden ayırmak için, [feof] fonksiyonu kullanılarak dosyanın sonuna gelinip gelinmediği test edilir. Dosyanın sonuna gelinmemişse, bir hata meydana gelmiş demektir ve bu durumda bir istisna tetiklenir;
  • 83-88. satırlar: Dosyanın işlenmesi sırasında istisna oluşmuş olsun ya da olmasın, [finally] işlevi yürütülür;
  • 85. satır: Dosya açılmışsa, dosyanın [$taxPayersFile] "kullanıcı tanıtıcısı" TRUE boole değerine sahiptir; aksi takdirde FALSE değerine sahiptir;
  • 99-105. satırlar: 79. satırda hata tablosunu bir metin dosyasına kaydetmek için kullanılan özel yöntem [saveString];
  • 99. satır: [saveString] yöntemi iki parametre alır:
    • [string $filename], verilerin kaydedilmesi için kullanılan metin dosyasının adıdır;
    • [string $data], metin dosyasına kaydedilecek karakter dizisidir. Bu dizgi, satır sonu karakteri \n ile biten bir dizi satırdan oluşur;
  • 102. satır: PHP [file_puts_contents] işlevi, bir karakter dizisini bir metin dosyasına kaydeder. Bu işlev, dosyayı açar, içine karakter dizisini yazar ve dosyayı kapatır. Bir hata meydana gelirse FALSE boole değerini döndürür;
  • 103. satır: Bir hata oluşursa, bir istisna atılır;
  • 92-96. satırlar: [InterfaceDao] arayüzünün [saveResults] yönteminin uygulanması. Yine özel [saveString] yöntemi kullanılır. Burada [saveString] yönteminin ikinci parametresi, elemanları [TaxPayerData] türünde olan [$taxPayersData] dizisinden oluşturulan bir dizedir. Bu işlemin sonucunun ne olacağı merak edilebilir:

implode("\n", $taxPayersData)

[TaxPayerData] sınıfında (bağlantı paragrafı) aşağıdaki [__toString] yöntemini tanımladık:


public function __toString() {
    // nesnenin JSON dizesi
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

İşlem


implode("\n", $taxPayersData)

işlemi, [$taxPayersData] dizisinin her bir elemanını, [__toString] yöntemi ile karakter dizisine dönüştürdükten sonra \n satır sonu işaretiyle birleştirecektir. Bu işlem, şu biçimde bir karakter dizisi oluşturacaktır:

json1\njson2\n…

Sonuç

[TraitDao] özelliği, [InterfaceDao] arayüzünün [getTaxPayersData] ve [saveResults] adlı iki yöntemini uygulamıştır:


<?php

// ad alanı
namespace Application;

interface InterfaceDao {

  // vergi mükellefi verilerinin okunması
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // vergi idaresi verilerinin okunması (vergi dilimleri)
  public function getTaxAdminData(): TaxAdminData;

  // sonuçların kaydedilmesi
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Geriye, vergi idaresinden verileri alan [getTaxAdminData] yöntemini uygulamak kalıyor.

11.3.3. [ImpotsWithTaxAdminDataInJsonFile] sınıfı

[ImpotsWithTaxAdminDataInJsonFile] sınıfı, [InterfaceDao] arayüzünü şu şekilde uygular:


<?php

// ad alanı
namespace Application;

// sınıf tanımlama ImpotsWithDataInFile
class DaoImpotsWithTaxAdminDataInJsonFile implements InterfaceDao {
  // bir özelliğin kullanımı
  use TraitDao;
  // vergi dilimlerine ait verileri içeren TaxAdminData türündeki nesne
  private $taxAdminData;

  // oluşturucu
  public function __construct(string $taxAdminDataFilename) {
    // [$this->taxAdminData] özniteliğini başlatmak istiyoruz
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // verginin hesaplanmasına olanak tanıyan verileri döndürür
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }
}

Açıklamalar

  • 7. satır: [ImpotsWithTaxAdminDataInJsonFile] sınıfı, [InterfaceDao] arayüzünü uygular;
  • 9. satır: [ImpotsWithTaxAdminDataInJsonFile] sınıfı, [traitDao] özelliğini kullanır; bu özelliğin, [getTaxPayersData] ve [saveResults] yöntemlerini[InterfaceDao] arayüzünün [getTaxPayersData] ve [saveResults] yöntemlerini uyguladığı biliniyor. Dolayısıyla, [ImpotsWithTaxAdminDataInJsonFile] sınıfına geriye, vergi idaresinden verileri alan [getTaxAdminData] yöntemini uygulamak kalıyor;
  • 11. satır: 20-22. satırlardaki [getTaxAdminData] yönteminin döndürdüğü [TaxAdminData] türündeki öznitelik. Bu öznitelik, 14-17. satırlardaki oluşturucu tarafından başlatılır;

Uygulamamızın [dao] katmanını tamamladık: kendimize belirlediğimiz [InterfaceDao] arayüzünü tamamen uygulayan bir sınıfa sahibiz. Artık [métier] katmanına geçebiliriz.

11.4. [métier] katmanı

Şimdi mimarimizin [2] katmanını uygulayacağız:

Image

11.4.1. [InterfaceMétier] arayüzü

[métier] katmanının arayüzü şu şekilde olacaktır:


<?php

// ad alanı
namespace Application;

interface InterfaceMetier {

  // bir vergi mükellefinin vergilerinin hesaplanması
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // toplu iş modunda vergi hesaplaması
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}

Açıklamalar

  • 9. satır: [InterfaceMétier] arayüzü, bir mükellefin vergi tutarını, medeni durumu, çocuk sayısı ve yıllık maaşı gibi bilgiler verildiğinde hesaplayabilir. [calculerImpot] yöntemi, [dao] katmanını kullanmadığından istisna oluşturmaz;
  • 9. satır: [InterfaceMétier] arayüzü, verileri [$taxPayersFileName] adlı metin dosyasında toplanan bir grup vergi mükellefinin vergi tutarını da hesaplayabilir. Sonuçları [$resultsFileName] adlı bir metin dosyasına kaydeder. [executeBatchImpots] yöntemi, dosya sistemine erişimden sorumlu olan [dao] katmanına başvurmalıdır. Bu durumda, [dao] katmanından istisnalar yukarı doğru iletilebilir; [executeBatchImpots] yöntemi bu istisnaları yakalamayacak ve bunların ana komut dosyasına iletilmesine izin verecektir. Önemli olmayan hatalar, [$errorsFileName] adlı metin dosyasına kaydedilir;
  • 9. satır: [calculerImpot] yöntemi, tamamen [métier]'e özgü bir yöntemdir. Kullandığı verilerin nereden geldiğiyle ilgilenmez;
  • 12. satır: [executeBatchImpots] yöntemi, metin dosyalarına veri okumak ve yazmak için [dao] katmanına başvuracaktır. İş işlevi yöntemi olan [calculerImpot]'i tekrar tekrar çağıracaktır;

11.4.2. [Metier] sınıfı

[Metier] sınıfı, [InterfaceMetier] arayüzünü şu şekilde uygular:


<?php

// ad alanı
namespace Application;

class Metier implements InterfaceMetier {
  // Dao katmanı
  private $dao;
  // vergi idaresi verileri
  private $taxAdminData;

  //---------------------------------------------
  // [dao] katman ayarlayıcısı
  public function setDao(InterfaceDao $dao) {
    $this->dao = $dao;
    return $this;
  }

  public function __construct(InterfaceDao $dao) {
    // [dao] katmanında bir referans kaydedilir
    $this->dao = $dao;
    // vergi hesaplamasına olanak tanıyan veriler alınır
    // [getTaxAdminData] yöntemi bir istisna oluşturabilir ExceptionImpots
    // bu durumda istisna, çağıran koda iletilir
    $this->taxAdminData = $this->dao->getTaxAdminData();
  }

// vergi hesaplaması
// --------------------------------------------------------------------------
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {

    // sonuç
    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 {

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

  // revenuImposable=yıllıkMaaş-muafiyet
  // muafiyetin bir minimum ve bir maksimum değeri vardır
  private function getRevenuImposable(float $salaire): float {

    // sonuç
    return floor($revenuImposable);
  }

// olası bir indirimi hesaplar
  private function getDecôte(string $marié, float $salaire, float $impots): float {

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

// olası bir indirimi hesaplar
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

    // sonuç
    return ceil($réduction);
  }

  // toplu iş modunda vergi hesaplaması
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {

    // sonuçların kaydedilmesi
    $this->dao->saveResults($resultsFileName, $results);
  }

}

Açıklamalar

  • 6. satır: [Metier] sınıfı, [InterfaceMetier] arayüzünü uygular, yani [calculerImpot] (satır 30-34) ve [executeBatchImpots] (satır 66-70) yöntemlerini;
  • 8. satır: [dao] katmanına bir referans. [métier] katmanının harici verileri istediğinde kime başvuracağını bilmesi için bu referansın bulunması zorunludur. Bu öznitelik, 14-17. satırlardaki setter aracılığıyla veya 19-26. satırlardaki oluşturucu aracılığıyla başlatılacaktır;
  • 10. satır: Vergi idaresi verilerini kapsayan [TaxAdminData] türündeki nesne. Bu veriler, [calculerImpot] iş yöntemi için gereklidir. Bu öznitelik, 19-26. satırlardaki oluşturucu aracılığıyla başlatılır;
  • 19-26. satırlar: Oluşturucu, sınıfın iki özniteliğini başlatır:
    • [$dao] özniteliği, oluşturucuya parametre olarak geçirilen referansla başlatılır. Bu parametrenin türünün [InterfaceDao] arayüzünün türü olduğu ve böylece [Metier] sınıfının bu arayüzü uygulayan herhangi bir sınıf tarafından başlatılabilmesine olanak sağladığına dikkat edilmelidir;
    • [$taxAdminData] özniteliği, [dao] katmanının [getTaxAdminData] yöntemini çağırarak başlatılır;

Buradan, [calculerImpots] ve [executeBatchImpots] yöntemleri çalıştırıldığında, [$dao] ve [$taxAdminData] özniteliklerinin her ikisinin de başlatıldığı sonucuna varılır.

[calculerImpots] yöntemi şu şekildedir:


public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    // $marié: evet, hayır
    // $enfants: çocuk sayısı
    // $salaire: yıllık maaş
    // $this->taxAdminData: vergi dairesi verileri
    //
    // Vergi dairesi verilerinin mevcut olup olmadığı kontrol edilir
    if ($this->taxAdminData === NULL) {
      $this->taxAdminData = $this->getTaxAdminData();
    }
    // çocuklu vergi hesaplaması
    $result1 = $this->calculerImpot2($marié, $enfants, $salaire);
    $impot1 = $result1["impôt"];
    // çocuklar hariç vergi hesaplaması
    if ($enfants != 0) {
      $result2 = $this->calculerImpot2($marié, 0, $salaire);
      $impot2 = $result2["impôt"];
      // aile katsayısı üst sınırının uygulanması
      $plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
      if ($enfants < 3) {
        // $PLAFOND_QF_DEMI_PART avro (ilk 2 çocuk için)
        $impot2 = $impot2 - $enfants * $plafonDemiPart;
      } else {
        // $PLAFOND_QF_DEMI_PART avro ilk 2 çocuk için, sonraki çocuklar için iki katı
        $impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
      }
    } else {
      $impot2 = $impot1;
      $result2 = $result1;
    }
    // en yüksek vergi tutarı alınır
    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"];
    }
    // olası bir indirimin hesaplanması
    $décôte = $this->getDecôte($marié, $salaire, $impot);
    $impot -= $décôte;
    // olası vergi indiriminin hesaplanması
    $réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
    $impot -= $réduction;
    // sonuç
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }

Açıklamalar

  • Bu kod, bağlantı paragrafında açıklanan sürüm 3'teki [AbstractBaseImpots::calculerImpot] yöntemine aittir. Aynı durum, özel yöntemler olan [calculerImpot2, getDecôte, getRéduction, getRevenuImposable] için de geçerlidir;

[Metier::executeBatchImpots] yöntemi şöyledir:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // [dao] katmanından gelen istisnaların yukarıya iletilmesine izin verilir
    // vergi mükellefi verileri alınır
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // sonuç tablosu
    $results = [];
    // veriler üzerinde işlem yapılır
    foreach ($taxPayersData as $taxPayerData) {
      // vergi hesaplanıyor
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // formu doldurulur [$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"]);
      // sonuç sonuç tablosuna ekleniyor
      $results [] = $taxPayerData;
    }
    // sonuçların kaydedilmesi
    $this->dao->saveResults($resultsFileName, $results);
  }

Açıklamalar

  • 1. satır: Yöntem, [$taxPayersFileName] adlı metin dosyasında bulunan her bir vergi mükellefi için [calculerImpot] yöntemini tekrar tekrar çağırmalıdır. Sonuçları [$resultsFileName] adlı metin dosyasına kaydetmelidir. Karşılaşılan ölümcül olmayan hatalar, [$errorsFileName] adlı metin dosyasına kaydedilir. Yöntem kendisi istisna oluşturmaz, ancak [dao] katmanının oluşturduğu istisnaların yukarıya iletilmesine izin verir;
  • 4. satır: Vergi mükelleflerinin verileri [dao] katmanından istenir. Bu katman, [TaxPayerData] türünde bir dizi döndürür; bu dizi, [marié, nbEnfants, salaire, montant, décôte, réduction, surcôte, taux] öznitelik sınıfıdır (bkz. bağlantı paragrafı). Burada bir istisna meydana gelirse, bu istisna bir catch tarafından yakalanmadığı için otomatik olarak çağıran koda iletilir. Bu, istisna durumunda 6. satırın yürütülmeyeceği anlamına gelir;
  • 6. satır: [TaxPayerData] türündeki sonuç dizisi;
  • 8-22. satırlar: [$taxPayersData] vergi mükellefleri tablosundaki her bir öğe için vergi hesaplanır. Bunun için [calculerImpot] iç yöntemi çağrılır (10. satır);
  • satır 15-19: Elde edilen sonuç, henüz başlatılmamış olan [TaxPayerData] tablosunun özniteliklerini başlatmak için kullanılır;
  • satır 21: Elde edilen sonuç, [$results] sonuç tablosunda toplanır;
  • satır 24: tüm vergi mükellefleri için vergi hesaplandıktan sonra, sonuçlar bir metin dosyasına kaydedilir. Bu işlemi [dao] katmanı gerçekleştirir;

Sonuç

Genel olarak [métier] katmanını yazmak oldukça basittir, çünkü bu katman, verilere erişimi ve buna bağlı hata yönetimini üstlenen [dao] katmanına yöneliktir.

11.5. Ana komut dosyası

Şimdi mimarimizin [3] katmanının komut dosyasını yazalım:

Image

Ana komut dosyası şöyledir: [main.php]:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı sıkıya uyulur
declare (strict_types=1);

// ad alanı
namespace Application;

// PHP ile hata yönetimi
//ini_set("display_errors", "0");

// arayüz ve sınıfların dahil edilmesi
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 -----------------------------------------------------
// sabitlerin tanımlanması
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // [dao] katmanının oluşturulması
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // katman oluşturma [métier]
  $métier = new Metier($dao);
  // toplu iş modunda vergi hesaplama
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // hatanın görüntülenmesi
  print $ex->getMessage() . "\n";
}
// son
print "Terminé\n";
exit;


Açıklamalar

  • 24. satır: vergi mükellefi verilerinin dosya adı;
  • 25. satır: sonuç dosyasının adı;
  • 26. satır: hata dosyasının adı;
  • 27. satır: vergi idaresi verilerini içeren jSON dosyasının adı;
  • satır 31: [dao] katmanının oluşturulması;
  • satır 33: bu [dao] katmanını temel alan [métier] katmanının oluşturulması;
  • satır 35: [métier] katmanının [executeBatchImpots] yönteminin yürütülmesi;
  • satır 36-39: [métier] katmanının istisnalar iletebileceğini görmüştük. Bu istisnalar burada yakalanır;

11.6. Görsel testler

11.6.1. Test No. 1

Aşağıdaki [taxpayersdata.txt] vergi mükellefi dosyası ile:


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

şu hata dosyası elde edilir: [errors.txt]:


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

ve aşağıdaki [resultats.txt] sonuç dosyası elde edilir:

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 No. 2

Ana komut dosyasında, vergi mükellefleri dosyası için mevcut olmayan bir dosya adı verilir:

const TAXPAYERS_DATA_FILENAME = "taxpayersdata2.txt";

Konsolda elde edilen sonuçlar şu şekildedir:


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.
  • 1. satır: PHP yorumlayıcısından uyarılar (warning);
  • 2. satır: [dao] katmanı tarafından tetiklenen istisna hatası mesajı;

PHP yorumlayıcısının hata mesajlarını sessize almak mümkündür:

Image

Yukarıdaki kodun 21. satırı, PHP hatalarının görüntülenmemesini sağlar. Geliştirme aşamasında bu hataların görüntülenmesi gerekir. Üretim modunda ise gizlenmeleri gerekir.

Çalıştırma sonuçları şu şekildedir:


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

11.7. [Codeception] testleri

Görsel testler oldukça yetersizdir:

  • genellikle birkaç testle yetinilir;
  • bu görsel kontrol sırasında dikkat seviyesi değişkenlik gösterir ve bazı ayrıntılar gözden kaçabilir;

Gerçek profesyonel yazılım geliştirme ortamında, testler bu işi ana görevi olarak üstlenen uzman kişiler tarafından yazılır. Bu kişiler, testleri olabildiğince kapsamlı hale getirmeye çalışırlar. Bunun için test çerçeveleri kullanırlar.

Burada, NetBeans'e entegre edilebildiği için Codeception [https://codeception.com/] çerçevesini kullanacağız. Bu, geniş bir yelpazede olanaklar sunan bir çerçevedir. Biz bunlardan sadece birkaçını kullanacağız. Amaç, uygulama alıştırmasının her yeni sürümünden sonra, bunun çalıştığını hızlı bir şekilde doğrulayabilmek. Başarılı testlerin varlığı, geliştiriciye yazdığı koda güven verir. Bu önemli bir faktördür.

11.7.1. [Codeception] çerçeve yapısının kurulumu

Birçok PHP kütüphanesi gibi, [Codeception] çerçeve de [Composer] ile birlikte kurulur. Bu nedenle bir Laragon terminali açıyoruz (bkz. bağlantı paragrafı).

Öncelikle PHPUnit ve [https://phpunit.de/] test çerçevelerini kurmamız gerekiyor. Zira Codeception, arka planda PHPUnit çerçevesini kullanır:

Image

Ardından, Codeception çerçevesini kuruyoruz:

Image

Hepsi bu kadar. Şimdi [Codeception]'in NetBeans'e entegrasyonuna bakalım.

11.7.2. [CodeCeption]'in NetBeans'e entegrasyonu

Image

  • [1-2]'te proje özelliklerine erişiyoruz;
  • [3-4]'te, [Codeception]'i projenin test çerçevelerinden biri olarak belirleyin;

Image

Image

  • [5-8] dosyasında, proje için [Codeception] çerçeve yapısı başlatılır;

Image

  • [9]'te, [tests] adlı bir klasör ve [10-11]'te [codeception.yml] adlı bir yapılandırma dosyası oluşturulmuştur. [11] dosyası, [10] dosyasıyla aynıdır. Codeception, [10] dosyasına özel bir anlam vermek için sadece [Important Files] klasörünü oluşturmuştur;
  • [12-13] dosyasında ise proje özelliklerine geri dönülür;

Image

  • [14-16]'te, [tests] ve [16] klasörleri, projenin test klasörü olarak belirlenir;
  • [16]'te, [tests] klasörü yeni adı olan [Test Files] altında görünür. Bu klasörün bir PHP projesinde bulunması, bu projenin programlanmış test çerçevesini içerdiğini gösterir;
  • testlerimizi [unit] ve [17] klasörlerinde oluşturacağız;

11.7.3. [dao] katman testleri

Image

  • tüm testlerimizi [unit] ve [1] klasörlerinde oluşturacağız;
  • [Codeception] test sınıflarının adları [Test] anahtar sözcüğüyle bitmelidir, aksi takdirde sınıflar test sınıfı olarak tanınmayacaktır;

[Codeception] test sınıflarımız şu şekilde olacaktır: [https://codeception.com/docs/05-UnitTests]:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı sıkıya uyulması
declare (strict_types=1);

// ad alanı
namespace Application;

// test ortamının yüklenmesi


class DaoTest extends \Codeception\Test\Unit {
  // test öznitelikleri
  private $attribut1;

  public function __construct() {
    parent::__construct();
    // test ortamının başlatılması

  }

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

  }

}

Açıklamalar

  • 7. satır: Test sınıfları, test edilen uygulama ile aynı ad alanında olacaktır;
  • 9-10. satırlar: Burada, test edilen sınıfları ve arayüzleri yüklemek için [require] işlemlerini bulacağız;
  • 12. satır: Test sınıfının adı mutlaka [Test] anahtar sözcüğüyle bitmelidir. Bu sınıf, [\Codeception\Test\Unit] sınıfını genişletmelidir;
  • 16-20. satırlar: Oluşturucu, test ortamını başlatmamızı sağlayacaktır;
  • 23. satır: Test yöntemlerinin adları mutlaka [test] anahtar sözcüğüyle başlamalıdır;
  • satır 25-31: çeşitli test yöntemleri kullanılabilir;

[DaoTest] test sınıfı şu şekilde olacaktır:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı uyumu
declare (strict_types=1);

// ad alanı
namespace Application;

// sabitler
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// arayüz ve sınıfların dahil edilmesi
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 -----------------------------------------------------
// sabitlerin tanımlanması
const TAXADMINDATA_FILENAME = "taxadmindata.json";

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

  public function __construct() {
    parent::__construct();
    // katman oluşturma [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // testler
  public function testTaxAdminData() {

  }
}

Açıklamalar

Uygulama alıştırmasının bir sürümüne ait testleri oluşturmak için, o sürümün ana betiğinde kullanılanla aynı ortamı kullanacağız. Sürüm 04'teki ortam, aşağıdaki [main.php] betiğidir:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı sıkıya uyulması
declare (strict_types=1);

// ad alanı
namespace Application;

// PHP ile hata yönetimi
ini_set("display_errors", "0");

// arayüz ve sınıfların dahil edilmesi
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 -----------------------------------------------------
// sabitlerin tanımlanması
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // katman oluşturma [dao]
  $dao = new DaoImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // katman oluşturma [métier]
  $métier = new Metier($dao);
  // toplu iş modunda vergi hesaplama
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // hatanın görüntülenmesi
  print $ex->getMessage() . "\n";
}
// son
print "Terminé\n";
exit;

[dao] katmanını test etmek için, test sınıfında:

  • [main.php] dosyasının 13-27. satırlarındaki ortamı kullanacağız;
  • test sınıfının oluşturucusunda, 31. satırdaki gibi [dao] katmanını oluşturuyoruz;
  • test yöntemlerini yazıyoruz;

Tüm test sınıfları için bu şekilde ilerleyeceğiz.

Test sınıfının tam koduna geri dönelim:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı sıkıya uyulması
declare (strict_types=1);

// ad alanı
namespace Application;

// sabitler
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// arayüz ve sınıfların dahil edilmesi
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 -----------------------------------------------------
// sabitlerin tanımlanması
const TAXADMINDATA_FILENAME = "taxadmindata.json";

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

  public function __construct() {
    parent::__construct();
    // katman oluşturma [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // testler
  public function testTaxAdminData() {
    // hesaplama sabitleri
    $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());
    // vergi dilimleri
    $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());
  }

}

Yorumlar

  • 10-25. satırlar: testler için gerekli ortamın yüklenmesi ve sabitlerin tanımlanması;
  • 31-36. satırlar: [dao] katmanının oluşturulması (34. satır), ardından 29. satırdaki [$taxAdminData] özniteliğinin başlatılması. Bu öznitelik, vergi idaresine ait verileri içerir;
  • satır 39-55: tek test yöntemi. Bu, [$taxAdminData] özniteliğinin içeriğinin beklenen değerle uyuşup uyuşmadığının kontrol edilmesinden ibarettir;
  • satır 41-50: vergi hesaplamasına ilişkin sabitlerin doğrulanması;
  • satır 52-55: vergi dilimlerinin doğrulanması. [assertSame] yöntemi, iki PHP nesnesinin (burada tablolar) aynı olup olmadığını kontrol eder;

Bu test sınıfını çalıştırmak için aşağıdaki adımlar izlenir:

Image

  • [1-2]'te test çalıştırılır;
  • [3]: test sonuçları penceresi;
  • [4]: çalıştırılan test sınıfı;
  • [5]: sonuçlar. Burada tek test yöntemi başarılı olmuştur;
  • [6]: Test başarısız olduğunda veya daha sıklıkla hiçbir test çalıştırılmadığında, [6] penceresine bakmak gerekir. Çoğu zaman, test ortamının yüklenmesi başarısız olur ve bu durumda hiçbir test çalıştırılamaz. [6] penceresinde görüntülenen hatalar, klasik bir PHP komut dosyasının çalıştırılması sırasında karşılaşılabilecek hatalardır;

Hatalı bir test örneğini inceleyelim:

Test sınıfında, bir sabitin tanımında bir hata ekliyoruz:


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

ardından testi çalıştırıyoruz. Elde edilen sonuç şöyledir:

Image

[4] penceresinde:

Image

11.7.4. [métier] katman testleri

[MetierTest] test sınıfı, [DaoTest] sınıfıyla aynı oluşturma kurallarına uyar, ancak daha fazla test yöntemi içerir:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı uyum
declare (strict_types=1);

// ad alanı
namespace Application;

// sabitler
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-04");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// arayüz ve sınıfların dahil edilmesi
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 -----------------------------------------------------
// sabitlerin tanımlanması
const TAXADMINDATA_FILENAME = "taxadmindata.json";

class MetierTest extends \Codeception\Test\Unit {
  // iş mantığı katmanı
  private $métier;

  public function __construct() {
    parent::__construct();
    // katmanın oluşturulması [dao]
    $dao = new DaoImpotsWithTaxAdminDataInJsonFile(ROOT . "/" . TAXADMINDATA_FILENAME);
    // katman oluşturma [métier]
    $this->métier = new Metier($dao);
  }

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

Açıklamalar

  • 10-25. satırlar: Test ortamını tanımlayan dosyaların yüklenmesi. Bu ortam, [dao] katmanı ile aynıdır;
  • 31-37. satırlar: [dao] ve [métier] katmanlarının örneklenmesi;
  • satır 40-47: bir vergi hesaplama testi;
  • satır 41: [métier] katmanı kullanılarak belirli bir vergi hesaplaması yapılır;
  • satır 42-46: elde edilen sonuçların, vergi idaresinin [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm] simülatörünün sonuçlarıyla aynı olup olmadığı kontrol edilir;
  • satır 23-26: Eşitlik testleri 1 avroye kadar yapılmaktadır. Nitekim, yuvarlama sorunları nedeniyle belgedeki algoritmanın beklenen sonuçları 1 avroya kadar verdiğini gördük;
  • 27. satır: Vergi oranı ise hata payı olmaksızın hesaplanmaktadır;
  • 49-137. satırlar: Bu tür testler, her seferinde farklı bir vergi mükellefi yapılandırmasıyla 10 kez tekrarlanır;

Testler şu sonuçları vermektedir:

Image

11.7.5. Gelecek sürümlerin testleri

Bundan sonra, [dao] ve [métier] katmanlarına yönelik testler, 04 sürümündekilerle aynı olacaktır. Yalnızca test ortamı değişecektir. Bu nedenle, sadece bunu ve test sonuçlarını sunacağız.