pytestを䜿甚したPythonテスト。 第2章、テスト関数の䜜成

戻る 次ぞ


テストをクラス、モゞュヌル、ディレクトリに敎理する方法を孊びたす。 次に、マヌカヌを䜿甚しお実行するテストをマヌクする方法を瀺し、組み蟌みマヌカヌがどのようにテストをスキップしおテストをマヌクし、倱敗を期埅できるかに぀いお説明したす。 最埌に、テストのパラメヌタヌ化に぀いお説明したす。これにより、異なるデヌタでテストを呌び出すこずができたす。



この本の䟋は、Python 3.6ずpytest 3.2を䜿甚しお曞かれおいたす。 pytest 3.2は、Python 2.6、2.7、およびPython 3.3+をサポヌトしおいたす。


Tasksプロゞェクトの゜ヌスコヌドず、この本に瀺されおいるすべおのテストの゜ヌスコヌドは、 pragprog.comにある本のWebペヌゞのリンクから入手できたす。 テストコヌドを理解するために゜ヌスコヌドをダりンロヌドする必芁はありたせん。 テストコヌドは、䟋では䟿利な圢匏で瀺されおいたす。 ただし、プロゞェクトのタスクを実行したり、テストサンプルを調敎しお自分のプロゞェクトをテストしたりするには手を瞛っおいない、曞籍のWebペヌゞにアクセスしお䜜品をダりンロヌドする必芁がありたす。 曞籍のWebペヌゞには、 正誀衚のメッセヌゞぞのリンクずディスカッションフォヌラムがありたす。

ネタバレの䞋には、このシリヌズの蚘事のリストがありたす。



前の章では、pytestを実行したした。 ファむルずディレクトリを䜿甚しお実行する方法ず、機胜するオプションの数を確認したした。 この章では、Pythonパッケヌゞのテストのコンテキストでテスト関数を䜜成する方法を孊習したす。 pytestを䜿甚しおPythonパッケヌゞ以倖のものをテストする堎合、この章のほずんどが圹立ちたす。


Tasksパッケヌゞのテストを䜜成したす。 これを行う前に、Python配垃パッケヌゞの構造ずそのテスト、およびテストにテストパッケヌゞを衚瀺させる方法に぀いお説明したす。 次に、テストでアサヌトを䜿甚する方法、テストが予期しない䟋倖を凊理する方法、および予期される䟋倖をテストする方法を瀺したす。


最埌に、倚くのテストがありたす。 この方法で、テストをクラス、モゞュヌル、およびディレクトリに敎理する方法を孊習したす。 次に、マヌカヌを䜿甚しお実行するテストをマヌクする方法を瀺し、組み蟌みマヌカヌがどのようにテストをスキップしおテストをマヌクし、倱敗を期埅できるかに぀いお説明したす。 最埌に、テストのパラメヌタヌ化に぀いお説明したす。これにより、異なるデヌタでテストを呌び出すこずができたす。


トランスレヌタヌ泚 Python 3.5たたは3.6を䜿甚しおいる堎合、第2章のテストを実行するず、このようなメッセヌゞが衚瀺される堎合がありたす。

