605
562

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Python標準のunittestの使い方メモ

Last updated at Posted at 2016-11-30

はじめに

このメモの位置付け

この解説は、Pythonのunittest機能の使い方を簡単に記録したメモである。Pythonのユニットテスト機能がどのようなものであるのかを説明すること、簡単な例を提示し今後何らかのテストを行いたいと思った際の雛形を提供することを目的としている。

なお、doctestの使い方については扱わない。

それから、多くのテストを実行する際にはnosetestsとか使うと思うが、それも説明していない。

本か公式のドキュメントを読んだほうが当然ちゃんと役に立つ知識が身につくし、仕事で使うならこれではなくてちゃんとしたものを読んだほうが良いと思う。誰のためにも。

対象とするPythonのバージョン

Python 2.7.11

Python

Python unittestとは

Pythonコードのテストを行う上で有用な機能が実装された一群のモジュールのことである。
Pythonの実行環境に含まれており、Pythonをインストールした時点で利用可能になっているはず。
下記のようにインポートすることで利用することが可能。

import unittest

その他にもテストツールは大量にあるようだ。referncesにあるサイトを色々眺めてみてほしい。

公式ドキュメント

Python unittestの機能の詳細については、公式ドキュメントを参照するとよい。

最初に、どのような機能を持ったものであるのかが簡潔に述べられている。その後、多くの人にとってこの使い方を抑えておけばよかろうというような使い方の例が示されている。中盤以降では、各機能の詳細、およびテストに用いるAssertほげほげ系のメソッド一覧がある。

英語版:https://docs.python.org/2.7/library/unittest.html
日本語版: https://docs.python.org/ja/2.7/library/unittest.html#module-unittest

使い方

使い方

基本的な使い方は以下のとおり。

  1. unittestをインポートする
  2. unittest.TestCaseを継承したクラスTestHOGEHOGEを作る
  3. TestHOGEHOGEの中に、テストケースを記述する。テストケースは、AssertHOGE()という名称の一群のメソッドを使う。このメソッドによって一致・大小関係などなどの比較が行われる。
  4. unittest.main()でテストを実行する。所望の通りの結果が実現されていれば、成功、そうでなければ失敗という結果が得られる。

最も基本的な例

ある適当なコードがあるとする。たとえば、次のような、足し算を行うだけのメソッド。

tashizan.py

def tashizan(a, b):
    """ calculate (a + b)
    """
    return a + b

これのテストを行いたいとする。満たされているべき機能は、足し算が正しく実行されていること(引き算などになっていないこと)だろう。そのためのモジュールを書いてやる。

test_tashizan.py

import unittest
from tashizan import tashizan


class TestTashizan(unittest.TestCase):
    """test class of tashizan.py
    """

    def test_tashizan(self):
        """test method for tashizan
        """
        value1 = 2
        value2 = 6
        expected = 8
        actual = tashizan(value1, value2)
        self.assertEqual(expected, actual)


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

unittest.main()を叩くと一群のテストが実行される。
これを実行した結果は以下のとおり。

  • 実行結果
$ python test_tashizan.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

unittest.main()を実行すると、対象スクリプトのなかでスクリプト内でunittest.TestCaseを継承した全てのクラスを認識し、そのメソッドのうちtestで始まる名称を持つものが全てテストケースとして実行される。

特定のテストメソッドのみを実行する

複数のテストが存在する場合には上記の方法で実行すると、一挙に実行される。さっきのものに引き算も追加して実行した結果は以下の通り

keisan.py
def tashizan(a, b):
    """calculate a+b

    :param a: value 1(int)
    :param b: value 2(inte)
    :return: a + b
    """
    return a + b


def hikizan(a, b):
    """calculate a - b

    :param a:  value 1(int)
    :param b:  value 2(int)
    :return:  a - b
    """
    return a - b

--−−−

test_keisan.py
import unittest
import keisan


class TestKeisan(unittest.TestCase):
    """test class of keisan.py
    """

    def test_tashizan(self):
        """test method for tashizan
        """
        value1 = 2
        value2 = 6
        expected = 8
        actual = keisan.tashizan(value1, value2)
        self.assertEqual(expected, actual)


class TestKeisan2(unittest.TestCase):
    def test_hikizan(self):
        """test method for hikizan
        """
        value1 = 2
        value2 = 12
        expected = -10
        actual = keisan.hikizan(value1, value2)
        self.assertEqual(expected, actual)


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

$ python test_keisan.py -v
test_tashizan (__main__.TestKeisan)
test method for tashizan ... ok
test_hikizan (__main__.TestKeisan2)
test method for hikizan ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

(恣意的に、クラスを分けている。うえの場合だと、たぶん普通はひとつのクラスに書くんだろう)
ただ、特定のテストクラスのみを対象に実行したいこともある。その場合は、実行時に -m オプションを利用する。
以下に例を示しておく。ここで、unittestの後ろに-vオプションをつけることで、テスト内容に関する情報が表示される。
python -m -v ...とすると、Pythonインタプリタに-vオプションが渡されるのでテストと関係ない情報が大量に表示されてしまう。
@skawagt さんのコメント参照)

