Django + pytestでテストの初期化や後始末を書く場合、
- django.test.TestCaseを使った、
setUp()
やtearDown()
- pytestを使った、classic xUnitスタイルの
setup_xxx()
やteardown_xxx()
- pytestを使った、@fixtureの
scope
などのテストフィクスチャが使えます。
今回、それぞれがどのタイミングで動作するのか知りたくなったため、実際に試してみることにしました。
なお、pytest全体については以下が参考になりました。
[Python] 初中級者のためのpytest入門 - くろのて
目次
- 環境
- django.test.TestCaseのsetUp/tearDownで書く
- pytestのxunitスタイルで書く
- pytestのfixtureで書く
- 今までのパターンを一つのテストクラスに書いてみる
- ソースコード
環境
Djangoアプリの事前準備は以下の通りです。
>virtualenv -p c:\python35-32\python.exe env >env\Scripts\activate (env) >pip install django pytest-django (env) >django-admin startproject myproject . (env) >python manage.py startapp myapp
django.test.TestCaseのsetUp/tearDownで書く
django.test.TestCase
はPythonのunittest.TestCase
のサブクラスであるため、setUpなどが使えます。
26.4.8.1. テストクラス - 26.4. unittest — ユニットテストフレームワーク — Python 3.5.1 ドキュメント
なお、動作中の関数・メソッド名を取得するため、以下を参考にして実装しました。
実行中の関数・メソッド名を取得したい - Qiita
# myapp/tests/test_unittest_style.py from django.test import TestCase import sys class Test_UnittestStyle(TestCase): @classmethod def setUpClass(cls): print('unittest style - setup > {}'.format(sys._getframe().f_code.co_name)) @classmethod def tearDownClass(cls): print('unittest style - teardown > {}'.format(sys._getframe().f_code.co_name)) def setUp(self): print('unittest style - setup > {}'.format(sys._getframe().f_code.co_name)) def tearDown(self): print('unittest style - teardown > {}'.format(sys._getframe().f_code.co_name)) # test method def testSpam(self): print('unittest style - setup > [{}]'.format(sys._getframe().f_code.co_name)) assert True def testHam(self): print('unittest style - setup > [{}]'.format(sys._getframe().f_code.co_name)) assert True
実行結果は以下の通りです*1。
>py.test myapp/tests/test_unittest_style.py -s ... myapp\tests\test_unittest_style.py unittest style - setup > setUpClass unittest style - setup > setUp unittest style - setup > [testHam] unittest style - teardown > tearDown . unittest style - setup > setUp unittest style - setup > [testSpam] unittest style - teardown > tearDown . unittest style - teardown > tearDownClass
pytestのxunitスタイルで書く
pytestでは、classic xunit-styleで書けるよう、setup_xxxやteardown_xxxが用意されています。
classic xunit-style setup - pytest
# myapp/tests/test_classic_xunit_style.py from django.test import TestCase import sys def setup_module(module): print('classic xunit style - setup > {}'.format(sys._getframe().f_code.co_name)) def teardown_module(module): print('classic xunit style - teardown > {}'.format(sys._getframe().f_code.co_name)) def setup_function(function): print('classic xunit style - setup > {}'.format(sys._getframe().f_code.co_name)) def teardown_function(function): print('classic xunit style - teardown > {}'.format(sys._getframe().f_code.co_name)) class Test_ClassicXunitStyle(TestCase): @classmethod def setup_class(cls): print('classic xunit style - setup > {}'.format(sys._getframe().f_code.co_name)) @classmethod def teardown_class(cls): print('classic xunit style - teardown > {}'.format(sys._getframe().f_code.co_name)) def setup_method(self, method): print('classic xunit style - setup > {}'.format(sys._getframe().f_code.co_name)) def teardown_method(self, method): print('classic xunit style - teardown > {}'.format(sys._getframe().f_code.co_name)) # test method def testSpam(self): print('classic xunit style > [{}]'.format(sys._getframe().f_code.co_name)) assert True def testHam(self): print('classic xunit style > [{}]'.format(sys._getframe().f_code.co_name)) assert True
実行結果は以下の通りです。今回の場合書き方が悪いのか、setup_function()
は実行されませんでした。
(env) >py.test myapp/tests/test_classic_xunit_style.py -s ... myapp\tests\test_classic_xunit_style.py classic xunit style - setup > setup_module classic xunit style - setup > setup_class classic xunit style - setup > setup_method classic xunit style > [testHam] . classic xunit style - teardown > teardown_method classic xunit style - setup > setup_method classic xunit style > [testSpam] . classic xunit style - teardown > teardown_method classic xunit style - teardown > teardown_class classic xunit style - teardown > teardown_module
pytestのfixtureで書く
pytestのfixtureでは、scope
パラメータを使うことで、xUnitスタイルのsetup/teardownと同様のことができます。
- Autouse fixtures (xUnit setup on steroids) - pytest fixtures: explicit, modular, scalable
- fixtures and requests - Pytest API and builtin fixtures
fixtureを使う場合、
- setup相当は、デコレータを使ったメソッドに記述
- teardown相当は、ファイナライザに追加
として実装します。
また、今回はautouse=True
としてfixtureを自動的に使うようにしています。
Autouse fixtures (xUnit setup on steroids) - pytest fixtures: explicit, modular, scalable
# myapp/tests/test_fixture_style.py from django.test import TestCase import pytest import sys class Test_PytestFixtureStyle(TestCase): @pytest.fixture(autouse=True) def scope_default(self, request): print('fixture style - setup > {}'.format(sys._getframe().f_code.co_name)) def fin_scope_default(): print('fixture style - teardown > {}'.format(sys._getframe().f_code.co_name)) request.addfinalizer(fin_scope_default) @pytest.fixture(autouse=True, scope='function') def scope_function(self, request): print('fixture style - setup > {}'.format(sys._getframe().f_code.co_name)) def fin_scope_function(): print('fixture style - teardown > {}'.format(sys._getframe().f_code.co_name)) request.addfinalizer(fin_scope_function) @pytest.fixture(autouse=True, scope='class') def scope_class(self, request): print('fixture style - setup > {}'.format(sys._getframe().f_code.co_name)) def fin_scope_class(): print('fixture style - teardown > {}'.format(sys._getframe().f_code.co_name)) request.addfinalizer(fin_scope_class) @pytest.fixture(autouse=True, scope='module') def scope_module(self, request): print('fixture style - setup > {}'.format(sys._getframe().f_code.co_name)) def fin_scope_module(): print('fixture style - teardown > {}'.format(sys._getframe().f_code.co_name)) request.addfinalizer(fin_scope_module) @pytest.fixture(autouse=True, scope='session') def scope_session(self, request): print('fixture style - setup > {}'.format(sys._getframe().f_code.co_name)) def fin_scope_session(): print('fixture style - teardown > {}'.format(sys._getframe().f_code.co_name)) request.addfinalizer(fin_scope_session) # test method def testSpam(self): print('fixture style - setup > [{}]'.format(sys._getframe().f_code.co_name)) assert True def testHam(self): print('fixture style - setup > [{}]'.format(sys._getframe().f_code.co_name)) assert True
実行結果は以下の通りです。
(env) >py.test myapp/tests/test_fixture_style.py -s ... myapp\tests\test_fixture_style.py fixture style - setup > scope_session fixture style - setup > scope_module fixture style - setup > scope_class fixture style - setup > scope_default fixture style - setup > scope_function fixture style - setup > [testHam] . fixture style - teardown > fin_scope_function fixture style - teardown > fin_scope_default fixture style - setup > scope_default fixture style - setup > scope_function fixture style - setup > [testSpam] . fixture style - teardown > fin_scope_function fixture style - teardown > fin_scope_default fixture style - teardown > fin_scope_class fixture style - teardown > fin_scope_module fixture style - teardown > fin_scope_session
今までのパターンを一つのテストクラスに書いてみる
setUp/tearDown/fixtureをすべて1つのテストクラスに実装してみて、動作順を確認します。ソースコードはtest_mix_style.pyです。
実行結果は以下の通りです。
(env) >py.test myapp/tests/test_mix_style.py -s ... myapp\tests\test_mix_style.py mix style (classic xunit) - setup > setup_class mix style (classic xunit) - setup > setup_method mix style (fixture) - setup > scope_session mix style (fixture) - setup > scope_module mix style (unittest) - setup > setUpClass mix style (fixture) - setup > scope_class mix style (fixture) - setup > scope_default mix style (fixture) - setup > scope_function mix style (unittest) - setup > setUp mix style > [testHam] mix style (unittest) - teardown > tearDown . mix style (fixture) - teardown > fin_scope_function mix style (fixture) - teardown > fin_scope_default mix style (classic xunit) - teardown > teardown_method mix style (classic xunit) - setup > setup_method mix style (fixture) - setup > scope_default mix style (fixture) - setup > scope_function mix style (unittest) - setup > setUp mix style > [testSpam] mix style (unittest) - teardown > tearDown . mix style (fixture) - teardown > fin_scope_function mix style (fixture) - teardown > fin_scope_default mix style (classic xunit) - teardown > teardown_method mix style (fixture) - teardown > fin_scope_class mix style (unittest) - teardown > tearDownClass mix style (classic xunit) - teardown > teardown_class mix style (fixture) - teardown > fin_scope_module mix style (fixture) - teardown > fin_scope_session
ちなみに、今まではテストファイルを一つだけ実行していましたが、複数のテストファイルを同時に実行すると
(env) >py.test -s ... myapp\tests\test_fixture_style.py fixture style - setup > scope_session fixture style - setup > scope_module ... fixture style - teardown > fin_scope_module ... myapp\tests\test_unittest_style.py unittest style - setup > setUpClass ... unittest style - teardown > tearDownClass fixture style - teardown > fin_scope_session
scope=session
であるfixtureのteardownが、一番最後に実行されました。
ソースコード
GitHubに上げました。
thinkAmi-sandbox/Django_pytest_setup_teardown_fixture-sample
*1:見やすくするため、一部に改行を入れています。以降も同様です。