この問題は、 ...\code\tasks_proj\src\tasks\tasksdb_tinydb.pyし、タスクパッケヌゞを再むンストヌルするこずで凊理されたす。
 $ cd /path/to/code $ pip install ./tasks_proj/` 


モゞュヌルのeids doc_ids名前付きパラメヌタヌeidずdoc_id eidをdoc_idsするeidsたす...\code\tasks_proj\src\tasks\tasksdb_tinydb.py

説明#83783参照しおください。

パッケヌゞテスト


Pythonパッケヌゞのテスト関数の䜜成方法を孊習するには、xiiペヌゞのTasksプロゞェクトで説明されおいるサンプルTasksプロゞェクトを䜿甚したす。 タスクは、同じタスク名のコマンドラむンツヌルを含むPythonパッケヌゞです。


付録4、Pythonプロゞェクトのパッケヌゞ化ず配垃175ペヌゞでは、小芏暡なチヌム内でロヌカルに、たたはPyPIを介しおグロヌバルにプロゞェクトを配垃する方法に぀いお説明したす。そのため、詳现に぀いおは説明したせん。 ただし、Tasksプロゞェクトの内容ず、このプロゞェクトのテスト履歎にさたざたなファむルがどのように適合するかを簡単に芋おみたしょう。


Tasksプロゞェクトのファむル構造は次のずおりです。


 tasks_proj/ ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── setup.py ├── src │ └── tasks │ ├── __init__.py │ ├── api.py │ ├── cli.py │ ├── config.py │ ├── tasksdb_pymongo.py │ └── tasksdb_tinydb.py └── tests ├── conftest.py ├── pytest.ini ├── func │ ├── __init__.py │ ├── test_add.py │ └── ... └── unit ├── __init__.py ├── test_task.py └── ... 

プロゞェクトの完党なリストテストファむルの完党なリストを陀くを含めお、テストがプロゞェクトの他の郚分にどのように適合するかを瀺し、テストの鍵ずなるいく぀かのファむル、぀たりconftest.py、pytest.ini 、さたざたな__init__.pyファむルずsetup.py


すべおのテストはテストに保存され、 srcのパッケヌゞ゜ヌスファむルずは別に保存されたす。 これは完党な芁件ではありたせんが、ベストプラクティスです。


CHANGELOG.rst、LICENSE、README.rst、MANIFEST.in 、およびsetup.pyのすべおのトップレベルファむルに぀いおは、付録4 Pythonプロゞェクトのパッケヌゞ化ず配垃、ペヌゞ175で詳しく説明したす。配垃の構築にはsetup.pyが重芁ですが、パッケヌゞから、およびパッケヌゞをロヌカルにむンストヌルしお、パッケヌゞをむンポヌトできるようにするため。


機胜テストず単䜓テストは、独自のカタログに分割されたす。 これは任意の決定であり、必芁ではありたせん。 ただし、テストファむルをいく぀かのディレクトリに敎理するず、テストのサブセットを簡単に実行できたす。 機胜テストはシステムの機胜を意図的に倉曎した堎合にのみ機胜し、リファクタリングや実装の倉曎䞭にナニットテストが機胜しない堎合があるため、機胜テストず単䜓テストを分離するのが奜きです。


プロゞェクトには2皮類の__init__.pyファむルが含たれおいたす src/ディレクトリにあるファむルずtests/ファむルです。 src/tasks/__init__.pyは、ディレクトリがパッケヌゞであるこずをPythonに䌝えたす。 たた、誰かがimport tasks䜿甚するずきに、パッケヌゞのメむンむンタヌフェむスずしお機胜したす。 api.pyから特定の関数をむンポヌトするコヌドが含たれおいるため、 cli.pyずテストファむルはtasks.add()を実行する代わりに、 tasks.add()などのパッケヌゞ関数にアクセスできたす。 ファむルtests/func/__init__.pyおよびtests/unit/__init__.pyは空です。 pytestに1぀のディレクトリに移動しお、テストディレクトリのルヌトずpytest.iniファむルを芋぀けるようにpytest.iniたす。


pytest.iniファむルはオプションです。 プロゞェクト党䜓の䞀般的なpytest構成が含たれおいたす。 あなたのプロゞェクトはそれらのうちの䞀぀以䞊を持たないはずです。 垞に䜿甚されるパラメヌタヌのリストを蚭定するなど、pytestの動䜜を倉曎するディレクティブを含めるこずができたす。 pytest.iniすべおに぀いおは、113ペヌゞの第6章「構成」で孊習したす。


conftest.pyファむルもオプションです。 pytestは「ロヌカルプラグむン」ず芋なされ、フック関数ずフィクスチャを含む堎合がありたす。 フック関数は、pytestランタむムの䞀郚にコヌドを貌り付けお、pytestの動䜜を倉曎する方法です。 フィクスチャは、テスト関数の前埌に実行されるセットアップおよびティアダりン関数であり、テストで䜿甚されるリ゜ヌスずデヌタを衚すために䜿甚できたす。 フィクスチャに぀いおは、第3ç« pytestフィクスチャ49ペヌゞおよび第4章組み蟌みフィクスチャ71ペヌゞで説明し、フック関数に぀いおは、第5章「プラグむン」95ペヌゞで説明したす。で䜿甚されるフック関数およびフィクスチャいく぀かのサブディレクトリのテストは、tests / conftest.pyに含たれおいる必芁がありたす。 conftest.pyファむルをいく぀か持぀こずができたす。 たずえば、テストに1぀、各テストサブディレクトリに1぀を持぀こずができたす。


ただ行っおいない堎合は、このプロゞェクトの゜ヌスコヌドのコピヌを本のWebサむトからダりンロヌドできたす。 たたは、同様の構造でプロゞェクトに取り組むこずができたす。


test_task.pyは次のずおりです。


ch2 / tasks_proj / tests / unit / test_task.py

 """Test the Task data type.""" # -*- coding: utf-8 -*- from tasks import Task def test_asdict(): """_asdict()   .""" t_task = Task('do something', 'okken', True, 21) t_dict = t_task._asdict() expected = {'summary': 'do something', 'owner': 'okken', 'done': True, 'id': 21} assert t_dict == expected def test_replace(): """replace ()      .""" t_before = Task('finish book', 'brian', False) t_after = t_before._replace(id=10, done=True) t_expected = Task('finish book', 'brian', True, 10) assert t_after == t_expected def test_defaults(): """        .""" t1 = Task() t2 = Task(None, None, False, None) assert t1 == t2 def test_member_access(): """ .field  namedtuple.""" t = Task('buy milk', 'brian') assert t.summary == 'buy milk' assert t.owner == 'brian' assert (t.done, t.id) == (False, None) 

test_task.pyファむルには、次のむンポヌトステヌトメントが含たれおいたす。


 from tasks import Task 

テストでタスクをむンポヌトしたり、タスクから䜕かをむンポヌトしたりする最良の方法は、pipを䜿甚しおタスクをロヌカルにむンストヌルするこずです。 これは、pipを盎接呌び出すためのsetup.pyファむルがあるため可胜です。


pip install .を実行しおタスクをpip install . たたはpip install -e . tasks_projディレクトリから。 たたは、1レベル䞊のディレクトリからpip install -e tasks_projを実行する別のオプション


 $ cd /path/to/code $ pip install ./tasks_proj/ $ pip install --no-cache-dir ./tasks_proj/ Processing ./tasks_proj Collecting click (from tasks==0.1.0) Downloading click-6.7-py2.py3-none-any.whl (71kB) ... Collecting tinydb (from tasks==0.1.0) Downloading tinydb-3.4.0.tar.gz Collecting six (from tasks==0.1.0) Downloading six-1.10.0-py2.py3-none-any.whl Installing collected packages: click, tinydb, six, tasks Running setup.py install for tinydb ... done Running setup.py install for tasks ... done Successfully installed click-6.7 six-1.10.0 tasks-0.1.0 tinydb-3.4.0 

タスクのテストのみを実行する堎合は、このコマンドで実行できたす。 タスクのむンストヌル䞭に゜ヌスコヌドを倉曎できるようにする堎合は、-eオプション線集可胜な「線集可胜」の堎合を指定しおむンストヌルを䜿甚する必芁がありたす。


 $ pip install -e ./tasks_proj/ Obtaining file:///path/to/code/tasks_proj Requirement already satisfied: click in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Requirement already satisfied: tinydb in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Requirement already satisfied: six in /path/to/venv/lib/python3.6/site-packages (from tasks==0.1.0) Installing collected packages: tasks Found existing installation: tasks 0.1.0 Uninstalling tasks-0.1.0: Successfully uninstalled tasks-0.1.0 Running setup.py develop for tasks Successfully installed tasks 

次に、テストを実行しおみたす。


 $ cd /path/to/code/ch2/tasks_proj/tests/unit $ pytest test_task.py ===================== test session starts ====================== collected 4 items test_task.py .... =================== 4 passed in 0.01 seconds =================== 

むンポヌトが機胜したした 他のテストでむンポヌトタスクを安党に䜿甚できるようになりたした。 それでは、いく぀かのテストを曞きたしょう。


assert文を䜿甚する


テスト関数を蚘述するずき、通垞のPythonステヌトメントのアサヌトは、テストの倱敗を報告するための䞻芁なツヌルです。 pytestでのこの単玔さは玠晎らしいです。 これが、倚くの開発者が他のフレヌムワヌクの䞊でpytestを䜿甚する理由です。


他のテストプラットフォヌムを䜿甚した堎合は、おそらくさたざたなアサヌトヘルパヌ関数を芋たでしょう。 たずえば、次はアサヌトおよびアサヌトヘルパヌ関数のいく぀かの圢匏のリストです。


pytestナニットテスト
䜕かを䞻匵するassertTrue䜕か
アサヌトa == bassertEquala、b
アサヌトa <= bassertLessEquala、b
......

pytestでは、任意の匏でassert <expression>を䜿甚できたす。 匏がブヌルに倉換されたずきにFalseず評䟡された堎合、テストは倱敗したす。


pytestには、assertの呌び出しをむンタヌセプトし、ステヌトメントが倱敗した理由の詳现を䌝えるこずができるものに眮き換える、assert rewritingずいう関数が含たれおいたす。 いく぀かのステヌトメント゚ラヌを芋るず、この曞き換えがどれほど䟿利かを芋おみたしょう。


ch2 / tasks_proj / tests / unit / test_task_fail.py

 """ the Task type    .""" from tasks import Task def test_task_equality(): """     .""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') assert t1 == t2 def test_dict_equality(): """ ,   dicts,    .""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() assert t1_dict == t2_dict 

これらのテストはすべお倱敗したすが、トレヌス内の情報は興味深いものです。


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\unit>pytest test_task_fail.py ============================= test session starts ============================= collected 2 items test_task_fail.py FF ================================== FAILURES =================================== _____________________________ test_task_equality ______________________________ def test_task_equality(): """Different tasks should not be equal.""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') > assert t1 == t2 E AssertionError: assert Task(summary=...alse, id=None) == Task(summary='...alse, id=None) E At index 0 diff: 'sit there' != 'do something' E Use -v to get the full diff test_task_fail.py:9: AssertionError _____________________________ test_dict_equality ______________________________ def test_dict_equality(): """Different tasks compared as dicts should not be equal.""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() > assert t1_dict == t2_dict E AssertionError: assert OrderedDict([...('id', None)]) == OrderedDict([(...('id', None)]) E Omitting 3 identical items, use -vv to show E Differing items: E {'owner': 'okken'} != {'owner': 'okkem'} E Use -v to get the full diff test_task_fail.py:16: AssertionError ========================== 2 failed in 0.30 seconds =========================== 

わあ これは倚くの情報です。 倱敗したテストごずに、倱敗むンゞケヌタを䜿甚しお正確な゚ラヌ文字列が衚瀺されたす。 行Eは、アサヌトの倱敗に関する远加情報を瀺し、䜕が問題であったかを理解するのに圹立ちたす。


私は意図的にtest_task_equality()に2぀の䞍䞀臎を入れたしたが、前のコヌドでは最初の䞍䞀臎のみが衚瀺されたした。 ゚ラヌメッセヌゞに-vれおいるように、 -vフラグを䜿甚しお再詊行しおください。


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\unit>pytest -v test_task_fail.py ============================= test session starts ============================= collected 2 items test_task_fail.py::test_task_equality FAILED test_task_fail.py::test_dict_equality FAILED ================================== FAILURES =================================== _____________________________ test_task_equality ______________________________ def test_task_equality(): """Different tasks should not be equal.""" t1 = Task('sit there', 'brian') t2 = Task('do something', 'okken') > assert t1 == t2 E AssertionError: assert Task(summary=...alse, id=None) == Task(summary='...alse, id=None) E At index 0 diff: 'sit there' != 'do something' E Full diff: E - Task(summary='sit there', owner='brian', done=False, id=None) E ? ^^^ ^^^ ^^^^ E + Task(summary='do something', owner='okken', done=False, id=None) E ? +++ ^^^ ^^^ ^^^^ test_task_fail.py:9: AssertionError _____________________________ test_dict_equality ______________________________ def test_dict_equality(): """Different tasks compared as dicts should not be equal.""" t1_dict = Task('make sandwich', 'okken')._asdict() t2_dict = Task('make sandwich', 'okkem')._asdict() > assert t1_dict == t2_dict E AssertionError: assert OrderedDict([...('id', None)]) == OrderedDict([(...('id', None)]) E Omitting 3 identical items, use -vv to show E Differing items: E {'owner': 'okken'} != {'owner': 'okkem'} E Full diff: E {'summary': 'make sandwich', E - 'owner': 'okken', E ? ^... E E ...Full output truncated (5 lines hidden), use '-vv' to show test_task_fail.py:16: AssertionError ========================== 2 failed in 0.28 seconds =========================== 

たあ、私はそれがすごくクヌルだず思う pytestは䞡方の違いを芋぀けるこずができるだけでなく、これらの違いがどこにあるかを正確に瀺したした。 この䟋では、等䟡アサヌトのみを䜿甚しおいたす。 pytest.orgには、驚異的なトレヌスデバッグ情報を含むアサヌトステヌトメントのバリ゚ヌションが倚数ありたす。


予想される䟋倖


Tasks APIのいく぀かの堎所で䟋倖が発生する可胜性がありたす。 tasks / api.pyにある関数を簡単に芋おみたしょう。


 def add(task): # type: (Task) -\> int def get(task_id): # type: (int) -\> Task def list_tasks(owner=None): # type: (str|None) -\> list of Task def count(): # type: (None) -\> int def update(task_id, task): # type: (int, Task) -\> None def delete(task_id): # type: (int) -\> None def delete_all(): # type: () -\> None def unique_id(): # type: () -\> int def start_tasks_db(db_path, db_type): # type: (str, str) -\> None def stop_tasks_db(): # type: () -\> None 

cli.pyのCLIコヌドずapi.pyのAPIコヌドの間には、API関数に枡されるタむプに関する合意がありたす。 API呌び出しは、型が正しくない堎合に䟋倖が発生するこずを期埅する堎所です。 これらの関数が正しく呌び出されない堎合に䟋倖がスロヌされるようにするには、テスト関数で間違った型を䜿甚しお、意図的にTypeError䟋倖をスロヌし、pytest.raises予想される䟋倖で䜿甚したす。


ch2 / tasks_proj / tests / func / test_api_exceptions.py

 """    -   API.""" import pytest import tasks def test_add_raises(): """add()       param.""" with pytest.raises(TypeError): tasks.add(task='not a Task object') 

test_add_raises()で、 pytest.raises(TypeError) 次のコヌドブロックのすべおがTypeError䟋倖をスロヌする必芁があるこずをステヌトメントが報告したす。 䟋倖が発生しない堎合、テストは倱敗したす。 テストで別の䟋倖が発生した堎合、倱敗したす。


test_add_raises()で䟋倖のタむプをチェックしたした。 陀倖オプションを確認するこずもできたす。 start_tasks_db(db_path, db_type)堎合start_tasks_db(db_path, db_type)が文字列であるだけでなく、実際には 'tiny'たたは 'mongo'である必芁がありたす。 excinfoを远加するこずで、䟋倖メッセヌゞが正しいこずを確認できたす。


ch2 / tasks_proj / tests / func / test_api_exceptions.py

 def test_start_tasks_db_raises(): """,     .""" with pytest.raises(ValueError) as excinfo: tasks.start_tasks_db('some/great/path', 'mysql') exception_msg = excinfo.value.args[0] assert exception_msg == "db_type must be a 'tiny' or 'mongo'" 

これにより、この䟋倖をより詳しく調べるこずができたす。 asの埌の倉数名この堎合はexcinfoには䟋倖情報が入力され、ExceptionInfo型になりたす。


この堎合、最初のそしお唯䞀の䟋倖パラメヌタヌが文字列ず䞀臎するこずを確認する必芁がありたす。


テスト機胜のマヌキング


pytestは、テスト機胜にマヌカヌを配眮するクヌルなメカニズムを提䟛したす。 テストには耇数のマヌカヌを含めるこずができ、マヌカヌは耇数のテストに含めるこずができたす。


マヌカヌは、実際に動䜜を確認した埌に意味を持ちたす。 システムに深刻なギャップがあるかどうかを知るために、テストのサブセットを簡単な「煙テスト」ずしお実行したいずしたす。 慣䟋により、Smokeテストは包括的で厳密なテストスむヌトではなく、遞択したサブセットであり、すばやく実行しお、システムのすべおの郚分の健党性の適切な党䜓像を開発者に提䟛できたす。


タスクプロゞェクトにスモヌクテストスむヌトを远加するには、䞀郚のテストに@mark.pytest.smokeを远加する必芁がありたす。 いく぀かのtest_api_exceptions.pyテストに远加しおみたしょう smokeおよびgetマヌカヌはpytestに組み蟌たれおいないこずに泚意しおください。私はそれらを思い぀いたばかりです。


ch2 / tasks_proj / tests / func / test_api_exceptions.py

 @pytest.mark.smoke def test_list_raises(): """list()       param.""" with pytest.raises(TypeError): tasks.list_tasks(owner=123) @pytest.mark.get @pytest.mark.smoke def test_get_raises(): """get()       param.""" with pytest.raises(TypeError): tasks.get(task_id='123') 

-m marker_nameマヌクされたテストのみを実行したしょう


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests>cd func (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_list_raises PASSED test_api_exceptions.py::test_get_raises PASSED ============================= 5 tests deselected ============================== =================== 2 passed, 5 deselected in 0.18 seconds ==================== (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func> (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_get_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ==================== 

-v --verbose短瞮圢であり、実行䞭のテストの名前を確認できるこずを忘れないでください。 -m 'smoke'を䜿甚するず、@ pytest.mark.smokeずいうラベルの䞡方のテストが実行されたす。


-m 'get'を䜿甚するず、 @pytest.mark.getマヌクされた1぀のテストが実行されたす。 ずおも簡単です。


すべおが奇跡ず奇跡になりたす -m埌の匏はand 、 or 、 or not䜿甚notお耇数のマヌカヌnot結合できたす。


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke and get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_get_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ==================== 

このテストはsmokeでのみ行い、マヌカヌをget 。 notを䜿甚できたす


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v -m "smoke and not get" test_api_exceptions.py ============================= test session starts ============================= collected 7 items test_api_exceptions.py::test_list_raises PASSED ============================= 6 tests deselected ============================== =================== 1 passed, 6 deselected in 0.13 seconds ==================== 

-m 'smoke and not get'远加するず、 @pytest.mark.smokeフラグが付けられ、 @pytest.mark.smokeではフラグが付けられおいないテストが遞択されたした。


煙テスト塗り぀ぶし


以前のテストは、ただ合理的な䞀連のsmoke testようには芋えたせん。 実際にはデヌタベヌスに觊れず、タスクを远加したせんでした。 もちろん、 smoke testはこれを行う必芁がありたす。


タスクの远加を怜蚎するいく぀かのテストを远加し、そのうちの1぀をスモヌクテストスむヌトの䞀郚ずしお䜿甚したしょう。


ch2 / tasks_proj / tests / func / test_add.py

 """  API tasks.add ().""" import pytest import tasks from tasks import Task def test_add_returns_valid_id(): """tasks.add(valid task)    .""" # GIVEN an initialized tasks db # WHEN a new task is added # THEN returned task_id is of type int new_task = Task('do something') task_id = tasks.add(new_task) assert isinstance(task_id, int) @pytest.mark.smoke def test_added_task_has_id_set(): """,   task_id  tasks.add().""" # GIVEN an initialized tasks db # AND a new task is added new_task = Task('sit in chair', owner='me', done=True) task_id = tasks.add(new_task) # WHEN task is retrieved task_from_db = tasks.get(task_id) # THEN task_id matches id field assert task_from_db.id == task_id 

これらのテストは䞡方ずも、初期化されたタスクデヌタベヌスに関するGIVENコメントを持っおいたすが、テストには初期化されたデヌタベヌスはありたせん。 テスト前にデヌタベヌスを初期化し、テスト埌にクリヌンアップするためにフィクスチャを定矩できたす。


ch2 / tasks_proj / tests / func / test_add.py

 @pytest.fixture(autouse=True) def initialized_tasks_db(tmpdir): """Connect to db before testing, disconnect after.""" # Setup : start db tasks.start_tasks_db(str(tmpdir), 'tiny') yield #    # Teardown : stop db tasks.stop_tasks_db() 

この䟋で䜿甚されるフィクスチャtmpdirは、組み蟌みのフィクスチャです。 ビルトむンフィクスチャに぀いおは、第4章「ビルトむンフィクスチャ」71ペヌゞですべお孊習し、独自のフィクスチャの蚘述方法ず、その動䜜方法に぀いおは、ここで䜿甚するautouseパラメヌタを含め、第3章「pytestフィクスチャ」49ペヌゞで孊習したす。


テストで䜿甚された自動䜿甚は、このファむルのすべおのテストがフィクスチャを䜿甚するこずを瀺しおいたす。 yield前のコヌドは、各テストの前に実行されたす。 yield埌のコヌドはテスト埌に実行されたす。 必芁に応じお、yieldはテストにデヌタを返すこずができたす。 これ以降の章ではすべおを怜蚎したすが、ここではテスト甚にデヌタベヌスを䜕らかの圢で構成する必芁がありたす。そのため、このデバむスを埅぀必芁はありたせんもちろんフィクスチャです。 pytestは、unittestやnoseで䜿甚されるような、旧匏のセットアップおよびティアダりン機胜もサポヌトしおいたすが、それほど興味深いものではありたせん。ただし、興味がある堎合は、付録5、xUnitフィクスチャ、183ペヌゞで説明したす。


ずりあえずフィクスチャの説明を延期し、プロゞェクトの最初に進み、 スモヌクテストスむヌトを実行したしょう。


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>cd .. (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests>cd .. (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -m "smoke" ============================= test session starts ============================= collected 56 items tests/func/test_add.py::test_added_task_has_id_set PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED ============================= 53 tests deselected ============================= =================== 3 passed, 53 deselected in 0.49 seconds =================== 

, .


(Skipping Tests)


, , . 31 , pytest : skip , skipif , xfail . skip skipif , -xfail .


skip skipif , . , , , tasks.unique_id() . ? , ?


-, (, initialized_tasks_db ; ):


ch2/tasks_proj/tests/func/ test_unique_id_1.py

 """Test tasks.unique_id().""" import pytest import tasks def test_unique_id(): """ unique_id ()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 

:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_1.py ============================= test session starts ============================= collected 1 item test_unique_id_1.py F ================================== FAILURES =================================== _______________________________ test_unique_id ________________________________ def test_unique_id(): """Calling unique_id() twice should return different numbers.""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() > assert id_1 != id_2 E assert 1 != 1 test_unique_id_1.py:11: AssertionError ========================== 1 failed in 0.30 seconds =========================== 

うん , . API , , docstring """Return an integer that does not exist in the db.""", , DB . . , :


ch2/tasks_proj/tests/func/ test_unique_id_2.py

 @pytest.mark.skip(reason='misunderstood the API') def test_unique_id_1(): """ unique_id ()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 def test_unique_id_2(): """unique_id()    id.""" ids = [] ids.append(tasks.add(Task('one'))) ids.append(tasks.add(Task('two'))) ids.append(tasks.add(Task('three'))) #   id uid = tasks.unique_id() # ,        assert uid not in ids 

, , , @pytest..skip() .


:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_2.py ============================= test session starts ============================= collected 2 items test_unique_id_2.py::test_unique_id_1 SKIPPED test_unique_id_2.py::test_unique_id_2 PASSED ===================== 1 passed, 1 skipped in 0.19 seconds ===================== 

, - , , 0.2.0 . skipif:


ch2/tasks_proj/tests/func/ test_unique_id_3.py

 @pytest.mark.skipif(tasks.__version__ < '0.2.0', reason='not supported until version 0.2.0') def test_unique_id_1(): """ unique_id ()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 

, skipif() , Python. , , . skip , skipif . skip , skipif . ( reason ) skip , skipif xfail . :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py s. ===================== 1 passed, 1 skipped in 0.20 seconds ===================== 

s. , (skipped), (passed). , - -v :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py::test_unique_id_1 SKIPPED test_unique_id_3.py::test_unique_id_2 PASSED ===================== 1 passed, 1 skipped in 0.19 seconds ===================== 

. -rs :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -rs test_unique_id_3.py ============================= test session starts ============================= collected 2 items test_unique_id_3.py s. =========================== short test summary info =========================== SKIP [1] func\test_unique_id_3.py:8: not supported until version 0.2.0 ===================== 1 passed, 1 skipped in 0.22 seconds ===================== 

-r chars :


 $ pytest --help ... -r chars show extra test summary info as specified by chars (     ,  ) (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (p)passed, (P)passed with output, (a)all except pP. ... 

, .



skip skipif , . xfail pytest , , . unique_id () , xfail :


ch2/tasks_proj/tests/func/ test_unique_id_4.py

 @pytest.mark.xfail(tasks.__version__ < '0.2.0', reason='not supported until version 0.2.0') def test_unique_id_1(): """ unique_id()     .""" id_1 = tasks.unique_id() id_2 = tasks.unique_id() assert id_1 != id_2 @pytest.mark.xfail() def test_unique_id_is_a_duck(): """ xfail.""" uid = tasks.unique_id() assert uid == 'a duck' @pytest.mark.xfail() def test_unique_id_not_a_duck(): """ xpass.""" uid = tasks.unique_id() assert uid != 'a duck' 

Running this shows:


, , xfail . == vs.! =. .


:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest test_unique_id_4.py ============================= test session starts ============================= collected 4 items test_unique_id_4.py xxX. =============== 1 passed, 2 xfailed, 1 xpassed in 0.36 seconds ================ 

X XFAIL, « ( expected to fail )». X XPASS «, , ( expected to fail but passed. )».


--verbose :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_unique_id_4.py ============================= test session starts ============================= collected 4 items test_unique_id_4.py::test_unique_id_1 xfail test_unique_id_4.py::test_unique_id_is_a_duck xfail test_unique_id_4.py::test_unique_id_not_a_duck XPASS test_unique_id_4.py::test_unique_id_2 PASSED =============== 1 passed, 2 xfailed, 1 xpassed in 0.36 seconds ================ 

pytest , , , xfail , FAIL. pytest.ini :


 [pytest] xfail_strict=true 

pytest.ini 6, , . 113.



, ​​ . . , , . , . . .


A Single Directory


, pytest :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest tests\func --tb=no ============================= test session starts ============================= collected 50 items tests\func\test_add.py .. tests\func\test_add_variety.py ................................ tests\func\test_api_exceptions.py ....... tests\func\test_unique_id_1.py F tests\func\test_unique_id_2.py s. tests\func\test_unique_id_3.py s. tests\func\test_unique_id_4.py xxX. ==== 1 failed, 44 passed, 2 skipped, 2 xfailed, 1 xpassed in 1.75 seconds ===== 

, -v , .


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v tests\func --tb=no ============================= test session starts ============================= 

...


 collected 50 items tests\func\test_add.py::test_add_returns_valid_id PASSED tests\func\test_add.py::test_added_task_has_id_set PASSED tests\func\test_add_variety.py::test_add_1 PASSED tests\func\test_add_variety.py::test_add_2[task0] PASSED tests\func\test_add_variety.py::test_add_2[task1] PASSED tests\func\test_add_variety.py::test_add_2[task2] PASSED tests\func\test_add_variety.py::test_add_2[task3] PASSED tests\func\test_add_variety.py::test_add_3[sleep-None-False] PASSED ... tests\func\test_unique_id_2.py::test_unique_id_1 SKIPPED tests\func\test_unique_id_2.py::test_unique_id_2 PASSED ... tests\func\test_unique_id_4.py::test_unique_id_1 xfail tests\func\test_unique_id_4.py::test_unique_id_is_a_duck xfail tests\func\test_unique_id_4.py::test_unique_id_not_a_duck XPASS tests\func\test_unique_id_4.py::test_unique_id_2 PASSED ==== 1 failed, 44 passed, 2 skipped, 2 xfailed, 1 xpassed in 2.05 seconds ===== 

, .


File/Module


, , pytest:


 $ cd /path/to/code/ch2/tasks_proj $ pytest tests/func/test_add.py =========================== test session starts =========================== collected 2 items tests/func/test_add.py .. ======================== 2 passed in 0.05 seconds ========================= 

.



, :: :


 $ cd /path/to/code/ch2/tasks_proj $ pytest -v tests/func/test_add.py::test_add_returns_valid_id =========================== test session starts =========================== collected 3 items tests/func/test_add.py::test_add_returns_valid_id PASSED ======================== 1 passed in 0.02 seconds ========================= 

-v , , .


Test Class


Here's an example:


— , .
以䞋に䟋を瀺したす。


ch2/tasks_proj/tests/func/ test_api_exceptions.py

 class TestUpdate(): """    tasks.update().""" def test_bad_id(self): """non-int id   excption.""" with pytest.raises(TypeError): tasks.update(task_id={'dict instead': 1}, task=tasks.Task()) def test_bad_task(self): """A non-Task task   excption.""" with pytest.raises(TypeError): tasks.update(task_id=1, task='not a task') 

, update() , . , , :: , :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v tests/func/test_api_exceptions.py::TestUpdate ============================= test session starts ============================= collected 2 items tests\func\test_api_exceptions.py::TestUpdate::test_bad_id PASSED tests\func\test_api_exceptions.py::TestUpdate::test_bad_task PASSED ========================== 2 passed in 0.12 seconds =========================== 

A Single Test Method of a Test Class


, — :: :


 $ cd /path/to/code/ch2/tasks_proj $ pytest -v tests/func/test_api_exceptions.py::TestUpdate::test_bad_id ===================== test session starts ====================== collected 1 item tests/func/test_api_exceptions.py::TestUpdate::test_bad_id PASSED =================== 1 passed in 0.03 seconds =================== 

,

, , , , . , pytest -v .


-k , . and , or not . , _raises :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -k _raises ============================= test session starts ============================= collected 56 items tests/func/test_api_exceptions.py::test_add_raises PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED tests/func/test_api_exceptions.py::test_delete_raises PASSED tests/func/test_api_exceptions.py::test_start_tasks_db_raises PASSED ============================= 51 tests deselected ============================= =================== 5 passed, 51 deselected in 0.54 seconds =================== 

and not test_delete_raises() :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj>pytest -v -k "_raises and not delete" ============================= test session starts ============================= collected 56 items tests/func/test_api_exceptions.py::test_add_raises PASSED tests/func/test_api_exceptions.py::test_list_raises PASSED tests/func/test_api_exceptions.py::test_get_raises PASSED tests/func/test_api_exceptions.py::test_start_tasks_db_raises PASSED ============================= 52 tests deselected ============================= =================== 4 passed, 52 deselected in 0.44 seconds =================== 

, , , -k . , , .


[Parametrized Testing]:


, , . . - pytest, - .


, , add() :


ch2/tasks_proj/tests/func/ test_add_variety.py

 """  API tasks.add().""" import pytest import tasks from tasks import Task def test_add_1(): """tasks.get ()  id,   add() works.""" task = Task('breathe', 'BRIAN', True) task_id = tasks.add(task) t_from_db = tasks.get(task_id) # ,  ,    assert equivalent(t_from_db, task) def equivalent(t1, t2): """   .""" #  ,   id return ((t1.summary == t2.summary) and (t1.owner == t2.owner) and (t1.done == t2.done)) @pytest.fixture(autouse=True) def initialized_tasks_db(tmpdir): """    ,  .""" tasks.start_tasks_db(str(tmpdir), 'tiny') yield tasks.stop_tasks_db() 

tasks id None . id . == , , . equivalent() , id . autouse , , . , :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_1 ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_1 PASSED ========================== 1 passed in 0.69 seconds =========================== 

. , . , ? . @pytest.mark.parametrize(argnames, argvalues) , :


ch2/tasks_proj/tests/func/ test_add_variety.py

 @pytest.mark.parametrize('task', [Task('sleep', done=True), Task('wake', 'brian'), Task('breathe', 'BRIAN', True), Task('exercise', 'BrIaN', False)]) def test_add_2(task): """    .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

parametrize() — — 'task', . — , Task. pytest :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_2 ============================= test session starts ============================= collected 4 items test_add_variety.py::test_add_2[task0] PASSED test_add_variety.py::test_add_2[task1] PASSED test_add_variety.py::test_add_2[task2] PASSED test_add_variety.py::test_add_2[task3] PASSED ========================== 4 passed in 0.69 seconds =========================== 

parametrize() . , , :


ch2/tasks_proj/tests/func/ test_add_variety.py

 @pytest.mark.parametrize('summary, owner, done', [('sleep', None, False), ('wake', 'brian', False), ('breathe', 'BRIAN', True), ('eat eggs', 'BrIaN', False), ]) def test_add_3(summary, owner, done): """    .""" task = Task(summary, owner, done) task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

, pytest, , :


 (venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_3 ============================= test session starts ============================= platform win32 -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0 -- cachedir: ..\.pytest_cache rootdir: ...\bopytest-code\code\ch2\tasks_proj\tests, inifile: pytest.ini collected 4 items test_add_variety.py::test_add_3[sleep-None-False] PASSED [ 25%] test_add_variety.py::test_add_3[wake-brian-False] PASSED [ 50%] test_add_variety.py::test_add_3[breathe-BRIAN-True] PASSED [ 75%] test_add_variety.py::test_add_3[eat eggs-BrIaN-False] PASSED [100%] ========================== 4 passed in 0.37 seconds =========================== 

, , pytest, :


 (venv35) c:\BOOK\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_3[sleep-None-False] ============================= test session starts ============================= test_add_variety.py::test_add_3[sleep-None-False] PASSED [100%] ========================== 1 passed in 0.22 seconds =========================== 

, :


 (venv35) c:\BOOK\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v "test_add_variety.py::test_add_3[eat eggs-BrIaN-False]" ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_3[eat eggs-BrIaN-False] PASSED [100%] ========================== 1 passed in 0.56 seconds =========================== 

, :


ch2/tasks_proj/tests/func/ test_add_variety.py

 tasks_to_try = (Task('sleep', done=True), Task('wake', 'brian'), Task('wake', 'brian'), Task('breathe', 'BRIAN', True), Task('exercise', 'BrIaN', False)) @pytest.mark.parametrize('task', tasks_to_try) def test_add_4(task): """ .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

. :


 (venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_4 ============================= test session starts ============================= collected 5 items test_add_variety.py::test_add_4[task0] PASSED [ 20%] test_add_variety.py::test_add_4[task1] PASSED [ 40%] test_add_variety.py::test_add_4[task2] PASSED [ 60%] test_add_variety.py::test_add_4[task3] PASSED [ 80%] test_add_variety.py::test_add_4[task4] PASSED [100%] ========================== 5 passed in 0.34 seconds =========================== 

, . , ids parametrize() , . ids , . , tasks_to_try , :


ch2/tasks_proj/tests/func/ test_add_variety.py

 task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done) for t in tasks_to_try] @pytest.mark.parametrize('task', tasks_to_try, ids=task_ids) def test_add_5(task): """Demonstrate ids.""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) 

, :


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::test_add_5 ============================= test session starts ============================= collected 5 items test_add_variety.py::test_add_5[Task(sleep,None,True)] PASSED test_add_variety.py::test_add_5[Task(wake,brian,False)0] PASSED test_add_variety.py::test_add_5[Task(wake,brian,False)1] PASSED test_add_variety.py::test_add_5[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)] PASSED ========================== 5 passed in 0.45 seconds =========================== 

:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v "test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)]" ============================= test session starts ============================= collected 1 item test_add_variety.py::test_add_5[Task(exercise,BrIaN,False)] PASSED ========================== 1 passed in 0.21 seconds =========================== 

; shell. parametrize() . :


ch2/tasks_proj/tests/func/ test_add_variety.py

 @pytest.mark.parametrize('task', tasks_to_try, ids=task_ids) class TestAdd(): """   .""" def test_equivalent(self, task): """ ,   .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert equivalent(t_from_db, task) def test_valid_id(self, task): """          .""" task_id = tasks.add(task) t_from_db = tasks.get(task_id) assert t_from_db.id == task_id 

:


 (venv33) ...\bopytest-code\code\ch2\tasks_proj\tests\func>pytest -v test_add_variety.py::TestAdd ============================= test session starts ============================= collected 10 items test_add_variety.py::TestAdd::test_equivalent[Task(sleep,None,True)] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(wake,brian,False)0] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(wake,brian,False)1] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::TestAdd::test_equivalent[Task(exercise,BrIaN,False)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(sleep,None,True)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(wake,brian,False)0] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(wake,brian,False)1] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(breathe,BRIAN,True)] PASSED test_add_variety.py::TestAdd::test_valid_id[Task(exercise,BrIaN,False)] PASSED ========================== 10 passed in 1.16 seconds ========================== 

, @pytest.mark.parametrize() . pytest.param(<value\>, id="something") :


:


 (venv35) ...\bopytest-code\code\ch2\tasks_proj\tests\func $ pytest -v test_add_variety.py::test_add_6 ======================================== test session starts ========================================= collected 3 items test_add_variety.py::test_add_6[just summary] PASSED [ 33%] test_add_variety.py::test_add_6[summary\owner] PASSED [ 66%] test_add_variety.py::test_add_6[summary\owner\done] PASSED [100%] ================================ 3 passed, 6 warnings in 0.35 seconds ================================ 

, id .



  1. , task_proj , - , pip install /path/to/tasks_proj .
  2. .
  3. pytest .
  4. pytest , tasks_proj/tests/func . pytest , . . , ?
  5. xfail , pytest tests .
  6. tasks.count() , . API , , , .
  7. ? test_api_exceptions.py . , . ( api.py .)

次は䜕ですか


pytest . , , , . initialized_tasks_db . / .


たた、耇数のテスト機胜が同じ蚭定を䜿甚できるように、共通コヌドを分離するこずもできたす。次の章では、フィクスチャpytestの玠晎らしい䞖界に深く入り蟌みたす。


戻る 次ぞ

Source: https://habr.com/ru/post/J448788/


All Articles