[PYTHON] Erleichtern Sie das Testen von Programmen, die mit APIs mit vcrpy arbeiten

Zielgruppe Leser

Artikelübersicht

Voraussetzungen

Einführung

Verschiedene APIs werden im Web veröffentlicht, und die Anzahl der damit verbundenen Dienste und Programme nimmt von Jahr zu Jahr zu. Gleichzeitig denke ich, dass die Möglichkeiten, Programme und Dienste zu erstellen, die solche APIs verwenden, ebenfalls zunehmen.

Probleme, die dieses Mal gelöst werden müssen

Beispielprogramm

Erstellen wir eine einfache Klasse, die tatsächlich Informationen von der Web-API abruft und das Extraktionsergebnis zurückgibt.

sample_api_client.py


from http import client
import json


class SampleApiClient(object):

    def __init__(self, base_url):
        """
        An Api Client that accesses to resources under specific url.
        :param base_url: root url of API server that contains several resources (String)
        """
        self.base_url = base_url

    def get(self, resource, key='id', value=None):
        """
        An method to get entry of specific resources that satisfy following searching option.
        :param resource: a relative path from base_url that correspond to resource you want to access.(String)
        :param key: an attribute name of resource you want to filter by. default: id (String)
        :param value: a value of an attribute you want to filter by. (String)
        :return: filtered_data: a result of operation. (Array of Dictionary)
        """

        # create connection, and get raw data from API server
        conn = client.HTTPConnection(self.base_url, port=80)

        conn.request(method='GET', url=('/' + resource))
        response = conn.getresponse()

        raw_body = response.read()
        json_body = json.loads(raw_body.decode(encoding='utf-8'))

        # filter if value is specified.
        if value is not None:
            filtered_data = []
            for entry in json_body:
                if entry[key] == value:
                    filtered_data.append(entry)
        else:
            filtered_data = json_body

        return filtered_data


Das Obige ist eine Klasse, die Informationen von der gefälschten Web-API unten erhält.

http://jsonplaceholder.typicode.com/

Dieses Mal werden wir die Methode der obigen Klasse testen. Dies ist eine Methode zum Extrahieren von Elementen, die bestimmten Bedingungen entsprechen, aus der obigen API.

Beispielprogramm-Testcode

Definieren Sie den Testcode zum Testen dieser Methode wie folgt: In diesem Test testen wir, ob das Akquisitionsergebnis von resource'todos durch einen bestimmten Titel eingegrenzt werden kann. (Zielressource): http://jsonplaceholder.typicode.com/todos/

test_sample_api_client.py


from unittest import TestCase
from bin.sample_api_client import SampleApiClient
# Test Case Definition Starts from here.


class TestSampleApiClient(TestCase):

        def test_get_todo_by_title(self):

            client = SampleApiClient(base_url='jsonplaceholder.typicode.com')
            # a free online REST service that produces some fake JSON data.

            result = client.get(resource='todos', key='title', value="delectus aut autem")

            expected = [
                {
                "userId": 1,
                "id": 1,
                "title": "delectus aut autem",
                "completed": False
              }
            ]

            self.assertEqual(expected, result)

Ich werde es tatsächlich ausführen.

Befehl
python -m unittest test_sample_api_client.py
Ausführungsergebnis
----------------------------------------------------------------------
Ran 1 test in 0.311s

OK

Auf diese Weise wurde der Test erfolgreich bestanden. Wenn sich andererseits der Status des API-Servers ändert, schlägt der Test möglicherweise fehl. Wenn beispielsweise der Kommunikationspfad zum API-Server fehlschlägt, ist das Ergebnis der Testausführung der folgende Fehler.

======================================================================
ERROR: test_get_todo_by_title (tests.test_sample_api_client.TestSampleApiClient)
----------------------------------------------------------------------
Traceback (most recent call last):
 ~Kürzung~
OSError: [WinError 10065]Es wurde versucht, eine Socket-Operation auf einem nicht erreichbaren Host auszuführen.

----------------------------------------------------------------------
Ran 1 test in 21.026s

FAILED (errors=1)

Problem

Der Komponententest bestätigt die Gültigkeit des Programms als einzelne Einheit, eine solche Testimplementierung wird jedoch vom Verbindungsziel (API-Server) und vom Kommunikationspfad beeinflusst. Wenn ein anderer Benutzer eine Aufgabe mit demselben Titel veröffentlicht, beträgt die Anzahl der Treffer 2, und der Test schlägt fehl, obwohl die Implementierung selbst normal ist.

