概要
Pythonでテストといえば、unittestやpytestが有名ですが、unittestやpytestを使わないやり方、unittestとpytestの違いなどを解説していきたいと思います。まず、テストするにあたり、モックのための前提知識として、再宣言について解説し、次に実際にテストの解説をしていきます。
この記事を読むと以下のことが理解できるようになります。
初心者の方やPythonのテストの仕組みについて知りたい方におすすめです。
再宣言
PythonではGoやJavaなどの言語とは異なり、再宣言が可能です。
a = 1
a = 1 # エラーにならない
他の言語では以下のようにコンパイルエラーになります。(以下はGoの例)
func main() {
a := 10
a := "Hello" // コンパイルエラー
fmt.Println(a)
}
また、関数も同様に再宣言できます。
def hoge():
return 1
def hoge2():
return 99
hoge = hoge2
print(hoge()) # 99
再宣言ができるということは、実装を置き換えたり、テストでモックすることが可能になります。
テストのディレクトリ
まず、今回は以下のディレクトリ構想でテストを行います。app
配下にソースコード、tests
配下にテストを書いていきます。
.
├── app
│ ├── sample1.py
│ └── __init__.py
└── tests // テストディレクトリ
├── __init__.py
└── test_sample1.py
ライブラリを使わずにテストする
以下のコードのfoo()
をテストするとします。
def foo():
return hoge() + 1
def hoge():
return 1
foo() # 2
foo()
をテストするにはhoge()
をモックする必要があります。先ほどの再宣言によってhoge()
をモックしていきます。
from app import sample1
def mock_hoge():
return 99
def test_foo():
sample1.hoge = mock_hoge
assert sample1.foo() == 100 # 成功
次にクラスの場合をapp/sample2.pyに実装していきます。
class Example:
def foo(self):
return self.hoge() + 1
def hoge(self):
return 1
print(Example().foo()) # 2
クラスの場合も関数とほぼ変わりません。(ちなみに、import が from app import sample2
になるとExample().foo()
がsample2.Example().foo()
にする必要があります。
from app.sample2 import Example
def test_foo():
Example.hoge = mock_hoge
assert Example().foo() == 100 # 成功
def mock_hoge(self):
return 99
モックの問題点
上記のライブラリを使わない方法だと、mock対象の関数などが増えた際に、一つずつ自分で書くと、大変です。
また、モックする前に前もってコピーしておかないと、元の実装戻すことができないという問題が出てきます。
unittestやpytestなどのライブラリを使用することでこれらの問題が解決します。
unittest
標準ライブラリなので、インストールの必要がありません。
import unittest
from app.sample2 import Example
class TestSample2(unittest.TestCase):
def test_func(self):
expected = 2
actual = Example().foo()
self.assertEqual(expected, actual)
if __name__ == "__main__":
unittest.main()
MagicMock
unittest.mockのMagicMockを使ってモックしていきます。
import unittest
from unittest.mock import MagicMock
from app.sample2 import Example
class TestSample2(unittest.TestCase):
def test_func(self):
Example.hoge = MagicMock(return_value=99)
expected = 100
actual = Example().foo()
self.assertEqual(expected, actual)
Example.hoge.assert_called_once()
if __name__ == "__main__":
unittest.main()
さて、ここでassert_called_once_with()
という指定した関数で一度だけ呼ばれたことを検証する関数を使っています。MagicMockを使用すると使用することができます
例えば、hoge()
をモックしたmock_hoge()
の実装が以下のようになっていたら
from app.sample2 import Example
class MockSample2:
def __init__(self):
self.count = 0
def mock_hoge(self):
self.count += 1
return 99
def assert_called_once_with(self):
if self.count:
return True
return False
def test_sample2():
mock = MockSample2()
Example.hoge = mock.mock_hoge
assert 100 == Example().foo()
assert mock.assert_called_once_with()
上記のようMockSample2を実装すると、MagicMockのassert_called_once_with()
と同じ動作をするassert_called_once_with()
を作成することができます。MagicMockには、assert_called_once_with()
の他にも色々な関数が実装されております。
メソッド名 | 説明 |
---|---|
assert_called_with() | 最後に実行したモックの引数を検証 |
assert_called_once_with() | 指定した引数で一度だけ呼ばれたか検証 |
assert_any_call() | 指定した引数で一度でも呼ばれたか検証 |
assert_has_calls() | 順番通りに呼ばれたか検証 |
side_effect() | モックの返り値を設定 |
side_effect() | モックの返り値を設定(関数や例外、ランダムな値を返すとき) |
patcherでモックを戻す
withを使ったやり方
withだとスコープを自由に変えることができます。
class TestSample2(unittest.TestCase):
def test_func(self):
with patch("app.sample2.Example.hoge") as mock_hoge:
mock_hoge.return_value = 99
self.assertEqual(100, Example().foo())
self.assertEqual(2, Example().foo())
if __name__ == "__main__":
unittest.main()
デコレータを使ったやり方
デコレータで書くこともできます。
mockのスコープが1つの関数となります。
class TestSample2(unittest.TestCase):
@patch("app.sample2.Example.hoge")
def test_sample2(self, mock_hoge):
mock_hoge.return_value = 99
self.assertEqual(100, Example().foo())
def test_fuck(self):
self.assertEqual(2, Example().foo())
if __name__ == "__main__":
unittest.main()
pytest
pytestです。unittestよりPythonらしい書き方で、よりシンプルに書けます。ただし、インストールが必要となります。
from app.sample2 import Example
def test_func_main(mocker):
assert 2 == Example().foo()
モック
下記のpytest-mock以外にunittestのmockと同じようにMagicMockが使える。
pytest-mock
pytest-mockは unittest のmockをフォークしたもの。使用するにはインストールが必要。
フィクスチャを使えて、unittestのmockよりシンプルにかけます。
from app.sample2 import Example
def test_func_main(mocker):
mocker.patch("app.sample2.Example.hoge", return_value=99)
assert 100 == Example().foo()
カバレッジや並列実行
pytestでは、カバレッジを出すpytest-cov
やテストの並列実行を可能にするpython-xdist
といったライブラリがあるので特におすすめです!
pytest-covでカバレッジを出力しているときは、デバッグができないので注意