Help us understand the problem. What is going on with this article?

pytestのとりあえず知っておきたい使い方

More than 1 year has passed since last update.

pytestとは

Pythonのテストツールです。
pipコマンドで簡単に導入できます。

pip install pytest

pytestの詳細、最新情報は以下サイト参考。
- docs.pytest.org
- The Hitchhiker's Guide to Python

オプションがいろいろあるので、とりあえず知っておくと便利なオプションを選んで
やりたいこと別にコマンドと表示例を記載します。

フォルダ構成、プログラム

として以下のフォルダ構成、プログラムについての実行結果を記載します。

  • フォルダ構成

    • mainフォルダに コード (テスト対象) を配置
    • testsフォルダに テストコード を配置
    • テストコードのファイル名は test_*.py とするのがお約束
study_pytest
 ├─main
 |  ├─ calc.py
 |  └─ say.py
 └─tests
    ├─ test_calc.py
    └─ test_say.py
  • コード
calc.py
class Calc: 

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def add(self):
        return self.a + self.b

    def dif(self):
        return self.a - self.b

    def seki(self):
        return self.a*self.b

    def shou(self):
        return self.a/self.b
say.py
class Foo:

    def say(self):
        return 'foo'

    def say2(self):
        return 'foo2'

class Hoge:

    def say(self):
        return 'hoge'

    def say2(self):
        return 'hoge2'
  • テストコード

    • ダブルメンテにならないように、テスト対象はcallして実行する
    • 必要により、システム環境変数PYTHONPATHにパスを追加する
    • テストコードの各ケースは独立であること
    • テストコードの各ケースの名前(定義関数名)は説明的であること    
      • テスト結果に表示されたときに、名前からわかるようにします。
      • 例: test_(テスト対象のクラス名/定義関数名)_(検証観点)_(連番)
test_calc.py
from main.calc import Calc

def test_add_01():
    assert Calc(9,2).add() == 11

def test_add_02():
    assert Calc(-9,2).add() == -7

def test_dif_01():
    assert Calc(9,2).dif() == 7

def test_dif_02():
    assert Calc(-9,2).dif() == -11

def test_seki_01():
    assert Calc(9,2).seki() == 18

def test_seki_02():
    assert Calc(-9,2).seki() == -18

def test_shou_01():
    assert Calc(9,2).shou() == 4.5

def test_shou_02():
    assert Calc(-9,2).shou() == -4.5
test_say.py
from main.say import Foo,Hoge

def test_foo_say():
    assert Foo().say() == 'foo'

def test_foo_say2():
    assert Foo().say2() == 'foo'  #テストエラーの出力確認。

def test_hoge_say():
    assert Hoge().say() == 'hoge'

def test_hoge_say2():
    assert Hoge().say2()== 'hoge2'

テスト実行コマンド(基本)

study_pytest または study_pytest/testsのディレクトリ に移動してコマンドを実施します。

すべてのテストコードを実施

pytest

指定テストコードのみ実施

  • テストコードファイルを引数に指定する
pytest tests/test_calc.py

ヘルプの表示、オプションを確認したい

pytest -h

テスト実行コマンド(応用)

やりたいこと別に コマンド出力結果 を記載します。

オプションなし

  • コマンド
pytest
  • 実行結果
study_pytest>pytest
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.4.2, py-1.5.2, pluggy-0.6.0
rootdir: C:\Users\xxx\python\study_pytest, inifile:
collected 12 items

tests\test_calc.py ........                                              [ 66%]
tests\test_say.py .F..                                                   [100%]

================================== FAILURES ===================================
________________________________ test_foo_say2 ________________________________

    def test_foo_say2():
>       assert Foo().say2() == 'foo'
E       AssertionError: assert 'foo2' == 'foo'
E         - foo2
E         ?    -
E         + foo

tests\test_say.py:7: AssertionError
===================== 1 failed, 11 passed in 0.13 seconds =====================

テストケース別に結果を知りたい

テストケースごとPASSED/FAILED が表示される。

  • コマンド
pytest -v 
  • 実行結果
study_pytest>pytest -v
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.4.2, py-1.5.2, pluggy-0.6.0 -- c:\users\xxx\appdata\local\programs\python\python36\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\xxx\Documents\python\study_pytest, inifile:
collected 12 items

tests/test_calc.py::test_add_01 PASSED                                   [  8%]
tests/test_calc.py::test_add_02 PASSED                                   [ 16%]
tests/test_calc.py::test_dif_01 PASSED                                   [ 25%]
tests/test_calc.py::test_dif_02 PASSED                                   [ 33%]
tests/test_calc.py::test_seki_01 PASSED                                  [ 41%]
tests/test_calc.py::test_seki_02 PASSED                                  [ 50%]
tests/test_calc.py::test_shou_01 PASSED                                  [ 58%]
tests/test_calc.py::test_shou_02 PASSED                                  [ 66%]
tests/test_say.py::test_foo_say PASSED                                   [ 75%]
tests/test_say.py::test_foo_say2 FAILED                                  [ 83%]
tests/test_say.py::test_hoge_say PASSED                                  [ 91%]
tests/test_say.py::test_hoge_say2 PASSED                                 [100%]

