6
5

More than 1 year has passed since last update.

pythonのunittestについてまとめる

Last updated at Posted at 2021-11-07

今回のお題

今回はpythonのunittestについてまとめます。

unittestとはpythonの標準モジュールの一つであり、ここにはテストコードの実行に必要なクラスや関数がまとめられています。

すなわちこのモジュールをimportすることで手軽にテストコードの実装が可能になるというわけです。

本記事で扱うこと

  • unittestの基本的な仕組み
  • unittestの実行方法
  • unittestの結果の見方

本記事で扱わないこと

  • DOM操作関連
  • パラメータテスト
  • テストで用いるDBの設定方法

アプリケーションにテストを実装するとなるとDBの設定などでややこしくなるので、まずはテストのスクリプト単体で動作させることを目指します。

また、パラメータテストについてはパッケージのインストールが必要なので別で取り上げます。

unittestの仕組み

unittestの仕組みをごく単純化すると、

  • テストコードが書かれたファイルを用意する
  • そのファイルを% python3 ファイル名.pyとして実行する

これだけです。

そしてテストコードファイルの中身はこんな感じ。

tests.py
# unittestモジュールのimport
import unittest

# TestCaseクラスの定義
class MyTestCase(unittest.TestCase):
  # テスト実行関数の定義
  def test_test1(self):
    print("hoge")

# テストの実行判定
if __name__ == "__main__":
    unittest.main()

基本的にはunittest.TestCaseを継承したクラスを作成し、その中でtest_xxxというインスタンスメソッドを定義します。

このtest_xxxがテストの実行関数であり、% python3 tests.pyでテストを実行した際に呼び出されます。

最後の

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

についてですが、unittest.main()はモジュール内の全てのtest_xxxメソッドを呼び出して実行する役割、if __name__ == "__main__"はこのモジュールが他のファイルにimportされた際にはunittest.main()が実行されないようにするための役割です。

今回は詳しい説明は省きますが、おまじないだと思って書いてください。

さて、上記のtests.pyを実行しても今回はhogeと出力されるだけです。

実行関数の中身がprint("hoge")なので当たり前ですね。

実際にはこのテスト実行関数の中身を、プログラムが期待通りに動いているかどうかを調べるメソッドにすることでテストを行います。

テスト用のメソッド達

assertEqual(a, b)

a == bであることを確かめる関数。

import unittest

class MyTestCase(unittest.TestCase):
  def test_test1(self):
    self.assertEqual((10 + 5), 15) # assertEqualはTestCaseクラスの関数なのでself.の形で呼び出す。

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

assertNotEqual(a, b)

a != bであることを確かめる関数。

assertTrue(a)

a == Trueであることを確かめる関数。

assertFalse(a)

a == Falseであることを確かめる関数。

assertIn(a, b)

aがbに含まれるかどうかを調べる関数。

aとbの関係性は文字列対文字列, 文字列対リスト, リスト対リストのいずれでも可(辞書は使えない。成功失敗判定の例は以下参照)。

class MyTestCase(unittest.TestCase):
  def test_test3(self):
    self.assertIn("hoge", "hogehoge") # これはOK

  def test_test4(self):
    self.assertIn("hoge", ["hoge", "fuga"]) # これもOK

  def test_test5(self):
    # self.assertIn(["hoge", "fuga"], ["hoge", "fuga", "fuga"]) # これはNG
    # self.assertIn(["hoge", "fuga"], ["hoge", "fuga"]) # これでもNG
    pass

  def test_test6(self):
    self.assertIn(["hoge", "fuga"], [["hoge", "fuga"] ,["hoge", "hoge", "fuga", "fuga"]]) # これはOK

  def test_test7(self):
    self.assertIn(("hoge", "fuga"), (("hoge", "fuga"), ("hoge", "hoge", "fuga", "fuga"))) # これもOK

with self.assertRaises

-
引数として与えた例外の有無を確認する。

