unittest

【unittest】Pythonプログラムをunittestでテストする方法

【unittest】Pythonプログラムをunittestでテストする方法
naoki-hn

Python プログラムのテストを実行するためのテストフレームワークである unittest モジュールについて解説します。

unittest とは

unittestは、Python 標準のテストフレームワークです。unittest は、もともとは Java の JUnit に触発されて作成されたため、JUnit 等のテストフレームワークを使用したことがある人は似た感覚で使い始められます。

テスト駆動開発 (TDD:Test-Driven Development) という自動テストを中心にした高品質のソフトウェア開発をするための方法論があり、Python コミュニティでは幅広く利用されています。テスト駆動開発においては、unittest のようなテストフレームワークは非常に重要です。テスト駆動開発については「エキスパートPythonプログラミング」という書籍にも記載があるため、参考にしてください。

この記事では、Python の標準テストフレームワークである unittest を使用したテストの基本的な使い方を紹介します。

Pythonのテストフレームワークは他にもありますが、その中でも pytest というテストフレームワークは非常におすすめです。pytest の使い方については「Pythonプログラムをpytestでテストする方法」を参考にしてください。

unittest の使い方

プログラムのテストのイメージに慣れていない方のために、まずは以下の図を使って簡単なテストイメージを紹介します。以降で具体的なプログラムも使って説明をしていきますので、ここではまずイメージをつかんでください。

unittest テストイメージ

sample.py というプログラムを作成しテストしたいと考えているとします。このsample.py には Sample というクラスがあり x と y を足して 2 倍にするだけのシンプルな add_and_double というメソッドが定義されています。

このプログラムをテストしたい場合には「test_sample.py」といったテストプログラムを作成します。unittest では test という名前から始まるファイルを探索してまとめて実行することも可能です。テストプログラム内では、sample をインポートして、unittest.TestCase を継承して TestSample のようなクラスを作成します。

unittest では、TestCase を継承したクラス内で「test」で始まるメソッドをテストとして実行します。なお、unittestunittest.main() で実行します。

テストプログラムでは「self.assertEqual(self.temp.add_and_double(1, 1), 4)」のように「assertXXXX」といった unittest.TestCase で定義されているメソッドを使って判定します。この判定式の結果が一致しない場合にはテスト失敗となります。

なお、assertXXXX については、一致を確認する assertEqual、真 (True) かを判定する assertTrue など各種用意されています。各種メソッドの詳細は unittest.TestCase の公式ドキュメントのこちらを確認してください。

上記例では、x=1, y=1 として引数を渡していますので、(1 + 1) × 2 = 4 を add_and_double メソッドは返却してほしいわけですが、異なる値が返ってくるということはメソッドの処理に何か問題があるということになります。なお、assertXXXX は 1 つのメソッドに複数行でも書くことができます。

テストプログラム内にて、テスト観点毎にテストを実行するメソッドをどんどん追加していくわけです。unittest を実行するとテストプログラム内に定義されているテスト用メソッドを順番に実行し、それぞれが成功したか失敗したかを出力してくれます。

まずはテストに関して上記のようなイメージを持ってもらえればと思います。では、以降では unittest の実行方法や具体的な例を使った説明に進んでいきたいと思います。

unittest の実行方法

unittest では、テストプログラムに unittest.main() を記述することでテストが実行されます。

if __name__ == "__main__":
    # unittestを実行する
    unittest.main()

上記の記載があるテストプログラムを実行すると、unittest.TestCase を継承したクラスの test から始まるメソッドをテスト対象として unittest によるテストが実行されます。なお、「-v」を入れておくと詳細情報が表示されます。

python test_sample.py -v

通常、複数のテストプログラムをまとめて実行したくなるかと思います。unittestは、以下のようなコマンドを使用することで、test から始まる名前のファイルをテスト対象として判断してくれるため該当フォルダ内を探索して test から始まる名前のファイルを順番にテストしてくれます。

python -m unittest -v

unittest によるテストの実装例

ここからは、より具体的にunittestを使用したテスト実装例を見ていきます。まずは、以降で説明する関連プログラムをまとめて示します。テスト対象の sample.py、テストプログラムの test_sample.py があります。詳細内容は後ほど順番に説明します。

sample.py:テスト対象プログラム

class Sample:
    """Sampleクラス"""

    def add_and_double(self, x, y):
        """xとyを足して2倍した値を返却する

        Args:
            x: 入力値1
            y: 入力値2
        """
        # intでない場合は、ValueErrorとする
        if not isinstance(x, int) or not isinstance(y, int):
            raise ValueError

        # 計算処理
        result = x + y
        result *= 2

        return result

test_sample.py:テストプログラム

import unittest

import sample


# スキップ用フラグ
SKIP_FLAG = True


