unittest、doctest、noseライブラリについて

# Pythonのテストについて

Pythonのテストフレームワークである
`unittest` 、 `doctest` 、 `nose` ライブラリについてまとめます。

# unittestによるテスト

標準ライブラリには2つテストフレームがあり、 `unittest` ライブラリはその1つです。

以下に、 `unittest` を使用したサンプルコードを記述します。
`cap.py` にテスト対象のメソッドを記述し、
`test_cap.py` でテストを実行します。

```cap.py
def convert_to_title_case(text):
    # 文字列内の単語の先頭をすべて大文字にする
    return text.title()
``` 

```test_cap.py

import unittest
import cap

class TestCap(unittest.TestCase):

    def setUp(self):
        print('setUp()メソッドが呼び出されました')

    def tearDown(self):
        print('tearDown()メソッドが呼び出されました')

    def test_one_word(self):
        text = 'python'
        result = cap.convert_to_title_case(text)
        self.assertEqual(result, 'Python')

    def test_multiple_word(self):
        text = 'python programming'
        result = cap.convert_to_title_case(text)
        self.assertEqual(result, 'Python Programming')

if __name__ == "__main__":
    unittest.main()

```

```bash

$ python learn-test/test_cap.py
setUp()メソッドが呼び出されました
tearDown()メソッドが呼び出されました
.setUp()メソッドが呼び出されました
tearDown()メソッドが呼び出されました
.
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
```

## setUp()メソッドとtearDown()メソッドについて

`setUp()` と `tearDown()` の目的は、
テストが必要とする外部リソースを確保・解放することです。
ここでの外部リソースは、例えばデータベース接続や、なんらかのテストデータのことで、
テストを実行する前後になんらかの処理を行いたい場合に有用です。

しかし、 **unittestライブライリの `setUp()` と `tearDown()` はクラス単位で作成するものの、テストごとに実行されます。**

ちなみに、Python3.2以降では、setUpClassやtearDownClass、setUpModuleやtesrDownModuleが追加され、
これらを使用すれば、クラスやモジュールレベルで実行できるようになります。

# doctestによるテスト

標準ライブラリに含まれている `doctest` パッケージを使用すれば、
docstring(クラスや関数内に記述するコメント)の中にテストを書くことができます。

以下、テストが失敗する場合と成功場合のコードを書いて挙動を確認します。

#### テストが失敗する場合

```test_faile_cap_with_docstring.py

def convert_to_title_case(text):

    '''
    >>> convert_to_title_case('python')

    'Python'

    >>> convert_to_title_case('Python Programming')
    Error

    '''
    return text.title()

if __name__ == "__main__":

    import doctest
    doctest.testmod()

``` 

```bash
$ python test_cap_with_docstring.py
**********************************************************************
File "test_cap_with_docstring.py", line 5, in __main__.convert_to_title_case
Failed example:
    convert_to_title_case('Python Programming')
Expected:
    Error
Got:
    'Python Programming'
**********************************************************************
1 items had failures:
   1 of   2 in __main__.convert_to_title_case
***Test Failed*** 1 failures.
```

#### テストが成功する場合

```test_success_cap_with_docstring.py

def convert_to_title_case(text):

    '''
    >>> convert_to_title_case('python')

    'Python'

    >>> convert_to_title_case('python programming')

    'Python Programming'

    '''
    return text.title()

if __name__ == "__main__":

    import doctest
    doctest.testmod()

``` 

``` bash
$ python test_success_cap_with_docstring.py
$
```

すべてのテストが成功した場合は、何も表示されません。
詳細を確認する場合は `-v` オプションをつけると確認できます。

```bash
$python test_success_cap_with_docstring.py -v
Trying:

    convert_to_title_case('python')

Expecting:

    'Python'

ok
Trying:

    convert_to_title_case('python programming')

Expecting:

    'Python Programming'

ok
1 items had no tests:

    __main__

1 items passed all tests:
   2 tests in __main__.convert_to_title_case
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

``` 

# `nose` ライブライリ使用したテスト

[ `nose` ](https://nose.readthedocs.io/en/latest/) はPythonのサードパーティーのテストフレームワークの1つです。

まずはインストールします。

```bash
$ pip install nose
```

`nose` で使用するメソッドを一部紹介して、サンプルコードを記述します。
メソッドのリファレンスは[こちら](https://nose.readthedocs.io/en/latest/testing_tools.html)

* `nose.tools.ok_(expr, msg=None)` : `expr` がTrueかどうか評価します。
* `nose.tools.wq_(a, b,, msg=None)` : `a` と `b` が等価( `==` )であるか評価します。
* `nose.tools.times(limit)` :メソッドにデコレータとして付与し、 `limit` で指定した時間内にテストが実行されるかどうか評価します。
* `nose.tools.with_setup(setup=None, teardown=None)` :デコレータとして、 **メソッドレベル**で `setup` と `teardown` の仕組みを実行します。

```nose_test_sample.py
import cap
from nose.tools import eq_, with_setup

def setUp():

    print('setUp()メソッドが呼び出されました')

def tearDown():

    print('tearDown()メソッドが呼び出されました')

def set_ok():

    print('set_ok()メソッドが呼びだれました。')

@with_setup(set_ok)
def test_one_work():

    text = 'python'
    result = cap.convert_to_title_case(text)
    eq_(result, 'Python')

def test_multiple_word():

    text = 'python programming'
    result = cap.convert_to_title_case(text)
    eq_(result, 'Python Programming')

``` 

**nose をインストールすると、 `nosetests` コマンドが使用できるようになります**

```bash
$ nosetests -s -v nose_test_sample.py

setUp()メソッドが呼び出されました
nose_test_sample.test_one_work ... setUp()メソッドが呼び出されました
tearDown()メソッドが呼び出されました
ok
nose_test_sample.test_multiple_word ... ok
tearDown()メソッドが呼び出されました

----------------------------------------------------------------------
Ran 2 tests in 0.024s

OK

```

`setup()` 、 `teardown()` によって**モジュールレベル**で `setUp` / `tearDown` の仕組みを適用します。
ちなみに、**パッケージレベルになると、 `__init__.py` モジュールの中に `setUp` / `tearDown` 関数に書く必要があります。**

# `unittest` と `nose` のそれぞれの特徴

最後に `unittest` と `nose` の特徴を列挙します。

#### `unitest` 
  + PEP8に従っていない
  + JUnitに似せられた
  + `TestCase` を必ず継承しないといけない。
  + setUp/tearDownの仕組むがクラス単位に縛られるのにも関わらず、メソッドごとに実行

#### `nose` 

  + 継承が不要等、簡単にテストが書ける
  + プラグインが豊富
  + PEP8に従っている