$ python  -m unittest test_keisan.TestKeisan2 -v
.
test_hikizan (test_keisan.TestKeisan2)
test method for hikizan ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

テストケース実行前後の処理をまとめる

値の初期化に手間がかかることもある。ある特定のオブジェクトに対するもろもろの演算をチェックしたいときに、各テストケースごとに値を入れるのは面倒だというような具合である。その場合に便利なものとして、setUp, tearDownメソッドがある。各テストケースごとに、setUp, tearDownが実行されることになる。

なんか二次方程式のクラスがあって、そのテストをしたい場合の例を示しておく。

QuadraticEquation.py
import numpy as np


class QuadraticEquation(object):
    """Quadratic equation class
    """

    def __init__(self):
        self.a = 1.0
        self.b = 2.0
        self.c = 1.0

    def calc_root(self):
        """calculate root

        :return: root1, root2 (root1 < root2)
        """
        x1 = (-1.0 * self.b - np.sqrt(self.b * self.b - 4.0 * self.a * self.c)) / (2.0 * self.a)
        x2 = (-1.0 * self.b + np.sqrt(self.b * self.b - 4.0 * self.a * self.c)) / (2.0 * self.a)
        return x1, x2

    def calc_value(self, x):
        """ calculate polynomial value

        :param x: x
        :return: a * x^2 + b * x + c
        """

        return self.a * np.power(x, 2.0) + self.b * x + self.c

これに対するテストケースを次のようにかく。

test_quadraticEquation.py
import unittest
import QuadraticEquation
from unittest import TestCase


class TestQuadraticEquation(unittest.TestCase):
    def setUp(self):
        print "setup"
        self.eq = QuadraticEquation.QuadraticEquation()

    def test_calc_root(self):
        """ test method of s"""
        expected = (-1.0, -1.0)
        actual = self.eq.calc_root()

        self.assertEqual(expected, actual)
        self.assertEqual((0.0, 0.0), (self.eq.calc_value(actual[0]), self.eq.calc_value(actual[1])))

    def test_calc_value(self):
        """ test method of calc_value() """
        expected = (4.0, 9.0)
        actual = (self.eq.calc_value(1.0), self.eq.calc_value(2.0))

        self.assertEqual(expected, actual)

    def tearDown(self):
        print "tearDown"
        del self.eq


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

これで実行すると、以下のようにテストケースの頭と終わりでsetUp,tearDownメソッドが呼ばれていることがわかる。

$ python -m unittest test_quadraticEquation
setup
tearDown
.setup
tearDown
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

一群のテストを一挙に行う

実際には、わざわざユニットテストを明示的に作成してテストを行うのは、複数のモジュールがある場合だと思う。その場合には、いちいち手で大量のテストモジュールを叩いているわけにはいかない。

その場合にはunittestのdiscover機能を利用することができる。

$ python -m unittest discover

うえの2つのテストケースがあったとして、

$ ls
QuadraticEquation.py  keisan.py  test_keisan.py  test_quadraticEquation.py

上記のdiscoverを使うと、以下のようになる。全部のテストが実行された。

$ python -m unittest discover
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

カバレッジを測る

[未完です]

テストカバレッジ率を測りたいことがあるが、これについてはunittestの機能にはおそらくない。なので、
coverageを使う。

$ pip install coverage

これを利用してあるモジュールのカバレッジを測ったりhtmlファイルにしたりできる…。けど、大量にやるには
自分でスクリプト書かないといけない。

nosetestsというのがあるのでそれを使うのがよいのではないでしょうか(投げやり

Assetメソッド一覧

公式より抜粋。floatの比較に便利そうなのにはAlmostEqualなんてのもある。
これで全部ではないので、公式を参照してほしい。

ASSERメソッドの種類 チェック対象
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)
assertAlmostEqual(a, b) round(a-b, 7) == 0
assertNotAlmostEqual(a, b) round(a-b, 7) != 0
assertGreater(a, b) -a > b
assertGreaterEqual(a, b) a >= b
assertLess(a, b) a < b
assertLessEqual(a, b) a <= b
assertRegexpMatches(s, r) r.search(s)
assertNotRegexpMatches(s, r) not r.search(s)
assertDictContainsSubset(a, b) all the key/value pairs in a exist in b

まとめ

  • Pythonのunittestモジュールを用いて、Python言語で記述されたプログラムの単体テストを行う方法を書いた
  • 実行方法は概ね以下の通り
    1. import unittestして、unittest.TestCaseを継承したクラスのなかにテストケースを書く
    • この際、AssetHOGEHOGEというようなメソッドで
    1. unittest.main()を叩くことでテストを実行する。いろいろなモジュールのテストを一挙に実行したければ、discoverを使う
  • カバレッジはunittestだけでは測れないので、他のツールを併用する(coverage,nosetests)

references

以下、参考文献、サイト。

605
562
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
605
562

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?