class TestSample(unittest.TestCase):
    """Sampleクラスのテスト用クラス"""

    @classmethod
    def setUpClass(cls):
        print("\nStart TestSample")
        cls.temp = sample.Sample()

    @classmethod
    def tearDownClass(cls):
        print("\nEnd of TestSample")
        del cls.temp

    def setUp(self):
        print("\nTest Start")

    def tearDown(self):
        print("End of Test")

    def test_add_and_double(self):
        """テストケース1: 正常計算"""
        self.assertEqual(self.temp.add_and_double(1, 1), 4)
        self.assertEqual(self.temp.add_and_double(2, 2), 8)
        # self.assertEqual(self.temp.add_and_double(2, 2), 4)

    def test_add_and_double_raise(self):
        """テストケース2: 例外処理"""
        with self.assertRaises(ValueError):
            self.temp.add_and_double("1", 1)
        with self.assertRaises(ValueError):
            self.temp.add_and_double(1, "1")
        with self.assertRaises(ValueError):
            self.temp.add_and_double("1", "1")
        # with self.assertRaises(ValueError):
        #     self.temp.add_and_double(1, 1)

    @unittest.skip("skip理由:xxxxxxxxxx")
    def test_add_and_double_skip(self):
        """スキップ確認用"""
        assert self.temp.add_and_double(1, 1) == 4

    @unittest.skipIf(SKIP_FLAG is True, "条件付きskip理由:xxxxxxxxx")
    def test_add_and_double_skipif(self):
        """条件付きスキップ確認用"""
        assert self.temp.add_and_double(1, 1) == 4


if __name__ == "__main__":
    # unittestを実行する
    unittest.main()

実行結果「python -m unittest -v」または「python test_sample.py -v」実行

> python -m unittest -v   

Start TestSample
test_add_and_double (test_sample.TestSample)
テストケース1: 正常計算 ...
Test Start
End of Test
ok
test_add_and_double_raise (test_sample.TestSample)
テストケース2: 例外処理 ...
Test Start
End of Test
ok
test_add_and_double_skip (test_sample.TestSample)
スキップ確認用 ... skipped 'skip理由:xxxxxxxxxx'
test_add_and_double_skipif (test_sample.TestSample)
条件付きスキップ確認用 ... skipped '条件付きskip理由:xxxxxxxxx'

End of TestSample

----------------------------------------------------------------------
Ran 4 tests in 0.004s

OK (skipped=2)

以降では上記プログラムを使ってポイントとなるテスト記載方法を説明していきます。

テストの基本要素

テストの基本構成についてまず説明します。以下は、test_sample.py 内の TestSample クラスの一部抜粋です。

class TestSample(unittest.TestCase):
    """Sampleクラスのテスト用クラス"""

    def test_add_and_double(self):
        """テストケース1: 正常計算"""
        # print("実行:test_add_and_double")
        self.assertEqual(self.temp.add_and_double(1, 1), 4)
        self.assertEqual(self.temp.add_and_double(2, 2), 8)

unittest を使用したテストでは、上記の TestSample クラスのように unittest.TestCase を継承したテストクラスを作成、その中にテスト用メソッドを記載していきます。

test_add_and_double(self) は、正常計算結果を確認することを意図して作成したテストメソッドです。

テスト結果の判定を行う場合には、上記の assertEqual のように unittest に実装されているテスト判定用のメソッドを使用します。第 1 引数は判定対象のメソッドの返り値で、第 2 引数が正解として比較する値を指定しています。この判定が問題なく処理されれば、テストは OK となります。

他にも判定用メソッドは色々ありますので、各種メソッドの詳細は unittest.TestCase の公式ドキュメントのこちらを確認してください。

上記で記載しているケースは問題なく処理がされるため、実行結果を見ると以下のように ok となります。

test_add_and_double (test_sample.TestSample)
テストケース1: 正常計算 ...
ok

では、失敗する状況を考えてみます。例えば「self.assertEqual(self.temp.add_and_double(2, 2), 4)」のようなテストをした場合には、以下のように失敗となります。これは (2 + 2) × 2 = 8ですので、テストケースが間違っている例です。

======================================================================
FAIL: test_add_and_double (test_sample.TestSample)
テストケース1: 正常計算
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\PythonProjects\python-tech-sample-source\python-tech-sample-source\python-libraries\unittest\test_sample.py", line 39, in test_add_and_double
    self.assertEqual(self.temp.add_and_double(2, 2), 4)
AssertionError: 8 != 4

想定したものと出力結果が異なる場合には FAIL として失敗が表示されます。

なお、テストの失敗としては「テスト対象プログラムの処理結果が誤っている場合」や上記のように「テストケース自体が誤っているような場合」等、色々ありますのでエラーを見つつ、プログラムを修正していくようにしてください。

例外処理

次に、例外処理をテストする場合のテストメソッドの記載方法です。

    def test_add_and_double_raise(self):
        """テストケース2: 例外処理"""
        with self.assertRaises(ValueError):
            self.temp.add_and_double("1", 1)
        with self.assertRaises(ValueError):
            self.temp.add_and_double(1, "1")
        with self.assertRaises(ValueError):
            self.temp.add_and_double("1", "1")

今回のテスト対象プログラム sample.pyadd_and_double メソッドでは、int ではない引数が渡された場合に ValueError を返却することになっています。

