Tests et intégration continue avec CircleCI

  • | Développement
Workflow avec CircleCI

Cet article est la suite de l'article Simple Scraping Python. Je vous conseille de lire cet article notamment pour récupérer le code source.

Aujourd'hui nous allons faire des tests et de l'intégration continue avec CircleCI en ajoutant à notre script une série de tests unitaires sur nos fonctions d'extraction.

1. Ajouter des tests à Simple Scraping Python

Si vous avez téléchargé les sources du projet sur Github et installé le projet avec la commande ci dessous, vous avez installé la librairie pytest qui va nous servir à tester l'application.

pip install -r requirements.txt

Initialement si vous tapez la commande pytest, la librairie cherche les fichiers de test, si il n'y a pas de fichiers de tests, la commande s'execute et affiche :

julien@MacBook-Pro ~/Sites/test/test-github-circleci (master=) $ pytest
=================================================== test session starts ===================================================
platform darwin -- Python 3.9.4, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /Users/julien/Sites/test/test-github-circleci
plugins: cov-2.11.1
collected 0 items
=================================================== no tests ran in 0.01s ===================================================

pytest lance les tests sur tous les fichiers avec les masks "test_*.py" ou "*_test.py" dans le répertoire racine du projet et dans tous les sous dossiers. 

Dans le code source du projet, il y a un fichier test_function.py qui regroupe les tests des fonctions présentes dans le fichier function.py.

Si on relance la commande pytest à la racine du projet, on obtient : 

julien@MacBook-Pro ~/Sites/test/python-tuto (master *) $ pytest
=================================================== test session starts ===================================================
platform darwin -- Python 3.9.4, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /Users/julien/Sites/test/python-tuto
plugins: cov-2.11.1
collected 14 items
test_function.py ..............                                                                                                                                                                      [100%]
=================================================== 14 passed in 0.27s ===================================================
julien@MacBook-Pro ~/Sites/test/python-tuto (master *) $

On voit qu'il y a 14 tests qui sont passés.

 

Contenu du fichier text_function.py : 

# content of test_function.py
import pytest
import bs4
from bs4 import BeautifulSoup
from function import extract_id, extract_published, extract_title, extract_city, extract_price, extract_url
@pytest.fixture
def div():
    html = open("./annonces/informatique.html", "r")
    soup = BeautifulSoup(html, "lxml", from_encoding="utf-8")
    div = soup.find(name="div", attrs={"class":"row"})
    return div
@pytest.fixture
def div2():
    html = open("./annonces/test.html", "r")
    soup = BeautifulSoup(html, "lxml", from_encoding="utf-8")
    div = soup.find(name="div", attrs={"class":"row"})
    return div
@pytest.fixture
def div3():
    html = open("./annonces/test.html", "r")
    soup = BeautifulSoup(html, "lxml", from_encoding="utf-8")
    div = soup.find(name="div", attrs={"class":"row","id":"2"})
    return div
@pytest.fixture
def div4():
    html = open("./annonces/test.html", "r")
    soup = BeautifulSoup(html, "lxml", from_encoding="utf-8")
    div = soup.find(name="div", attrs={"class":"row","id":"3"})
    return div
def test_extract_id(div):
    assert extract_id(div) == "987654"
def test_bad_extract_id(div2):
    assert extract_id(div2) == "Nothing_found"
def test_extract_published(div):
    assert extract_published(div) == 0
def test_bad_extract_published(div2):
    assert extract_published(div2) == "Nothing_found"
def test_extract_published_with_text_not_expected(div3):
    assert extract_published(div3) == "Nothing_found"
def test_extract_published_with_int_not_expected(div4):
    assert extract_published(div4) == 0
def test_extract_title(div):
    assert extract_title(div) == "Titre de l'annonce 1"
def test_bad_extract_title(div2):
    assert extract_title(div2) == "Nothing_found"
def test_extract_city(div):
    assert extract_city(div) == "Paris (75)"
def test_bad_extract_city(div2):
    assert extract_city(div2) == "Nothing_found"
def test_extract_price(div):
    assert extract_price(div) == 233
def test_bad_extract_price(div2):
    assert extract_price(div2) == "Nothing_found"
def test_extract_url(div):
    assert extract_url(div) == "annonce1.html"
def test_bad_extract_url(div2):
    assert extract_url(div2) == "Nothing_found"

 

Ce fichier va tester toutes les fonctions qui se trouvent dans function.py, notamment grace à des fixtures qui me permettent d'aller chercher des morceaux de code Html à passer en paramètre aux fonctions.

J'ai testé pour chaque fonction quand cela se passe bien et également quand cela se passe mal.

A ce niveau on a 14 tests qui passent et renvoient bien les valeurs attendu dans chaque cas de figure.

On pourrait lancer les tests à chaque fois qu'on fait un changement, mais ça serait un peu répétitif. On va donc automatiser cet aspect avec CircleCI.

 

2. Automatisation des tests et intégration continue avec CircleCI

A. Qu'est ce que l'Intégration continue

CircleCI est un service en ligne permettant de faire de l'intégration continue. Mais qu'est ce que l'intégration continue?

L'intégration continue est un ensemble de règles qui va nous permettre de tester chaque modification du code source afin d'éviter les bugs ou les regressions.

On va pouvoir organiser facilement des suites de tests unitaires ou fonctionnels.

