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

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

```
pip install pytest
```

pytestの詳細、最新情報は以下サイト参考。
- [docs.pytest.org](https://docs.pytest.org/en/latest/contents.html)
- [The Hitchhiker's Guide to Python](http://python-guideja.readthedocs.io/ja/latest/writing/tests.html)

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


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

 - フォルダ構成

  - 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
```

## テストがpassしたときもprint出力を表示したい
「passed」 のときにもprint出力をコンソール上に表示させる。
オプション指定しない場合、「failed」 のときしか表示されない。

- コマンド

```
pytest -v --capture=no
```


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