このような場合には、上記のように self.assertRaises を使用した with 句を用いて確認します。もし「self.temp.add_and_double(1, 1)」のように例外をあげないような処理を with 句内に記載するとテストは失敗となります。

setUptearDown

次に、各テストの実行前後にテストのセットアップや完了時処理をするための setUptearDown についての記載方法です。

    @classmethod
    def setUpClass(cls):
        print("\nStart TestSample")
        cls.temp = sample.Sample()

    @classmethod
    def tearDownClass(cls):
        print("\nEnd of TestSample")
        del cls.temp

    def setUp(self):
        print("\nTest Start")

    def tearDown(self):
        print("End of Test")

setUp は、テストの前に準備をするために用意されているもので setUpClasssetUp があります。クラス生成時のセットアップが setUpClass で、メソッドの実行時のセットアップが setUp です。

一方で、tearDown は、テストの後に処理を実行するためのもので、tearDownClasstearDown があります。それぞれクラスとメソッドに対応します。

上記例では、テスト開始と終了、各メソッドの開始と終了が分かるように print しています。実行結果を見ると以下のように表示されます。

> python -m unittest -v

Start TestSample
test_add_and_double (test_sample.TestSample)
テストケース1: 正常計算 ...
Test Start
End of Test
ok
test_add_and_double_raise (test_sample.TestSample)
テストケース2: 例外処理 ...
Test Start
End of Test
ok
test_add_and_double_skip (test_sample.TestSample)
スキップ確認用 ... skipped 'skip理由:xxxxxxxxxx'
test_add_and_double_skipif (test_sample.TestSample)
条件付きスキップ確認用 ... skipped '条件付きskip理由:xxxxxxxxx'

End of TestSample

----------------------------------------------------------------------
Ran 4 tests in 0.004s

OK (skipped=2)

setUpClasstearDownClassprint は最初と最後のみ表示されていることが分かります。一方で setUptearDownprint は各メソッド実行前後で表示されます。

また、setUpClass ではあらかじめテスト対象のクラス Sample をオブジェクト化しており、tearDownClass では (特に必須なわけではないですが) オブジェクトを del しています。このようにすることで、各テストメソッドでは毎回クラスをオブジェクト化する必要がなくなりますし、テスト終了時の処理を必要に応じて記載できます。

テスト前後のタイミングで何か処理を記載したい場合には setUptearDown をうまく活用しましょう。

テストのスキップ

テストを多く記載していると一部のテストメソッドをスキップして処理を実行したくなる場合があります。テストをスキップする場合の記載方法について見ていきましょう。

通常のスキップ

一部のテストメソッドをスキップしたい場合には、以下のように @unittest.skip デコレータを使用します。引数にスキップ理由などを記載して実行時に表示可能です。

    @unittest.skip("skip理由:xxxxxxxxxx")
    def test_add_and_double_skip(self):
        """スキップ確認用"""
        assert self.temp.add_and_double(1, 1) == 4

スキップされると以下のような実行結果となります。

test_add_and_double_skip (test_sample.TestSample)
スキップ確認用 ... skipped 'skip理由:xxxxxxxxxx'
条件付きスキップ

条件に一致する場合にテストをスキップしたいときもあるかと思いますが、その場合は以下のように @unittest.skipIf デコレータを使用して条件を引数に渡します。スキップ理由も引数に渡すことで表示可能です。

# スキップ用フラグ
SKIP_FLAG = True

...(省略)...

    @unittest.skipIf(SKIP_FLAG is True, "条件付きskip理由:xxxxxxxxx")
    def test_add_and_double_skipif(self):
        """条件付きスキップ確認用"""
        assert self.temp.add_and_double(1, 1) == 4

スキップされると以下のような実行結果となります。

test_add_and_double_skipif (test_sample.TestSample)
条件付きスキップ確認用 ... skipped '条件付きskip理由:xxxxxxxxx'

まとめ

Python プログラムのテストを実行するためのテストフレームワークである unittest モジュールについて解説しました。unittest は Python 標準のテストフレームワークです。

この記事内では、unittest の実行方法やテスト実装例について紹介しました。また、setUpteaDown を使ったテスト準備やテストのスキップも紹介しています。

unittest は、Python 標準のテストフレームワークなので皆さんも色々と使い方を調べてもらえたらと思います。

また、テスト駆動開発 (TDD:Test-Driven Development) という自動テストを中心にした高品質のソフトウェア開発をするための方法論は Python コミュニティでは幅広く利用されています。unittest はテスト駆動開発においても使用できるテストフレームワークです。テスト駆動開発については「エキスパートPythonプログラミング」という書籍にも記載があるため、参考にしてください。

unittest の公式ドキュメントはこちらを参照してください。

ソースコード

上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

あわせて読みたい
【Python Tech】プログラミングガイド
【Python Tech】プログラミングガイド
ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。 私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

プログラミング言語はこれまでC、C++、JAVA等を扱ってきましたが、最近では特に機械学習等の分析でも注目されているPythonについてとても興味をもって取り組んでいます。これまでの経験をもとに、Pythonに興味を持つ方のお役に立てるような情報を発信していきたいと思います。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました