• 4 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 07/10/2017

Utilisez des mocks

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

 

Imiter une réponse HTTP

Nous avons presque terminé de tester le fichier  world.py . Mais notre programme en contient en fait deux, rappelez-vous ! Le premier,   download_agents.py  , récupère des agents de l'API et le second,  world.py,  les utilise pour afficher le graphique.

Nous allons créer un second fichier,  test_download_agents.py, pour écrire nos tests. Ceci nous permettra de créer une structure en miroir : nos tests sont organisés de la même manière que nos fichiers.

Que fait  download_agents.py  ? Ce script interroge l'API PPLAPI et inscrit les résultats dans un fichier JSON.

Nous sommes face à un souci. En effet, chaque lancement de ce script écrit dans le même fichier. Or nous ne voulons pas changer les données de ce fichier, fait pour héberger de vraies données, dans le but de faire nos tests. Normalement ce fichier ne doit se recharger que si l'utilisateur le veut et non si nous lançons nos tests.

De même, il nous faut pouvoir lancer nos tests sans avoir besoin d'internet. Cela vous paraît étrange ? Mais imaginez si vous travaillez dans le train ! De plus, lancer des tests doit être très rapide. Or chaque appel HTTP sera plus lent qu'un simple retour de l'ordinateur.

L'idéal serait de pouvoir tricher un peu en disant à Pytest : "Je veux que tu testes la fonction mais, plutôt que de lancer un appel 'pour de vrai', utilise plutôt ces données-là !". Hé oui, vous allez apprendre à tricher !

Ceci s'appelle un mock.

De manière générale, nous utilisons des mocks pour imiter le comportement de librairies ou de modules que nous ne souhaitons pas tester. Nous considérons que ces modules sont externes à notre code et fonctionnent. Nous n'avons donc pas à les tester !

Découvrons tout de suite comment utiliser ce nouveau concept pour imiter la réponse d'une requête.

Commençons déjà par imprimer le résultat d'une requête dans notre console afin de garder en tête le code à renvoyer.

Pour rappel, voici le code de notre fichier  download_agents.py  :

#! /usr/bin/env python
import argparse
import json
import time
import urllib.error
import urllib.request

def get_agents(count):
    # le parse
    agents = []
    while len(agents) < count:
        if agents:
            # Wait one second between every request
            time.sleep(1)

        request_count = min(count - len(agents), 500)
        try:
            response = urllib.request.urlopen("http://pplapi.com/batch/{}/sample.json".format(request_count))
            print(response)
            agents += json.loads(response.read().decode("utf8"))
        except urllib.error.HTTPError:
            print("Too many requests, sleeping 10s ({} agents)".format(len(agents)))
            time.sleep(10)
    return agents

def parse_args(args=None):
    parser = argparse.ArgumentParser(description="Download agents from pplapi.com")
    parser.add_argument("-c", "--count", type=int, default=10, help="Number of agents to download.")
    parser.add_argument("-d", "--dest", help="Destination file. If absent, will print to stdout")
    return parser.parse_args(args)

def main(command_line_arguments=None):
    args = parse_args(command_line_arguments)

    agents = get_agents(args.count)

    result = json.dumps(agents, indent=2, sort_keys=True)
    if args.dest:
        pass
        with open(args.dest, 'w') as out_f:
            out_f.write(result)
    else:
        print(result)


if __name__ == "__main__":
    main()

Lançons la commande  python download_agents.py --count 1

Voici ce qui est imprimé :

<http.client.HTTPResponse object at 0x10636d3c8>
[
  {
    "age": 27,
    "agreeableness": -1.330478164740173,
    "conscientiousness": -0.13936508775709072,
    "country_name": "Belarus",
    "country_tld": "by",
    "date_of_birth": "1990-03-29",
    "extraversion": -0.7695016063711204,
    "id": 6864578729,
    "id_str": "bSI-dKD",
    "income": 14798,
    "internet": true,
    "language": "Belarusian",
    "latitude": 52.98282724600774,
    "longitude": 28.003545708677223,
    "neuroticism": 0.4034048869311393,
    "openness": 0.4024561283710326,
    "religion": "other",
    "sex": "Female"
  }
]

