Various APIs are published on the Web, and the number of services and programs that cooperate with them is increasing year by year. Along with that, I think that the opportunities to create programs and services that use such APIs are also increasing.
Let's create a simple class that actually gets information from WebAPI and returns the extraction result.
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
The above is a class to get information from the fake Web API below.
http://jsonplaceholder.typicode.com/
This time, we will test get, the method of the above class. This is a method to extract elements that match specific conditions from the above API.
Define the test code to test this method as follows: In this test, we are testing whether the acquisition result of resource'todos' can be narrowed down by a specific title. (Target resource): 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)
I will actually run it.
python -m unittest test_sample_api_client.py
----------------------------------------------------------------------
Ran 1 test in 0.311s
OK
In this way, the test passed successfully. On the other hand, changes in the state of the API server can cause the test to fail. For example, if the communication path to the API server fails, the test execution result will be the following error.
======================================================================
ERROR: test_get_todo_by_title (tests.test_sample_api_client.TestSampleApiClient)
----------------------------------------------------------------------
Traceback (most recent call last):
~abridgement~
OSError: [WinError 10065]An attempt was made to perform a socket operation on an unreachable host.
----------------------------------------------------------------------
Ran 1 test in 21.026s
FAILED (errors=1)
A unit test confirms the validity of a program as a single unit, but such a test implementation is affected by the connection destination (API server) and communication path. Besides, if another user posts a todo with the same title, the number of hits will be 2, and the test will fail even though the implementation itself is normal.
A common way to get around this problem is to create a mock, replace the submodule with the mock, and always fix the return value. (Details are omitted in this article) However, when creating a mock, it is necessary to define and manage the return value of the lower module for each API or resource to be linked, and it becomes a difficult task as the number of links increases.
Although the introduction has become long, I will introduce a library called "vcrpy" as a means to solve this problem. By using this library, HTTP requests / responses made to the API server can be recorded in a file and played back, saving the trouble of creating a mock.
First, install the vcrpy module in your environment with the following command.
pip install vcrpy
Then rewrite the test code to use vcrpy.
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)
There are two major changes:
Only this. If you run the test in this state, the HTTP request / response contents will be recorded as a file in the directory specified in the VCR option on the 8th line.
If you run this test again, there will be no communication to the API server and instead the HTTP response will be read from this file. In fact, try disconnecting the network again and running the test again.
----------------------------------------------------------------------
Ran 1 test in 0.012s
OK
In this way, by using vcrpy, it is possible to reduce the influence of the state of the opposite device (API server) in the unit test. It's easy to implement, so please give it a try.
Recommended Posts