DEV Community

David YOTEAU
David YOTEAU

Posted on • Edited on

Exploration de l'API Standard de Crystal-lang PART 3 (Class)

Introduction

Partie 3 : Encapsulons les méthodes liées à la persistance des données dans une classe.

C'est parti.

Etat des lieux

Pour l'instant, nous gérons nos accès à la persistance via 2 méthodes écrites sur le Top level.

def add_task(item : String)
  file = File.open(Config::TODO_FILE_PATH, "a")
  file.puts item
  file.close
end

def task_list : Array(String)
  File.read_lines(Config::TODO_FILE_PATH)
end
Enter fullscreen mode Exit fullscreen mode

Vision Objet.

Si nous regardons ça sous la vision d'un objet, notre persistance est un Repository que l'on initialise avec le chemin d'accès. Puis nous aurons besoin des méthodes d'ajout d'un élément au dépôt et de lister ce que contient le dépôt.

Passons aux tests

Pour l'initialisation, nous voulons passer un chemin en argument. La vérification sera que le chemin est bien renseigné et que le fichier existe. Puis nous supprimerons le fichier pour la ré-utilisabilité du test.

  describe "#new" do
    it "initialize Repository with given path" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "new.txt")
      repo = Repository.new(path: path)
      repo.path.should eq path
      File.exists?(path).should eq true
      File.delete(path)
    end
  end
Enter fullscreen mode Exit fullscreen mode

Pour la méthode de remontée de la liste des éléments, nous allons créer un fichier all.txt dans ./spec/fixtures/repo. Nous valorisons le fichier avec 3 lignes.

test
all
method
Enter fullscreen mode Exit fullscreen mode

et le test comme suit

  describe "#all" do
    it "return a list for a given file" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "all.txt")
      repo = Repository.new(path)
      repo.all.should eq ["test","all","method"]
    end
  end
Enter fullscreen mode Exit fullscreen mode

Enfin, pour l'ajout, nous initialisons un repo vide. Nous appelons notre méthode d'ajout. Puis vous vérifions que notre repo contient bien notre élément.

  describe "#add" do
    it "store todo into the given file" do
      path = File.join(Dir.current, "spec", "fixtures", "repo", "add.txt")
      repo = Repository.new(path)
      repo.add("test add method")
      repo.all.should eq ["test add method"]
      File.delete(path)
    end
  end
Enter fullscreen mode Exit fullscreen mode

Le tout dans le fichier ./spec/repository_spec.cr, dans un describe global.
En résumé :

require "./spec_helper"

describe Repository do
  describe "#new" do
    it "initialize Repository with given path" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "new.txt")
      repo = Repository.new(path: path)
      repo.path.should eq path
      File.exists?(path).should eq true 
      File.delete(path)
    end
  end
  describe "#all" do
    it "return a list for a given file" do
      path = File.join(Dir.current,"spec", "fixtures", "repo", "all.txt")
      repo = Repository.new(path)
      repo.all.should eq ["test","all","method"]
    end
  end
  describe "#add" do
    it "store todo into the given file" do
      path = File.join(Dir.current, "spec", "fixtures", "repo", "add.txt")
      repo = Repository.new(path)
      repo.add("test add method")
      repo.all.should eq ["test add method"]
      File.delete(path)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Implémentation de notre Repository

Si nous lançons les tests en tant que tel, nous aurons plein d'erreur de méthodes et de classes non définies.

Écrivons le code minimum pour que les tests se lancent

class Repository
  getter path : String

  def initialize(@path)
  end

  def all
  end

  def add(element : String)
  end
end
Enter fullscreen mode Exit fullscreen mode

La commande crystal spec s'executent maintenant sans erreurs et tous nos tests sont en échec.

crystal spec
.....FFF

Failures:

  1) Repository #new initialize Repository with given path
     Failure/Error: File.exists?(path).should eq true

       Expected: true
            got: false

     # spec/repository_spec.cr:9

  2) Repository #all return a list for a given file
     Failure/Error: repo.all.should eq ["test","all","method"]

       Expected: ["test", "all", "method"]
            got: nil

     # spec/repository_spec.cr:17

  3) Repository #add store todo into the given file
     Failure/Error: repo.all.should eq ["test add method"]

       Expected: ["test add method"]
            got: nil

     # spec/repository_spec.cr:25

Finished in 416 microseconds
8 examples, 3 failures, 0 errors, 0 pending

Failed examples:

crystal spec spec/repository_spec.cr:5 # Repository #new initialize Repository with given path
crystal spec spec/repository_spec.cr:14 # Repository #all return a list for a given file
crystal spec spec/repository_spec.cr:21 # Repository #add store todo into the given file
Enter fullscreen mode Exit fullscreen mode

On voit ici l'importance du nommage des tests

Implémentation de l'init

Nous souhaitons que le chemin passé en argument corresponde à un fichier qui existe. Dans le cas contraire, nous le créons.

class Repository
...
  def initialize(@path)
    unless File.exists?(@path)
      file = File.new(@path,"wb")
      file.close
    end
  end
...
Enter fullscreen mode Exit fullscreen mode

Le mode wb permet de créer le fichier et de s'assurer qu'il soit vide.

Implémentation de la méthode all

Nous avons besoin d'ouvrir le fichier correspondant au path et d'en lister le contenu. Nous pouvons reprendre le code d'origine et l'adapter à notre nouveau contexte.

class Repository
...
  def all
    File.read_lines(@path)
  end 
...
Enter fullscreen mode Exit fullscreen mode

Implémentation de la méthode add

Comme ci dessus, nous adaptons l'existant.

class Repository
...

...
Enter fullscreen mode Exit fullscreen mode

Execution des tests.

crystal spec
........

Finished in 473 microseconds
8 examples, 0 failures, 0 errors, 0 pending
Enter fullscreen mode Exit fullscreen mode

Tout va bien.

Refactoring.

Etape 1 : Mettre notre nouvelle classe dans un fichier à part.

Nous prenons le code de notre class pour le mettre dans le fichier ./src/repository.cr et nous le remplaçons par require "./repository" dans notre fichier principal.

Etape 2 : Nous remplaçons les anciennes méthodes par notre classe.

    items = task_list
Enter fullscreen mode Exit fullscreen mode

devient

    items = Repository.new(Config::TODO_FILE_PATH).all
Enter fullscreen mode Exit fullscreen mode

et

          add_task(item)
Enter fullscreen mode Exit fullscreen mode

devient

  Repository.new(Config::TODO_FILE_PATH).add(item)
Enter fullscreen mode Exit fullscreen mode

Etape 3 : Nettoyage

Nous supprimons le code des anciennes méthodes.

Conclusion.

Nous avons vu comment créer une classe et la tester.

Dans la prochaine partie, nous allons encapsuler la partie serveur HTTP.

Top comments (0)