Comment allons-nous faire ? Retournons dans notre fichier  test_download_agents.py.

Créons une nouvelle fonction pour ce test et indiquons le retour souhaité :

def test_http_return():
    results = [{
            "age": 84,
            "agreeableness": 0.74
          }
        ]

Pourquoi ne pas indiquer toutes les informations reçues ?

Il n'est pas nécessaire de tout indiquer car nous n'en aurons pas besoin par la suite. Nous souhaitons simplement tester que la fonction  get_agents()  renvoie bien une liste contenant du JSON. Dans ce cas, avoir moins de code peut être préférable car cela rend nos tests plus faciles à lire.

Utilisons à présent un helper de Pytest pour imiter le retour souhaité. En lisant la documentation, vous découvrirez trois points :

  • notre fonction test doit utiliser le helper   monkeypatch

  • nous devons définir une fonction  mockreturn(obj)  qui prendra en paramètre l'objet sur lequel nous allons appeler la méthode

  • nous pouvons utiliser la méthode  monkeypatch.setattr()  pour changer la valeur renvoyée.

Un exemple vaut mieux que mille mots ! Voyons comment cela se traduit dans notre projet :

import program.download_agents as script

import urllib.request

from io import BytesIO
import json

def test_http_return(monkeypatch):
    results = [{
            "age": 84,
            "agreeableness": 0.74
          }
        ]

    def mockreturn(request):
        return results

    monkeypatch.setattr(urllib.request, 'urlopen', mockreturn)
    assert script.get_agents(1) == results

Cela paraît plutôt bien !

Mais si nous lançons le test... Il échoue ! Pourquoi ? Car  results  est de type  list  et que nous ne pouvons pas exécuter la méthode  read()  sur un objet de ce type.

Utilisons la classe  BytesIO  pour créer un objet sur lequel nous pourrons appeler la méthode  read().

Enfin, puisque la requête renvoie du json encodé et que nous le décodons, il nous faut imiter une réponse en json et encodée !

Changeons tout de suite le retour :

import program.download_agents as script

import urllib.request

from io import BytesIO
import json

def test_http_return(monkeypatch):
    results = [{
            "age": 84,
            "agreeableness": 0.74
          }
        ]

    def mockreturn(request):
        return BytesIO(json.dumps(results).encode())

    monkeypatch.setattr(urllib.request, 'urlopen', mockreturn)
    assert script.get_agents(1) == results

Bravo ! Cela fonctionne !

Imiter l'écriture dans un fichier

Nous allons ensuite tester que notre fonction  main()  écrit bien les données souhaitées dans un fichier.

Afin de ne pas créer et supprimer des fichiers à chaque lancement de test dans notre dossier de travail, nous allons utiliser le helper  tmpdir  de Pytest. Celui-ci nous permet de créer un nouveau fichier dans un répertoire temporaire. A la fin du test, Pytest le supprimera pour nous.

p = tmpdir.mkdir("program").join("agents.json")

Cette instruction crée un dossier  program  et un fichier  agents.json  à l'intérieur.

Puis nous exécutons notre fonction  main()  en passant le répertoire en argument :

script.main(["--dest", str(p), "--count", "1"])

Enfin, nous testons que le contenu du répertoire correspond bien au résultat de la fonction  get_agents():

local_res = json.load(open(p))
assert local_res == script.get_agents(1)

 

Notre test final ressemble donc à ceci :

def test_http_return(tmpdir, monkeypatch):
    results = [{
            "age": 84,
            "agreeableness": 0.74
          }
        ]

    def mockreturn(request):
        return BytesIO(json.dumps(results).encode())

    monkeypatch.setattr(urllib.request, 'urlopen', mockreturn)

    p = tmpdir.mkdir("program").join("agents.json")

    # run script
    script.main(["--dest", str(p), "--count", "1"])

    local_res = json.load(open(p))
    assert local_res == script.get_agents(1)

Pytest propose d'autres fonctionnalités très intéressantes ! Découvrez-les dans sa documentation sur les Fixtures.

Code du chapitre

Retrouvez l'intégralité du code de ce chapitre sur Github ! 

 

Exemple de certificat de réussite
Exemple de certificat de réussite