スクリプトのお勉強 技術

Python3 – VCR.py でネットワーク系テストを簡単に作成する

投稿日:

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/

-スクリプトのお勉強, 技術

執筆者:

関連記事

FastAPIでAPIを作ってみる(その1)

勉強として、FastAPIを使用して、実際にAPIを作成し、起動しようと思います。目標としては、本番運用用の設定まで行いたいと思います。 FastAPIとは FastAPIとは、Pythonによる、W …

fastapi + SQLAlchemy で CRUDアプリケーションを作ってみる

概要 勉強用に、PythonでPostgresqlを制御しようと思います。の続きです。 前回でPostgreSQLと、データベース/テーブルまでは用意したので、今回はAPIを作成しようと思います。 実 …

yoyo-migrationsを使ってみる

勉強用に、PythonでPostgresqlを制御しようと思います。 その前に、Postgresqlの設定と、マイグレーションをしようかと思います。まずyoyo-migrationsを使用します。 y …

リモート実家帰りしてみる

このご時世、実家には直接帰れないけど、1月には一応実家帰り的な感じでリモート実家帰りをしようかと思いました。 リモートは大変。。 一応実家には自分で設置したインターネットや無線LANがあるので、Zoo …

Pythonでの勘違い(if A:)

勘違い 小ネタです。 Pythonでは、以下のように書くことができます。 a = [] if a: print(“not empty!”) else: print(“empty!”) 結果は以下になり …