================================== FAILURES ===================================
________________________________ test_foo_say2 ________________________________

    def test_foo_say2():
>       assert Foo().say2() == 'foo'
E       AssertionError: assert 'foo2' == 'foo'
E         - foo2
E         ?    -
E         + foo

tests\test_say.py:7: AssertionError
===================== 1 failed, 11 passed in 0.18 seconds =====================

前回NGだったケースだけテストしたい

last-failed (前回failedだったケース) のみ実施する。

  • コマンド
pytest -v --lf
  • 実行結果
study_pytest>pytest -v --lf
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.4.2, py-1.5.2, pluggy-0.6.0 -- c:\users\xxx\appdata\local\programs\python\python36\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\xxx\Documents\python\study_pytest, inifile:
collected 12 items
run-last-failure: rerun previous 1 failure

tests/test_say.py::test_foo_say2 FAILED                                  [100%]

================================== FAILURES ===================================
________________________________ test_foo_say2 ________________________________

    def test_foo_say2():
>       assert Foo().say2() == 'foo'
E       AssertionError: assert 'foo2' == 'foo'
E         - foo2
E         ?    -
E         + foo

tests\test_say.py:7: AssertionError
============================= 11 tests deselected =============================
=================== 1 failed, 11 deselected in 0.12 seconds ===================

前回NGだったケースからテストしたい

failed-first (前回failedだったケースから) で実施する。
前回正常終了分も続けて実施される。

  • コマンド
pytest -v --ff
  • 実行結果
study_pytest>pytest -v --ff
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.4.2, py-1.5.2, pluggy-0.6.0 -- c:\users\xxx\appdata\local\programs\python\python36\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\xxx\Documents\python\study_pytest, inifile:
collected 12 items
run-last-failure: rerun previous 1 failure first

tests/test_say.py::test_foo_say2 FAILED                                  [  8%]
tests/test_calc.py::test_add_01 PASSED                                   [ 16%]
tests/test_calc.py::test_add_02 PASSED                                   [ 25%]
tests/test_calc.py::test_dif_01 PASSED                                   [ 33%]
tests/test_calc.py::test_dif_02 PASSED                                   [ 41%]
tests/test_calc.py::test_seki_01 PASSED                                  [ 50%]
tests/test_calc.py::test_seki_02 PASSED                                  [ 58%]
tests/test_calc.py::test_shou_01 PASSED                                  [ 66%]
tests/test_calc.py::test_shou_02 PASSED                                  [ 75%]
tests/test_say.py::test_foo_say PASSED                                   [ 83%]
tests/test_say.py::test_hoge_say PASSED                                  [ 91%]
tests/test_say.py::test_hoge_say2 PASSED                                 [100%]

================================== FAILURES ===================================
________________________________ test_foo_say2 ________________________________

    def test_foo_say2():
>       assert Foo().say2() == 'foo'
E       AssertionError: assert 'foo2' == 'foo'
E         - foo2
E         ?    -
E         + foo

tests\test_say.py:7: AssertionError
===================== 1 failed, 11 passed in 0.21 seconds =====================

テスト時間を記録したい

テストコードの定義関数ごとの時間を表示する。
処理時間の結果について、1つの定義関数につき setup/call/teardown に分かれることに注意。

  • コマンド
pytest -v --duration=0
  • 実行結果
study_pytest>pytest -v --duration=0
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.4.2, py-1.5.2, pluggy-0.6.0 -- c:\users\xxx\appdata\local\programs\python\python36\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\xxx\Documents\python\study_pytest, inifile:
collected 12 items

tests/test_calc.py::test_add_01 PASSED                                   [  8%]
tests/test_calc.py::test_add_02 PASSED                                   [ 16%]
tests/test_calc.py::test_dif_01 PASSED                                   [ 25%]
tests/test_calc.py::test_dif_02 PASSED                                   [ 33%]
tests/test_calc.py::test_seki_01 PASSED                                  [ 41%]
tests/test_calc.py::test_seki_02 PASSED                                  [ 50%]
tests/test_calc.py::test_shou_01 PASSED                                  [ 58%]
tests/test_calc.py::test_shou_02 PASSED                                  [ 66%]
tests/test_say.py::test_foo_say PASSED                                   [ 75%]
tests/test_say.py::test_foo_say2 FAILED                                  [ 83%]
tests/test_say.py::test_hoge_say PASSED                                  [ 91%]
tests/test_say.py::test_hoge_say2 PASSED                                 [100%]

