Exploration de l'API Standard de Crystal-lang PART 2 (Spec, ENV, Module)
Tags: crystal
Published: 2023-07-12 09:57:37

---
title: Exploration de l'API Standard de Crystal-lang PART 2 (Spec, ENV, Module)
published: true
description: Deuxième partie avec usage de Spec, Module, ENV
tags: crystal
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-07-12 07:44 +0000
---

#Introduction
Voici la partie 2 de notre série. il est nécessaire d'avoir lu la partie précédente pour suivre cet article.

Dans cet article, nous allons voir : 

- [Spec](https://crystal-lang.org/api/1.9.0/Spec.html) librairie interne de test
- Les modules.
- [ENV](https://crystal-lang.org/api/1.9.0/ENV.html) Manipulation des variables d'environnement

# C'est parti

## lançons les tests - premier essai.

La commande de test est `crystal spec`. Cette commande va exécuter les test contenus dans tous les fichier `_spec.cr` de toutes l'arborescence `./spec/**/*`.

Voici le code par défaut, qui a été généré avec le `crystal init` dans le fichier `./spec/web_spec.cr`

```crystal
describe Web do 
  # TODO: Write tests
  
  it "works" do
    false.should eq(true)
  end 
end
```

`describe` est un mot clé pour décrire une suite de test. Nous pouvons lui passer en argument soit une chaîne de caractère soit le nom d'un objet. Nous pouvons ecrire un autre bloc `describe` dans celui existant. Cela aura pour effet de se concaténer.

Dans notre cas, l'objet `Web` n'existe pas. la commande `crystal spec` va nous répondre une erreur.

`it` est le bloc de test. Nous lui passons une chaîne de caractère en argument. Chaîne qui doit décrire le test. Par convention, si le test concerne une méthode de l'objet passer en `describe`, nous préfixons le nom de la méthode par un `#`

Ensuite, nous pouvons voir que le test écrit est voué à l’échec `false` ne valant jamais `true`. 

Corrigeons ces deux points et regardons ce qui ce passe...

Dans l'état actuel des choses, il ne se passera rien et votre `crystal spec` restera bloqué. Il faudra un `CTRL+C` pour sortir.

Voici ce qu'il se passe. Notre fichier `./spec/web_spec.cr` charge le fichier `./spec/spec_helper.cr`. Ce dernier charge notre code du fichier `./src/web.cr`. Or, notre code lance un serveur HTTP et attend en boucle les requêtes. C'est ce qui bloque notre test.

Deux solutions s'offre à nous pour l'instant.

Rapide : Nous sortons la commande `server.listen` dans un autre fichier. Et nous lancerons ce nouveau fichier quand nous voudrons lancer le serveur.

Par configuration : Nous conditionnons le lancement de `server.listen` au fait de ne pas être en environnement de test.

Dans notre cas, nous partons sur la première solution pour évoluer vers la seconde.

## Testons notre configuration.

Dans le code actuel, nous avons quelques constantes au début : 

```crystal
TODO_FILE_PATH = File.join(Dir.current, "todo.txt")
```

et quelques valeurs en dur pour la configuration de notre serveur http

```crystal
server.bind_tcp "0.0.0.0", 8080
```

et comme nous venons de le voir, nous avons aussi besoin de déterminer dans quel environnement nous sommes.

Supprimons le fichier `./spec/web_spec.cr` qui ne nous sert pas. Créons un fichier `./spec/config_spec.cr`.

Commençons l’écriture de notre test, nous avons besoin d'un objet qui va stocker notre configuration et permettre un accès dans tout notre code.

La première constante va être `TODO_FILE_PATH`.

```crystal
require "./spec_helper"

describe Config do
  describe "#TODO_FILE_PATH" do
    pending "return a path by concat current dir with 'todo.txt' do
    end
  end
end
```

le mot clé `pending` permet ne pas exécuter ce test tout en gardant la structure pour plus tard.

Passons à l’écriture du test et du module

```crystal
  require "./spec_helper"
  
  describe Config do
    describe "#TODO_FILE_PATH" do
      it "return a path by concat current dir and todo_file_name" do                             
        Config::TODO_FILE_PATH.should eq File.join(Dir.current, "todo.txt")
      end
    end
  end
```

ce qui se résout par le code suivant :

```crystal
  module Config
    TODO_FILE_PATH = ENV.fetch("TODO_FILE_PATH", File.join(Dir.current, "todo.txt"))
  end
```

Crystal vient avec la librairie standard `ENV` qui permet de lire et de manipuler les variables d'environnement.

J'ai écris le code du module `config` dans le fichier `src/web.cr`. Nous allons le déplacer dans son propre fichier `src/config.cr` et le charger dans son fichier d'origine.

```crystal
# src/web.cr
require "http/server"
require "./config"
...
```

On remplace toutes les occurrences de `TODO_FILE_PATH` par `Config::TODO_FILE_PATH`

Continuons avec les variables du serveur http et l'environnement.

Le test 
```crystal
  require "./spec_helper"
  
  describe Config do
    describe "#TODO_FILE_PATH" do
      it "return a path by concat current dir and todo_file_name" do
        Config::TODO_FILE_PATH.should eq File.join(Dir.current, "todo.txt")
      end
    end
  
    describe "#environment" do
      it "return development as default environment" do
        ENV.delete("GTD_ENVIRONMENT")
        Config.environment.should eq "development"
      end
      it "return passed value as environment" do
        ENV["GTD_ENVIRONMENT"]= "test"
        Config.environment.should eq "test"
        ENV.delete("GTD_ENVIRONMENT")
      end
    end
  
    describe "#PORT" do
      it "return default port" do
        Config::PORT.should eq 3000
      end
    end
  
    describe "#BIND_IP" do
      it "return default binding" do
        Config::BIND_IP.should eq "0.0.0.0"
      end
    end
  end
```

le module 

```crystal
  module Config
    TODO_FILE_PATH = ENV.fetch("TODO_FILE_PATH", File.join(Dir.current, "todo.txt"))
    PORT = ENV.fetch("PORT", "3000").to_i
    BIND_IP = ENV.fetch("BIND_IP", "0.0.0.0")
      
    def self.environment
      ENV.fetch("GTD_ENVIRONMENT", "development")
    end
  end
```

tous les tests sont passant.

# Lancement du serveur sauf pour les tests.

Revenons à notre deuxième solution de gestion du lancement du serveur maintenant que nous avons le module de configuration.

Supprimons le fichier créer pour la première solution.

Puis, à la fin du fichier `src/web.cr` ajoutons notre condition de lancement.

```crystal
  server.bind_tcp Config::BIND_IP, Config::PORT
  if Config.environment != "test"
    server.listen
  end
```

Si nous lançons notre code avec `crystal src/web.cr`, nous avons bien notre application disponible via http://localhost:3000/

si je lance les tests avec la commande `GTD_ENVIRONMENT=test crystal spec`, les tests se lancent sans le serveur.

Il peut être lassant d'ajouter la variable d'environnement à chaque lancement. Pour ma part, j'ai ajouté cette variable dans le fichier `./spec/spec_helper.cr` comme suit :

```crystal
  require "spec"
  ENV["GTD_ENVIRONMENT"]="test"
  require "../src/web"
```

# Conclusion
Nous avons vu les bases de Spec, la manipulation de l'environnement via ENV et la forme d'objet Module.

Dans le prochain article, nous regrouperons les méthodes liées au dépôt de tâches au sein d'une classe

#Note :
[repository gitlab](https://gitlab.com/JadeKharats/gtd/-/tree/4cf8988ae2f71976f93042dbd819e31e1d5e8a33)