1. 始めに
python3で実装すると、モックテストをしたくなります。モックを使って、比較的簡単にテストできるからです。
問題はネットワーク系テスト
モックテストで問題になるのは、外部に依存するテストの場合です。
特にネットワークはテストする場所で決まってしまいます。
しかも私の場合、仕事ではxmlrpcを使うことが比較的多く、REST APIの手法が使えないんですよね。。
mock.patchは「patchしたライブラリ」をテストしている感。。
xmlrpcライブラリ自体をmock.patchする方法もありますが、それをすると、ほぼ間違いなく、「mock.patchしたライブラリ」をテストすることになります。
テスト用に作ったライブラリをテストするのは、本末転倒です。
それは避けたい、、
2. VCR.py – テスト用通信ライブラリ
ということで、最近はVCR.pyを使っています。
元々の出自はRubyのVCRライブラリだそうです。
何をするライブラリかというと、アクセスした最初は、ネットワーク通信のデータをそのまま丸ごと、YAMLファイルに格納してくれます。
そして、同じURLであれば、格納したYAMLファイルを使用して、
あたかもネットワーク通信しているかのように再現できます。
YAMLファイルを使用している場合は、そのURLで実際にアクセスできる必要はありません。最初だけアクセスすればいいです。
3. VCR.pyの使い方
言葉で説明するより、実際に見たほうが早いです。
以下で使用したコードはここにアップロードしてあります。
3.1 準備
今回はクライアント側のライブラリをテストする感じで、VCR.pyを使ってみます。構成的には以下のようになります。
テストコード -> クライアント -> (VCR.py) -> テスト用サーバ(server.py)
テスト用サーバ
テスト用のサーパは以下です。
tests/server/server.py
from datetime import datetime from xmlrpc.client import DateTime from xmlrpc.server import SimpleXMLRPCServer def hello(message): # Heloo {}返却 return 'Hello {}.'.format(message) def time_result(): # 時刻返却 return DateTime(datetime.now()) server = SimpleXMLRPCServer(('localhost', 8889)) server.register_function(hello, 'hello') server.register_function(time_result, 'time_result') server.register_introspection_functions() print("start server") server.serve_forever()
以下のように起動します。
$ pipenv run python3 tests/server/server.py
クライアント
テスト対象のクライアントは以下です。
lib/client.py
from datetime import datetime from xmlrpc.client import ServerProxy def xmlrpc_client_hello(): proxy = ServerProxy('http://localhost:8889/') l = proxy.system.listMethods() hello_message = proxy.hello('World') return dict(list=l, hello_message=hello_message) def xmlrpc_client_time_result(): proxy = ServerProxy('http://localhost:8889/') return proxy.time_result()
単にXMLRPCを呼び出しているだけです。
VCR.pyインストール
py.test経由で使用するので、以下をインストールします。
$ pipenv install pytest --dev $ pipenv install pytest-vcr --dev $ pipenv install vcrpy --dev $ pipenv install vcrpy-unittest --dev
テスト方法
tests/test_client.pyは以下にします。
import os from pathlib import Path import sys from vcr_unittest import VCRTestCase import pytest root_dir = Path(__file__).resolve().parent sys.path.append( str(root_dir.parent) ) from lib.client import xmlrpc_client_hello, xmlrpc_client_time_result class TestXMLRPC_Client(VCRTestCase): @pytest.mark.vcr() def test_client_hello(self): result = xmlrpc_client_hello() self.assertNotEqual(result, None) @pytest.mark.vcr() def test_client_time_result(self): result = xmlrpc_client_time_result() # self.assertNotEqual(result, None)
重要なのは、以下の所です。
from vcr_unittest import VCRTestCase class TestXMLRPC_Client(VCRTestCase): @pytest.mark.vcr()
VCRTestCaseをインポートして、テストクラスに継承します。そして、実際にVCRを使用するところに
@pytest.mark.vcr()
のデコレーターを設定します。
テストは以下で起動します。
$ pipenv run py.test tests/test_client.py
まずは、テスト用サーバを起動せずに、テストを起動してみます。
当然ながら、接続できないというエラーが出ます。
E ConnectionRefusedError: [Errno 111] Connection refused
起動してから、テストを起動すると成功します。
$ pipenv run py.test tests/test_client.py ========================================================================================== test session starts =========================================================================================== platform linux -- Python 3.6.8, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 rootdir: /home/tanino/script-plactice/practice_the_script/python/vcr plugins: vcr-1.0.2 collected 2 items tests/test_client.py .. [100%] =========================================================================================== 2 passed in 0.04s ============================================================================================
一度成功すると、テスト用サーバを止めても、上記と同じく成功します。
保存されたYAMLファイル
testsディレクトリにcassettesディレクトリが自動的に作成されます。
$ ls -l tests/cassettes/TestXMLRPC_Client.test_client_* -rw-rw-r-- 1 tanino tanino 2104 11月 9 17:39 tests/cassettes/TestXMLRPC_Client.test_client_hello.yaml -rw-rw-r-- 1 tanino tanino 905 11月 9 17:39 tests/cassettes/TestXMLRPC_Client.test_client_time_result.yaml
ファイル名はテストコードの関数名でデータが作成されます。
ファイル内容は以下のようになります。
... method: POST uri: http://localhost:8889/ response: body: string: '<?xml version=''1.0''?> <methodResponse> <params> <param> <value><dateTime.iso8601>20191109T17:39:43</dateTime.iso8601></value> </param> </params> </methodResponse> ' headers: Content-length: - '163' Content-type: - text/xml Date: - Sat, 09 Nov 2019 08:39:43 GMT Server: - BaseHTTP/0.6 Python/3.6.8 status: code: 200 message: OK version: 1
この場合、時刻が固定されるので、その意味でテストはしやすいかもしれません。ただし時刻だけが問題なら freezegun を使った方が簡単だと思います。
使い方のコツ
VCR.pyを使う場合は、まずはテストコードを成功させてしまう方がいいと思います。
失敗するとデータが途中になってしまい、その次に成功させても、意味がなくなってしまいます。
なお、データの取り方を失敗した場合は、素直にYAMLファイルを消してからもう一度やったほうがいいようです。
ということで上記のテストコードでは、とりあえずテストを通してしまってます。本来なら、もうすこしテストを充実させてた方がいいでしょう。
参考
- XMLRPCの使い方
https://www.python-izm.com/advanced/xmlrpc/