Python Unittest と Pytest
この記事の主な目的は、Python の単体テストで最もよく使用される 2つのフレームワーク、unittest
と pytest
、それぞれの長所と短所、およびどちらを優先するかについて説明することです。
Python unittest
vs Pytest
ソフトウェアを作成するときは、開発プロセス全体でエラー チェックのプロセスを維持する必要があります。 ソフトウェアがリリース段階に達すると、その使用中に発生するバグの数が最小限に抑えられます。
Python には、さまざまな入力を与えて動作をチェックすることで、記述されたコードをテストできるさまざまなテスト フレームワークもあります。
エラーが発生した場合は、アプリケーションの最初のリリース後のホットフィックス
ではなく、開発段階で修正できます。
コード例:
class Calculate:
def CheckPrime(self, a):
for i in range(a):
if a % i:
return False
return True
def CalcFact(self, a):
if a == 1:
return a
else:
return a * self.fact(a - 1)
上記のコードには、CheckPrime
と CalcFact
という名前の 2つの関数が含まれています。これらの関数は、その名前から明らかなように、素数をチェックして階乗を計算します。
Calculate
メソッドがスムーズに機能することを確認するには、さまざまな出力を与えることによって発生する可能性のあるエラーをチェックすることが不可欠です。
では、どうすればそれができるでしょうか? コードにエラーがないことを確認するために、さまざまなテスト フレームワークを使用してテスト ケースを記述し、その上でコードをテストして、コードの整合性をチェックできます。
多くのテスト フレームワークがありますが、最も広く使用されているのは unittest
と pytest
の 2つです。 以下でそれらを1つずつ調べてみましょう。
unittest
フレームワークによる単体テスト
unittest
は、Python 標準ライブラリに含まれている単体テスト フレームワークです。 このフレームワークは、単体テスト用の Java フレームワークであるJUnit
に触発されました。
unittest
の動作について説明する前に、unittest
で一般的に使用される用語 (他の関連するフレームワークでも使用される) を知っておくことが不可欠です。
テストケース
– テストの最小単位 – 通常、単一のテストで構成されますテスト スイート
– グループ化されたテスト ケース – 通常は次々に実行されますTest Runner
– テストケースとスイートの実行を調整および処理します
unittest
フレームワークを使用してテスト ケースを作成する
Python の標準ライブラリには既に unittest
が含まれているため、unittest
を使用して単体テストを書き始めるために外部モジュールをダウンロードする必要はありません。
unittest
モジュールをインポートした後に開始できます。 それでは、前に実行したコードに焦点を当てましょう。
コード例:
class Calculate:
def CheckPrime(self, a):
for i in range(a):
if a % i:
return False
return True
def CalcFact(self, a):
if a == 1:
return a
else:
return a * self.fact(a - 1)
unittest
を使用してテスト ケースを作成するには、特定の構文に従う必要があります。つまり、テスト クラスは unittest.TestCase
の子であり、そのメソッドは test_
で始まる必要があります。
次のコードを検討してください。
import unittest
class Calculate:
def CheckPrime(self, a):
for i in range(2, a):
if a % i == 0:
return False
return True
def CalcFact(self, a):
if a == 1:
return a
else:
return a * self.CalcFact(a - 1)
class TestCalc(unittest.TestCase):
def test_CheckPrime(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CheckPrime(2), True)
self.assertEqual(calc.CheckPrime(3), True)
self.assertEqual(calc.CheckPrime(4), False)
self.assertEqual(calc.CheckPrime(80), False)
def test_CheckFact(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CalcFact(2), 2)
self.assertEqual(calc.CalcFact(3), 6)
出力:
PS D:\Unittest> python -m unittest a.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
出力から判断すると、すべてのアサーションが成功したため、すべてのテスト ケースが成功したことがわかります。
次に、テスト ケースが失敗するケースを試してみましょう。
def test_CheckFact(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CalcFact(2), 2)
self.assertEqual(calc.CalcFact(3), 6)
# Supposed to throw an error
self.assertEqual(calc.CalcFact(0), 0)
出力:
PS D:\Unittest> python -m unittest a.py
======================================================================
ERROR: test_CheckFact (a.TestCalc)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Python Articles\a.py", line 34, in test_CheckFact
self.assertEqual(calc.CalcFact(0), 0) # Supposed to throw an error
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
[The previous line is repeated 974 more times]
File "D:\Python Articles\a.py", line 12, in CalcFact
if (a == 1):
RecursionError: maximum recursion depth exceeded in comparison
----------------------------------------------------------------------
Ran 2 tests in 0.004s
FAILED (errors=1)
コードから明らかなように、python -m unittest <name_of_script.py>
を使用してスクリプトを実行します。
unittest
モジュールは特定の形式で与えられたスクリプト ファイルを処理するため、このコードはテスト クラスのメソッドを呼び出さなくても機能します。
スクリプトには TestCalc
が含まれているため、unittest.TestCase
の子クラスは Test Runner
によって自動的にインスタンス化されます。
インスタンス化の後、テスト メソッドはクラス内で検出され、順番に実行されます。 メソッドが test method
と見なされるためには、test_
で始まる必要があります。
テスト メソッドが見つかると、それらは順番に呼び出されます。 この場合、test_CheckPrime
と test_CalcFact
の両方が呼び出されます。 実装ではアサーションがチェックされ、予期しない動作が発生した場合は出力にエラーがスローされます。
エラーが含まれていたテスト ケースから、コードの記述方法が原因で、CalcFact
メソッドで無限再帰が発生し始めたと推測できますが、これはテスト ケースのおかげで修正できるようになりました。
なぜエラーが発生するのか疑問に思っている場合は、初期条件が 1 未満の数値をチェックしていないことが原因です。
unittest
フレームワークの長所と短所
unittest
を使用する利点のいくつかを以下に示します。
- Python 標準ライブラリに含まれています
- 関連するテスト ケースを単一のテスト スイートに昇格
- スピーディーなテスト収集
- 正確なテスト時間
unittest
には次の欠点があります。
- わかりにくい場合があります
- カラー出力なし
- 冗長すぎる可能性があります
Pytest
フレームワークによる単体テスト
unittest
とは異なり、Pytest
は組み込みモジュールではありません。 個別にダウンロードする必要があります。 ただし、Pytest のインストールは比較的簡単です。 そのためには、pip
を使用して次のコマンドを実行します。
pip install pytest
Pytest
を使用してテスト ケースを作成する
Pytest
を使っていくつかのテストケースを書いてみましょう。 ただし、始める前に、テスト ケースの作成において Pytest
が unittest
とどのように異なるかを見てみましょう。 Pytest
で書かれた単体テストの場合、次のことを行う必要があります。
- 別のディレクトリを作成し、テストするスクリプトを新しく作成したディレクトリに配置します。
test_
で始まるか_test.py
で終わるファイルにテストを記述します。 例として、test_calc.py
またはcalc_test.py
があります。
Pytest
を使用してテスト ケース用に記述された次のコードを検討してください。
def test_CheckPrime():
calc = Calculate()
# Passing different outputs
assert calc.CheckPrime(2) == True
assert calc.CheckPrime(3) == True
assert calc.CheckPrime(4) == False
assert calc.CheckPrime(80) == False
def test_CheckFact():
calc = Calculate()
# Passing different outputs
assert calc.CalcFact(2) == 2
assert calc.CalcFact(3) == 6
# assert calc.CalcFact(0) == 0 # Supposed to throw an error
出力:
============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items
test_a.py
[100%]
=============================================================== 2 passed in 0.04s ===============================================================
ここで、失敗したテスト ケースを使用します。
============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items
test_a.py .F
[100%]
=================================================================== FAILURES ====================================================================
________________________________________________________________ test_CheckFact _________________________________________________________________
def test_CheckFact():
calc = Calculate()
# Passing different outputs
assert calc.CalcFact(2) == 2
assert calc.CalcFact(3) == 6
> assert calc.CalcFact(0) == 0 # Supposed to throw an error
test_a.py:50:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
.
.
.
.
.
RecursionError: maximum recursion depth exceeded in comparison
test_a.py:10: RecursionError
============================================================ short test summary info ============================================================
FAILED test_a.py::test_CheckFact - RecursionError: maximum recursion depth exceeded in comparison
========================================================== 1 failed, 1 passed in 2.42s ==========================================================
Pytest
を使用して記述されたテスト ケースは、unittest
よりも少し単純です。 unittest.TestCase
の子クラスを作成する代わりに、メソッドの先頭に test_
を付けてテスト関数を書くことができます。
Pytest
フレームワークの長所と短所
Python で Pytest
フレームワークを使用する利点を次に示します。
-
コンパクトなテスト スイート
-
最小限の定型コード
-
プラグインのサポート
-
きちんとした適切な出力プレゼンテーション
また、以下にリストされている欠点もあります。
-
他のフレームワークと互換性がないことが多い
Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.
LinkedIn