=========================== slowest test durations ============================
0.00s call     tests/test_say.py::test_foo_say2
0.00s setup    tests/test_calc.py::test_add_01
0.00s teardown tests/test_say.py::test_foo_say
0.00s call     tests/test_say.py::test_hoge_say2
0.00s teardown tests/test_say.py::test_foo_say2
0.00s call     tests/test_calc.py::test_add_01
0.00s setup    tests/test_calc.py::test_add_02
0.00s call     tests/test_calc.py::test_seki_01
0.00s teardown tests/test_calc.py::test_shou_01
0.00s teardown tests/test_calc.py::test_dif_02
0.00s setup    tests/test_calc.py::test_dif_02
0.00s call     tests/test_say.py::test_foo_say
0.00s teardown tests/test_calc.py::test_shou_02
0.00s call     tests/test_say.py::test_hoge_say
0.00s call     tests/test_calc.py::test_shou_02
0.00s call     tests/test_calc.py::test_shou_01
0.00s call     tests/test_calc.py::test_seki_02
0.00s call     tests/test_calc.py::test_dif_02
0.00s call     tests/test_calc.py::test_dif_01
0.00s call     tests/test_calc.py::test_add_02
0.00s teardown tests/test_say.py::test_hoge_say2
0.00s setup    tests/test_say.py::test_hoge_say2
0.00s teardown tests/test_say.py::test_hoge_say
0.00s setup    tests/test_say.py::test_hoge_say
0.00s setup    tests/test_say.py::test_foo_say2
0.00s setup    tests/test_say.py::test_foo_say
0.00s setup    tests/test_calc.py::test_shou_02
0.00s setup    tests/test_calc.py::test_shou_01
0.00s teardown tests/test_calc.py::test_seki_02
0.00s setup    tests/test_calc.py::test_seki_02
0.00s teardown tests/test_calc.py::test_seki_01
0.00s setup    tests/test_calc.py::test_seki_01
0.00s teardown tests/test_calc.py::test_dif_01
0.00s setup    tests/test_calc.py::test_dif_01
0.00s teardown tests/test_calc.py::test_add_02
0.00s teardown tests/test_calc.py::test_add_01
================================== FAILURES ===================================
________________________________ test_foo_say2 ________________________________

    def test_foo_say2():
>       assert Foo().say2() == 'foo'
E       AssertionError: assert 'foo2' == 'foo'
E         - foo2
E         ?    -
E         + foo

tests\test_say.py:7: AssertionError
===================== 1 failed, 11 passed in 0.26 seconds =====================

遅いテストケースを見つけたい

遅かったテストTOP N個の時間を並べて表示する。

  • コマンド
pytest -v --duration=N
  • 実行結果
study_pytest>pytest -v --duration=3
============================= test session starts =============================
platform win32 -- Python 3.6.3, pytest-3.4.2, py-1.5.2, pluggy-0.6.0 -- c:\user\xxx\appdata\local\programs\python\python36\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\xxx\Documents\python\study_pytest, inifile:
collected 12 items

tests/test_calc.py::test_add_01 PASSED                                   [  8%]
tests/test_calc.py::test_add_02 PASSED                                   [ 16%]
tests/test_calc.py::test_dif_01 PASSED                                   [ 25%]
tests/test_calc.py::test_dif_02 PASSED                                   [ 33%]
tests/test_calc.py::test_seki_01 PASSED                                  [ 41%]
tests/test_calc.py::test_seki_02 PASSED                                  [ 50%]
tests/test_calc.py::test_shou_01 PASSED                                  [ 58%]
tests/test_calc.py::test_shou_02 PASSED                                  [ 66%]
tests/test_say.py::test_foo_say PASSED                                   [ 75%]
tests/test_say.py::test_foo_say2 FAILED                                  [ 83%]
tests/test_say.py::test_hoge_say PASSED                                  [ 91%]
tests/test_say.py::test_hoge_say2 PASSED                                 [100%]

========================== slowest 3 test durations ===========================
0.00s call     tests/test_say.py::test_foo_say2
0.00s setup    tests/test_calc.py::test_add_01
0.00s call     tests/test_calc.py::test_dif_02
================================== FAILURES ===================================
________________________________ test_foo_say2 ________________________________

    def test_foo_say2():
>       assert Foo().say2() == 'foo'
E       AssertionError: assert 'foo2' == 'foo'
E         - foo2
E         ?    -
E         + foo

tests\test_say.py:7: AssertionError
===================== 1 failed, 11 passed in 0.20 seconds =====================

テストログをテキストファイルに出力したい

この機能は 次のVUP時(V4.0)にて閉塞される予定。
出力ログは、コマンドの実行ログより情報量が少ない。

  • コマンド
pytest -v --result-log=tests/log.txt
  • 実行結果(log)
log.txt
. tests/test_calc.py::test_add_01
. tests/test_calc.py::test_add_02
. tests/test_calc.py::test_dif_01
. tests/test_calc.py::test_dif_02
. tests/test_calc.py::test_seki_01
. tests/test_calc.py::test_seki_02
. tests/test_calc.py::test_shou_01
. tests/test_calc.py::test_shou_02
. tests/test_say.py::test_foo_say
F tests/test_say.py::test_foo_say2
 def test_foo_say2():
 >       assert Foo().say2() == 'foo'
 E       AssertionError: assert 'foo2' == 'foo'
 E         - foo2
 E         ?    -
 E         + foo

 tests\test_say.py:7: AssertionError
. tests/test_say.py::test_hoge_say
. tests/test_say.py::test_hoge_say2

カバレッジを計測したい

カバレッジの計測も難しくないが、追加のpluginの導入が必要。
導入方法から以下のページに記載。
https://qiita.com/kg1/items/e2fc65e4189faf50bfe6

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away