序文
Python の単体テスト フレームワークというと、Python に関わったことのある友人が最初に思い浮かぶのは、unittest です。確かにPythonの標準ライブラリとしては優秀で、様々なプロジェクトで広く使われています。でも、何か知っていますか?実際、多くの Python プロジェクトの中で、主流の単体テスト フレームワークはこれよりもはるかに優れています。
このシリーズの記事では、現在人気のある Python 単体テスト フレームワークを紹介し、その機能と特徴について説明し、類似点と相違点を比較して、さまざまなシナリオや問題に直面したときに長所と短所を比較検討して最適なものを選択できるようにします。ユニットテストフレームワーク。
この記事ではデフォルトで Python 3 を例に挙げていますが、Python 2 で利用できない機能や異なる機能がある場合は、特別に説明します。
1. はじめに
Unittest 単体テスト フレームワークは、最初に JUnit からインスピレーションを得たもので、他の言語の主流の単体テスト フレームワークと同様のスタイルを持っています。
テストの自動化をサポートし、複数のテスト ケースでフロント (setUp) コードとクリーンアップ (tearDown) コードを共有し、複数のテスト ケースを 1 つのテスト セットに集約し、テストとレポートのフレームワークを分離します。
2. ユースケースの作成
次の簡単な例は、upper、isupper、split の 3 つの文字列メソッドをテストするための公式ドキュメントからのものです。
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError):
s.split(2)
if __name__ == '__main__':
unittest.main()
上記例では、unittest.TestCaseを継承してテストケースを作成しています。このクラスにtestで始まるメソッドを定義すると、テストフレームワークが独立したテストとして実行します。
各ユースケースでは、unittest の組み込みアサーション メソッドを使用して、テスト対象のオブジェクトの動作が期待どおりであるかどうかを判断します。次に例を示します。
test_upper テストでは、assertEqual を使用して期待値であるかどうかを確認します
test_isupper テストでは、assertTrue またはassertFalse を使用して条件が満たされていることを確認します。
test_split テストでは、assertRaises を使用して、特定の例外がスローされることを確認します。
組み込みのアサーション ステートメント Assert を使用しないのに、これほど多くのアサーション メソッドを提供してそれを使用するのはなぜだろうかと疑問に思う人もいるかもしれません。その理由は、unittest によって提供されるアサーション メソッドを使用することで、テスト フレームワークがすべてのテスト結果を集計し、実行後に有益なテスト レポートを生成できるためです。Assert を直接使用することでも、テスト対象のオブジェクトが期待を満たしているかどうかを検証するという目的を達成できますが、ユースケースが失敗した場合、エラー情報が十分ではありません。
3. ユース ケースの検出と実行
Unittest は、ユース ケースの自動 (再帰的) 検出をサポートします。
デフォルトでは、test*.py に準拠するすべてのテスト ケースは現在のディレクトリにあります。
python -munittest または python -munittest Discover を使用します。
-s パラメータで自動検出するディレクトリを指定し、-p パラメータでユースケースファイルの名前パターンを指定します
python -m Unittest Discover -s project_directory -p "test_*.py"
自動検出されたディレクトリとユースケースファイルの名前パターンを位置パラメータで指定します
python -m Unittest プロジェクト ディレクトリ "test_*.py" を検出します
Unittest は、指定されたユースケースの実行をサポートします。
テストモジュールを指定する
python -m ユニットテスト test_module1 test_module2
テストクラスを指定する
python -m ユニットテスト test_module.TestClass
テスト方法を指定する
python -m ユニットテスト test_module.TestClass.test_method
テストファイルのパスを指定します (Python 3 のみ)
python -m 単体テストテスト/test_something.py
4番目、テストフィクスチャ(Fixtures)
テスト フィクスチャは、テストの事前 (setUp) メソッドとクリーンアップ (tearDown) メソッドです。
テスト前メソッド setUp() は、データベース接続の確立など、いくつかの準備作業を行うために使用されます。テスト ケースが実行される前に、テスト フレームワークによって自動的に呼び出されます。
テスト クリーンアップ メソッド TearDown() は、データベース接続の切断などのクリーンアップ作業を行うために使用されます。テスト ケースの実行完了後 (失敗ケースも含む)、テスト フレームワークによって自動的に呼び出されます。
テストの準備方法とクリーンアップ方法にはさまざまな実行レベルを含めることができます。
1 効果的なレベル: テスト メソッド
各テスト メソッドの前後にテストの事前テスト メソッドとクリーンアップ メソッドを実行したい場合は、テスト クラスで setUp() と TearDown() を定義する必要があります。
class MyTestCase(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
2 有効レベル:テストクラス
単一のテスト クラスで pre メソッドを 1 回だけ実行し、次にテスト クラスですべてのテストを実行し、最後にクリーンアップ メソッドを実行する場合は、テスト クラスで setUpClass()と TearDownClass ()を定義する必要があります。 :
class MyTestCase(unittest.TestCase):
def setUpClass(self):
pass
def tearDownClass(self):
pass
3 有効性レベル: テストモジュール
単一のテスト モジュール内で pre メソッドを 1 回だけ実行し、次にモジュール内のすべてのテスト クラスのすべてのテストを実行し、最後にクリーンアップ メソッドを実行する場合は、 テスト モジュール:
def setUpModule():
pass
def tearDownModule():
pass
5. テストのスキップと失敗の予測
Unittest は、テストの直接または条件によるスキップをサポートし、テストの失敗の予測もサポートします。
Skip Decorator または SkipTest を介してテストを直接スキップする
SkipIf または SkipUnless を介して条件付きでテストをスキップするかどうかを指定します。
ExpectedFailure による予想されるテストの失敗
class MyTestCase(unittest.TestCase):
@unittest.skip("直接跳过")
def test_nothing(self):
self.fail("shouldn't happen")
@unittest.skipIf(mylib.__version__ < (1, 3),
"满足条件跳过")
def test_format(self):
# Tests that work for only a certain version of the library.
pass
@unittest.skipUnless(sys.platform.startswith("win"), "满足条件不跳过")
def test_windows_support(self):
# windows specific testing code
pass
def test_maybe_skipped(self):
if not external_resource_available():
self.skipTest("跳过")
# test code that depends on the external resource
pass
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "这个目前是失败的")
6、サブテスト
場合によっては、このようなテストを書きたいことがあります。テスト メソッドで異なるパラメーターを渡して同じロジック部分をテストしますが、これはテストとみなされますが、サブテストが使用されている場合は N ( と見なされます)パラメータの数) をテストします。ここに例があります
class NumbersTest(unittest.TestCase):
def test_even(self):
"""
Test that numbers between 0 and 5 are all even.
"""
for i in range(0, 6):
with self.subTest(i=i):
self.assertEqual(i % 2, 0)
この例では with self.subTest(i=i)
サブテストを定義する方法を使用していますが、この場合、1 つのサブテストの実行に失敗しても、後続のサブテストの実行には影響しません。このようにして、出力で 3 つのサブテストが失敗していることがわかります。
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
File "subtests.py", line 32, in test_even
self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
7. テスト結果の出力
簡単な例のセクションで説明した例に基づいて、 unittest
テスト実行後の結果出力を示します。
デフォルトの出力は非常にシンプルで、実行されたケースの数とそれに要した時間を示します。
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
パラメータを指定すると -v
、デフォルトの出力内容に加えて、ユースケース名が追加で表示され、詳細な出力が得られます。
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
test_upper
テストが失敗したと仮定すると 、詳細出力モードでは、結果は次のようになります。
test_isupper (tests.test.TestStringMethods) ... ok
test_split (tests.test.TestStringMethods) ... ok
test_upper (tests.test.TestStringMethods) ... FAIL
======================================================================
FAIL: test_upper (tests.test.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Uvsers/prodesire/projects/tests/test.py", line 6, in test_upper
self.assertEqual('foo'.upper(), 'FOO1')
AssertionError: 'FOO' != 'FOO1'
- FOO
+ FOO1
? +
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
test_upper
テスト方法を に self.assertEqual
変更する と assert
、テスト結果の出力には、トラブルシューティングに役立つコンテキスト情報が欠落します。
test_isupper (tests.test.TestStringMethods) ... ok
test_split (tests.test.TestStringMethods) ... ok
test_upper (tests.test.TestStringMethods) ... FAIL
======================================================================
FAIL: test_upper (tests.test.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/prodesire/projects/tests/test.py", line 6, in test_upper
assert 'foo'.upper() == 'FOO1'
AssertionError
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
HTML 形式でレポートを生成する場合は、サードパーティのライブラリ (HtmlTestRunner など) を使用して操作する必要があります。
サードパーティのライブラリをインストールした後は、python -m Unittest と --html report.html のようなメソッドを直接使用して HTML レポートを生成することはできませんが、テスト ケースを実行して取得するための少量のコードを自分で記述する必要があります。 HTMLレポート。詳細については、HtmlTestRunner の取扱説明書を参照してください。
8、unittestとpytestの違い
1 ユースケースの作成ルール
Unittest は、テストをより明確、便利、および制御可能にするために、テスト ケース、テスト スイート、テスト フィクスチャ、およびテスト ランナー関連のクラスを提供します。Unittest を使用してユースケースを作成する場合は、次のルールに従う必要があります。
(1) テストファイルは最初にunittestをインポートする必要があります
(2) テストクラスはunittest.TestCaseを継承する必要があります。
(3) テストメソッドは「test_」で始まる必要があります
(4) テストクラスには、unittest.main() メソッドが必要です。
pytest は、Python 用のサードパーティのテスト フレームワークであり、unittest に基づいた拡張フレームワークであり、unittest よりも簡潔で効率的です。pytest を使用してユースケースを作成するには、次のルールに従う必要があります。
(1) テストファイル名は「test_」で始まるか「_test」で終わる必要があります(例:test_ab.py)
(2) テストメソッドは「test_」で始まる必要があります。
(3) テストクラス名は「Test」で始まります。
概要: pytest は、unittest ユースケースのコードを変更することなく、unittest スタイルのテスト ケースを実行でき、互換性が優れています。pytest プラグインには、エラーが発生したときにユースケースを再実行するために使用できる flask プラグインなどの豊富なプラグインがあり、xdist プラグインもあります。デバイス。
2 ユースケースの前後
(1) Unittest は、各ユースケースの実行の前後に 1 回実行される setUp/tearDown を提供します。setUpClass と TearDownClass は、ユースケースの実行前後に 1 回だけ実行されます。
# unittset前置条件.py
import unittest
class Test(unittest.TestCase): # 继承unittest中的TestCase
@classmethod
def setUpClass(cls) -> None: # setUpClass:所有用例执行之前会执行一次,如:打开文件,链接数据库
print('setUpClass')
@classmethod
def tearDownClass(cls) -> None: # tearDownClass:所有用例执行之后会执行一次,如:注册后删掉数据
print('tearDownClass')
@classmethod
def setUp(self) -> None: # setUp:每条用例执行前都会先执行setUp,如:
print('setUp')
@classmethod
def tearDown(self) -> None: # tearDown:每条用例执行后都会先执行tearDown,如:
print('tearDown')
def testB(self): # 用例执行顺序:以test后字母开头排序
print('testB')
def testA(self):
print('testA')
def testZ(self):
print('testZ')
if __name__ == "__main__":
# unittest.main() # 不产生测试报告
pass
その実行結果は次のとおりです。
Ran 3 tests in 0.003s
Launching unittests with arguments python -m unittest 用例前置条件.Test in /Users/ray/PycharmProjects/day10
OK
setUpClass
tearDownClass
Process finished with exit code 0
setUp
testA
tearDown
setUp
testB
tearDown
setUp
testZ
tearDown
その実行結果は次のとおりです。
collected 4 items
test_module.py setup_module:整个.py模块只执行一次
setup_function:每个用例开始前都会执行
正在执行测试模块----test_one
.teardown_function:每个用例结束后都会执行
setup_function:每个用例开始前都会执行
正在执行测试模块----test_two
Fteardown_function:每个用例结束后都会执行
setup_class:所有用例执行之前
setup:每个用例开始前都会执行
正在执行测试类----test_three
.teardown:每个用例结束后都会执行
setup:每个用例开始前都会执行
正在执行测试类----test_four
Fteardown:每个用例结束后都会执行
teardown_class:所有用例执行之后
teardown_module:整个test_module.py模块只执行一次
方法 2: pytest のフィクスチャ メソッド
# conftest.py
# -*- coding: utf-8 -*-
import pytest
@pytest.fixture(scope="function")
def login():
print("请先输入账号和密码,然后登陆")
yield
print("退出登陆")
# test_1.py
# -*- coding: utf-8 -*-
import pytest
def test_fix1(login):
print("test_fix1 in test_1.py:需要登陆再执行操作")
def test_fix2():
print("test_fix2 in test_1.py:不需要登陆再执行操作")
def test_fix3(login):
print("test_fix3 in test_1.py:需要登陆再执行操作")
if __name__ == "__main__":
pytest.main(['-s', 'test_1.py'])
# test_2.py
# -*- coding: utf-8 -*-
import pytest
def test_fix3():
print("test_fix3 in test_2.py:不需要登陆再执行操作")
def test_fix4(login):
print("test_fix4 in test_2.py:需要登陆再执行操作")
if __name__ == "__main__":
pytest.main(['-s', 'test_2.py'])
その実行結果は次のとおりです。
pytest -s test_1.py
collected 3 items
test_1.py 请先输入账号和密码,然后登陆
test_fix1 in test_1.py:需要登陆再执行操作
.退出登陆
test_fix2 in test_1.py:不需要登陆再执行操作
.请先输入账号和密码,然后登陆
test_fix3 in test_1.py:需要登陆再执行操作
.退出登陆
3 断言
(1)unittest は、assertEqual、assertIn、assertTrue、assertFalse を提供します。
assertEqual: アサーションの最初のパラメータと 2 番目のパラメータが等しいかどうかを判断し、等しくない場合、テストは失敗します。
使用法:assertIn(キー、コンテナ、メッセージ)
key: 指定されたコンテナ内に存在するかどうかを確認する文字列
コンテナ: キー文字列を検索する文字列
message: テスト メッセージが失敗したときに表示されるメッセージである文字列ステートメント。
assertIn: 単体テストで文字列が別の文字列内に含まれているかどうかを確認するために使用されます。この関数は 3 つの文字列パラメータを入力として受け取り、アサーション条件に基づいてブール値を返します。キーがコンテナ文字列に含まれている場合は true を返し、それ以外の場合は false を返します。
使用法:assertIn(キー、コンテナ、メッセージ)
パラメータ:assertIn() は、次の 3 つのパラメータの説明を受け入れます。
key: 指定されたコンテナ内に存在するかどうかを確認する文字列
コンテナ: キー文字列を検索する文字列
message: テスト メッセージが失敗したときに表示されるメッセージである文字列ステートメント。
assertTrue:真かどうか判断する
assertFalse: falseかどうかを判定する
(1) pytest は、assert 式を直接使用します。
assert: 式を判定し、式の条件が false の場合に例外をトリガーするために使用されます。
4件のレポート
(1)unittest は HTMLTestRunnerNew ライブラリを使用します。
(2) pytest には pytest-HTML プラグインと allure プラグインがあります。
5 再実行失敗
(1)unittestにはこの機能はありません。
(2) pytest は、ユースケースの実行失敗の再実行、pytest-rerunfailures プラグインをサポートしています。
6 パラメータ化
(1) 単体テストは ddt ライブラリまたはパラメータ化されたライブラリに依存する必要があります。
# 单元测试.py
import unittest
import myFunction
import HTMLTestRunner
import HTMLTestRunnerNew # 测试报告丰富版本
import parameterized # 参数化
class TestAdd(unittest.TestCase):
'''测试add方法'''
@parameterized.parameterized.expand( # 传参为二维数组
[[1, 2, 3, '参数化1'],
[-1, 2, 3, '参数化2'],
[2, 4, 7, '参数化3']]
)
def testParamAdd(self, a, b, c, desc):
self._testMethodDoc = desc # 使用这个_testMethodDoc参数传递
result = myFunction.add(a, b)
self.assertEqual(c, result, '预期结果是%s,实际结果是%s' % (c, result))
(2) pytest は @pytest.mark.parametrize デコレーターを直接使用します。
@allure.epic("SOS接口自动化测试")
class TestCaseRunner:
@allure.feature("过程管理/风险处理/干预任务报表(新)-查询")
@pytest.mark.parametrize("case", CaseExcuteUtils.get_case_list("soscases/progressManagement/taskreport", case_tag))
def test_task_report(self, case):
"""
参数化执行测试用例
:param case:
:return:
"""
print(case.description)
allure.dynamic.title(case.description)
CaseExcuteUtils.excute_case(case, data)
7 ユースケース分類の実行
(1) Unittest はデフォルトですべてのテスト ケースを実行しますが、テストスイートをロードすることで一部のモジュール テスト ケースを実行できます。
(2) pytest は @pytest.mark を通じてテスト ケースをマークし、コマンドを実行してパラメーター "-m" を追加して、マークされたユース ケースを実行できます。
9. まとめ
Unittest は、Python 標準ライブラリによって提供される単体テスト フレームワークであり、使いやすく強力であり、日常的なテストのニーズを満たすことができます。サードパーティのライブラリを導入せずに単体テストを行う場合に最適です。
次の記事では、サードパーティの単体テスト フレームワークの names と names2 を紹介し、多くの開発者が最初に選択するようになった、unittest と比較したその改善点について説明します。
最後に、私の記事を注意深く読んでくださった皆さんに感謝します。互恵性は常に必要です。それほど価値のあるものではありませんが、必要な場合はそれを取り上げることができます。
これらの資料は、[ソフトウェア テスト] の友人にとって最も包括的で完全な準備倉庫となるはずです。この倉庫は、最も困難な旅を乗り越える何万人ものテスト。お役に立てれば幸いです。エンジニア