Ces tests permettent globalement de vérifier : 

  • que l'application fonctionne bien sous différents OS
  • que l'application fonctionne bien avec différentes versions de briques logiciels
  • que les modifications de code n'ont pas engendré de bugs
  • que les modifications de l'application n'ont pas engendré de problèmes fonctionnels
  • etc...

L'intégration continue est donc ton amie ;) puisqu'elle va améliorer la qualité de l'application. 

Le but est de détecter les problèmes d'intégrations au plus tôt. Cela marche souvent de pair avec la livraison continue qui consiste par des cycles très courts à livrer des fonctionnalités applicatives.

 

B. Qu'est ce que CircleCI

Comme on l'a vu juste au dessus, CircleCI est un service d'intégration continue qui permet :

  • de s'intégrer à un logiciel de gestion de version (VCS) tel que Github ou Bitbucket.
  • de définir un workflow au travers d'un pipeline
  • de tester toute la couche infra via Docker ou des machines virtuels (Windows, Mac, Linux)
  • de tester l'application via des tests unitaires (avec pytest sur Python ou PHPUnit sur Php) 
  • de tester l'application fonctionnellement via des tests navigateurs (avec Selenium par exemple)
  • d'automatiser les déploiements sur les serveurs

C'est juste le feu! ;)

 

C. Configurer CircleCI

Pour commencer à linker Github et CircleCI, on va créer un compte sur CircleCI en se connectant avec son compte Github ou Bitbucket.

Créer un compte CircleCI avec Github ou Bitbucket

 

Une fois connecté, on clique sur "Projects" pour lister les projets qui sont dans notre compte Github ou Bitbucket.

Projets sur CircleCI

 

Si on clique sur le nom d'un projet, on accède à la page d'exemple de configuration CircleCI :

Exemple de configuration CircleCI par langage de programmation

 

Pour cet exemple j'ai choisi Python :

version: 2.1
orbs:
  # The python orb contains a set of prepackaged CircleCI configuration you can use repeatedly in your configuration files
  # Orb commands and jobs help you with common scripting around a language/tool
  # so you dont have to copy and paste it everywhere.
  # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python
  python: circleci/python@1.2
workflows:
  sample:  # This is the name of the workflow, feel free to change it to better match your workflow.
    # Inside the workflow, you define the jobs you want to run. 
    # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 
    jobs:
      - build-and-test
jobs:
  build-and-test:  # This is the name of the job, feel free to change it to better match what you're trying to do!
    # These next lines defines a Docker executors: https://circleci.com/docs/2.0/executor-types/
    # You can specify an image from Dockerhub or use one of the convenience images from CircleCI's Developer Hub
    # A list of available CircleCI Docker convenience images are available here: https://circleci.com/developer/images/image/cimg/python
    # The executor is the environment in which the steps below will be executed - below will use a python 3.9 container
    # Change the version below to your required version of python
    docker:
      - image: cimg/python:3.8
    # Checkout the code as the first step. This is a dedicated CircleCI step.
    # The python orb's install-packages step will install the dependencies from a Pipfile via Pipenv by default.
    # Here we're making sure we use just use the system-wide pip. By default it uses the project root's requirements.txt.
    # Then run your tests!
    # CircleCI will report the results back to your VCS provider.
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pip
          # app-dir: ~/project/package-directory/  # If you're requirements.txt isn't in the root directory.
          # pip-dependency-file: test-requirements.txt  # if you have a different name for your requirements file, maybe one that combines your runtime and test requirements.
      - run:
          name: Run tests
          # This assumes pytest is installed via the install-package step above
          command: pytest

 

Dans la config on retrouve :

  • la version de python qui va être utilisée
  • le package manager utilisé : pip
  • et le run pour les tests avec la commande pytest

 

Il suffit maintenant de créer un répertoire .circleci à la racine du projet et ajouter un fichier config.yml avec la config ci-dessus.

A noter que pour que cette config fonctionne avec nos tests ci dessus, il faudra avoir créé à la racine un fichier requirements.txt avec toutes les dépendances et notamment pytest.

 

Une fois toutes ces étapes réalisées, on retourne dans les projets de CircleCI (image ci dessus) et on appuie sur le bouton "Set Up Project" et on lance la Build avec le bouton "Start Building".

Start Building

 

 

D. Lancer les test et voir les résultat dans CircleCI 

Quand on push maintenant nos modifications, CircleCI récupère le code et lance le workflow de la config :

Tests et intégration continue dans CircleCI

 

A partir de là, on peut modifier la configuration à convenance (documentation de CircleCI) et donc faire des tests dans tous les sens et du déploiement. 

De plus on pourra également intégrer CircleCI à Github pour avoir le status des builds CircleCI directement dans Github.

L'auteur de cet article
Julien Krier
Responsable Digital, ayant occupé différents postes en informatique depuis 2001, Julien Krier a travaillé sur de multiples plateformes, sites de contenu ou e-commerce à fort trafic. Il est spécialisé dans les technologies web sur les CMS comme Drupal et les Framework Php comme Symfony.
Cet article vous a aidé?
Average: 4.7 (3 votes)
Partagez cet article
Articles sur le même sujet
Simple scraping python : un script pour faire du web scrapingSimple Scraping Python est un script pour faire du web scraping sur un site web afin d'en extraire les données et en générer un fichier Csv.