Pythonにて、「メソッドを差し替え、テスト対象オブジェクトの属性を更新する」テストコードを作成する機会があったため、メモを残します。
なお、良いタイトルが思い浮かびませんでしたので、mock.object(autospect=True)
のサンプルとして考えてください…
目次
環境
- Python 3.6.0
- unittest.mock.patch.objectを使用
- pytest 3.0.6
- テストランナーとして使用
状況
こんなテスト対象コードがありました。
class Target(object): def target_method(self): self.can_print = False # この中でself.can_printを更新しているが、戻り値は何もない self.validate() if self.can_print: return 'OK' return 'NG' def validate(self): is_ok = False # 複雑な処理の結果、is_okの値を変えている if is_ok: self.can_print = True
このコードの対してテストを書きますが、
- 属性
self.can_print
は、メソッド内で初期化しているため、外部からデータを与えられない validate()
メソッドは、内部でデータベースなどで複雑な処理をしているため、is_ok=True
となるデータを用意できない
のため、validate()メソッドをモックに差し替えたいと考えています。
そこで、
class Test_Target(object): def test_can_not_patch(self): def validate_mock(self): self.can_print = True with patch.object(Target, 'validate', side_effect=validate_mock): sut = Target() actual = sut.target_method() assert actual == 'OK'
と、patch.object()の引数side_effect
を使って、validateメソッドをvalidate_mockメソッドに差し替えようと考えました。
しかし、これではvalidate_mock()
の引数の数が合わず、テストを実行するとエラーになります。
# > ret_val = effect(*args, **kwargs) # E TypeError: validate_mock() missing 1 required positional argument: 'self'
対応
unittest.mock.patch.object()
の引数autospec
を使います。
autospecは、
autospec は mock の API を元のオブジェクト (spec) に制限しますが、再帰的に適用される (lazy に実装されている) ので、 mock の属性も spec の属性と同じ API だけを持つようになります。さらに、 mock された関数/メソッドは元と同じシグネチャを持ち、正しくない引数で呼び出されると TypeError を発生させます。
(中略)
patch() か patch.object() に autospec=True を渡すか、 create_autospec() 関数を使って spec をもとに mock を作ることができます。 patch() の引数に autospec=True を渡した場合、置換対象のオブジェクトが spec オブジェクトとして利用されます。 spec は遅延処理される (mock の属性にアクセスされた時に spec が生成される) ので、非常に複雑だったり深くネストしたオブジェクト (例えばモジュールをインポートするモジュールをインポートするモジュール) に対しても大きなパフォーマンスの問題なしに autospec を使うことができます。
26.5.5.8. autospec を使う | 26.5. unittest.mock — モックオブジェクトライブラリ — Python 3.6.0 ドキュメント
とある通り、autospec=True
とすることで、mockの属性と対象オブジェクト(Target.validate()
)の属性が一致します。
- その他参考
これで引数self
が使えるため、オブジェクトの属性self.can_print
を更新できます。
class Test_Target(object): def test_can_patch(self): def validate_mock(self): self.can_print = True with patch.object(Target, 'validate', autospec=True, side_effect=validate_mock): sut = Target() actual = sut.target_method() assert actual == 'OK'
テストもpassしました。
ソースコード
GitHubにあげました。e.g._set_self_attr
ディレクトリが今回のものです。
thinkAmi-sandbox/python_mock-sample