class MyTestCase(unittest.TestCase):
  def test_div(self):
    with self.assertRaises(ZeroDivisionError):
      return (5 / 0) # これはテストOK
      # return (5 / 3) これは失敗

setUpとtearDown

setUp系メソッドはテスト実行前、tearDown系メソッドはテスト実行後に呼び出される。

呼び出しのタイミングはモジュールの呼び出し前後、クラスの呼び出し前後、メソッドの呼び出し前後の計6通り。

詳細は以下。

関数名 実行タイミング 関数の種類
setUp テスト関数実行前 インスタンスメソッド
tearDown テスト関数実行後 インスタンスメソッド
setUpClass クラスの呼び出し前 クラスメソッド
tearDownClass クラスの呼び出し後 クラスメソッド
setUpModule モジュールの呼び出し前 クラスとは独立
tearDownModule モジュールの呼び出し後 クラスとは独立
import unittest
from unittest import TestCase

def setUpModule():
  print("set up module")

def tearDownModule():
  print("tear down module")

class MyTestCase1(TestCase):
  @classmethod
  def setUpClass(cls) -> None:
    print("set up class1")
    return super().setUpClass()

  @classmethod
  def tearDownClass(cls) -> None:
    print("tear down class1")
    return super().tearDownClass()

  def test_test1(self):
    print("test1")

  def test_test2(self):
    print("test2")

  def setUp(self) -> None:
    print("set up")
    return super().setUp()

  def tearDown(self) -> None:
    print("tear down")
    return super().tearDown()

class MyTestCase2(TestCase):
  @classmethod
  def setUpClass(cls) -> None:
    print("set up class2")
    return super().setUpClass()

  @classmethod
  def tearDownClass(cls) -> None:
    print("tear down class2")
    return super().tearDownClass()

  def test_test1(self):
    print("test1")

  def test_test2(self):
    print("test2")

  def setUp(self) -> None:
    print("set up")
    return super().setUp()

  def tearDown(self) -> None:
    print("tear down")
    return super().tearDown()

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

実行結果は以下。

set up module
set up class1
set up
test1
tear down
.set up
test2
tear down
.tear down class1
set up class2
set up
test1
tear down
.set up
test2
tear down
.tear down class2
tear down module

.がついている部分は、直前にテスト実行関数が成功していることを示しています。

@unitest.skip

直後のテストをスキップする。

スキップの範囲はこの記述を配置した場所によって決まり、関数の前においたのであればその関数(及びその前後のsetUpメソッド、tearDownメソッド)のみを、クラスの前においたのであればそのクラス(と付随するsetUpメソッド、tearDownメソッド、setUpClassメソッド、tearDownClassメソッド)全体をスキップする。

使用例
# @unittest.skip("not implemented yet")
class MyTestCase2(TestCase):
  @classmethod
  def setUpClass(cls) -> None:
    print("set up class2")
    return super().setUpClass()

  @classmethod
  def tearDownClass(cls) -> None:
    print("tear down class2")
    return super().tearDownClass()

  @unittest.skip("not implimented yet") # ここをスキップ
  def test_test1(self):
    print("test1")

  def test_test2(self):
    print("test2")

  def setUp(self) -> None:
    print("set up")
    return super().setUp()

  def tearDown(self) -> None:
    print("tear down")
    return super().tearDown()

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

()内にはスキップの理由を記載することが可能。

また、テスト実行結果のログにおいてはスキップ部分はsに置き換わる。

テスト結果
set up module
set up class2
sset up # ここの先頭のsがスキップ箇所
test2
tear down
.tear down class2
tear down module

また、テスト実行時に-vをオプションでつけると詳細なログが表示され、その際には@unittest.skip()の括弧内のスキップ理由も表示される。

% python3 testcase.py -v

# 結果
set up module
set up class2
test_test1 (__main__.MyTestCase2) ... skipped 'not implimented yet'
test_test2 (__main__.MyTestCase2) ... set up
test2
tear down
ok
tear down class2
tear down module

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=1)
6
5
0

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
6
5