Eine übliche Methode, um dieses Problem zu umgehen, besteht darin, ein Modell zu erstellen, die zugrunde liegenden Module durch das Modell zu ersetzen und immer einen festen Rückgabewert zu haben. (Details werden in diesem Artikel weggelassen) Beim Erstellen eines Modells ist es jedoch erforderlich, den Rückgabewert des unteren Moduls für jede zu verknüpfende API und Ressource zu definieren und zu verwalten. Mit zunehmender Anzahl von Verknüpfungen wird dies zu einer schwierigen Aufgabe.

Lösung

Obwohl die Einführung lang geworden ist, werde ich eine Bibliothek namens "vcrpy" einführen, um dieses Problem zu lösen. Mithilfe dieser Bibliothek können an den API-Server gesendete HTTP-Anforderungen / -Antworten in einer Datei aufgezeichnet und wiedergegeben werden, wodurch die Erstellung eines Modells erspart wird.

Testcode mit vcrpy

Installieren Sie zunächst das vcrpy-Modul mit dem folgenden Befehl in Ihrer Umgebung.

pip install vcrpy

Schreiben Sie dann den Testcode neu, um vcrpy zu verwenden.

test_sample_api_client_with_vcr.py


from unittest import TestCase
import vcr
from bin.sample_api_client import SampleApiClient

# Instantiate VCR in order to Use VCR in test scenario.

vcr_instance = vcr.VCR(  # Following option is often used.
    cassette_library_dir='vcr/cassettes/',  # A Location to storing VCR Cassettes
    decode_compressed_response=True,  # Store VCR content (HTTP Requests / Responses) as a Plain text.
    serializer='json',  # Store VCR Record as a JSON Data
)

# Test Case Definition Starts from here.


class TestSampleApiClient(TestCase):
        @vcr_instance.use_cassette
        def test_get_todo_by_title(self):

            client = SampleApiClient(base_url='jsonplaceholder.typicode.com')
            # a free online REST service that produces some fake JSON data.

            result = client.get(resource='todos', key='title', value="delectus aut autem")

            expected = [
                {
                "userId": 1,
                "id": 1,
                "title": "delectus aut autem",
                "completed": False
              }
            ]

            self.assertEqual(expected, result)

Es gibt zwei wesentliche Änderungen:

Nur das. Wenn Sie den Test in diesem Status ausführen, wird der Inhalt der HTTP-Anforderung / Antwort als Datei in dem Verzeichnis aufgezeichnet, das in der Option VCR in der 8. Zeile angegeben ist.

Wenn Sie diesen Test erneut ausführen, erfolgt keine Kommunikation mit dem API-Server. Stattdessen wird die HTTP-Antwort aus dieser Datei gelesen. Versuchen Sie tatsächlich, das Netzwerk erneut zu trennen und den Test erneut auszuführen.

Ausführungsergebnis
----------------------------------------------------------------------
Ran 1 test in 0.012s

OK

Zusammenfassung

Auf diese Weise ist es mit vcrpy möglich, den Einfluss des Status des gegenüberliegenden Geräts (API-Server) im Unit-Test zu reduzieren. Es ist einfach zu implementieren, probieren Sie es also aus.

Recommended Posts

Erleichtern Sie das Testen von Programmen, die mit APIs mit vcrpy arbeiten
Ein Liner, der JSON formatiert, um die Anzeige zu vereinfachen
Erweitern Sie devicetree source include, um das Lesen zu vereinfachen
Fileinput, ein Modul, das das Schreiben von Perl-ähnlichen Filterprogrammen in Python erleichtert
Wenn ich versuche, mit Heroku zu pushen, funktioniert es nicht
Sie, die das Protokoll ausmalen, um es besser sehen zu können
Machen Sie es mit der Syntax einfach
Schließen Sie sich csv an, das von Python-Pandas normalisiert wurde, um die Überprüfung zu vereinfachen
Ich habe Django-Befehle geschrieben, um das Debuggen von Sellerie-Aufgaben zu vereinfachen
[Zaif] Ich habe versucht, den Handel mit virtuellen Währungen mit Python zu vereinfachen