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/
