👇それでは下記の記事の続きをやっていこう!
前回までは、csv, jsonファイルなどの “File I/Oについて(ファイル入出力)” 学習してきました。
今回は、pythonコードのテストについて学習していきましょう!
コードがある一定の品質を保っているのかを確認する大事な作業になりますので、テストエンジニアではなくても一緒に学習していこう🤓
Contents
Pythonのテストについて
✅ 実装したコードは必ずテストを行うこと
✅ 機能を開発するだけでなく、”品質”を保ちながら開発することが求められる
✅ テストの種類
・コードを実行してテスト
・自動で実行するテスト
・他のモジュールや機能との繋ぎ込みのテスト
・ユーザー側での動作テスト etc…
✅ 単体テスト(UnitTest)と結合テスト(IntegrationTest)
・コンポーネント単位、コンポーネント間のテスト
assert
✅ assert の後ろにTrueとなるか判断したい値を書く
✅ テストコードは通常、テストスクリプトにひとまとめに書く
👇簡単なassertを使った例になります。
1 2 3 4 5 6 7 8 |
# assert a = 1 b = 1 c = 2 # assertの後ろにTrueとなるか判断したい値を書く assert a + b == 3, "a + b is equal to 2" # AssertionError: a + b is equal to 2 # Trueとなる場合はエラーは吐き出さない assert a + c == 3, "a + c is equal to 3" |
👇テストスクリプトを作成してテストしてみる
1 2 3 4 5 6 |
# <power.py> def power(base, exp): return base * exp def times(num1, num2): return num1 + num2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# <test_power.py> from power import power, times def test_power(): base = 2 exp = 3 assert power(base, exp) == 8, "This should be 8" def test_times(): num1 = 2 num2 = 3 assert times(num1, num2) == 6, "This should be 6" if __name__ == "__main__": test_power() # AssertionError: This should be 8 test_times() # test_power()でエラーが出ると、このコードは実行されない→テスト用のライオブラリを使用する |
上記のテストスクリプトは問題があります、気づきましたでしょうか?
実はtest_power()でエラーが発生すると、test_times()の関数は実行されません。
本当は2つの関数のテストを行いたいのに、両方テストできないって問題ですよね?
このように単純にスクリプトでテストを実施するのは限界があります。
そこでテストランナーのライブラリを使用してテストを実施していきます。
その詳細を下記に記載しますね。
Test runner
✅ コードをテストする際に、途中エラーで止まってしまう箇所があってもプログラムが最後まで実行され、エラー内容を出力してくれるツール
✅ テスト結果をチェックしてくれたり、デバッグしやすいように結果を表示してくれる
✅ テストスクリプトは一つのフォルダにまとめて格納しておく
✅ unittest : Pythonの標準ライブラリ
✅ pytest : 有名で非常に多くのPJTで利用されるテストフレームワーク
👇unittestを使ってテストを実行する
1 2 3 4 5 6 |
# <power.py> def power(base, exp): return base * exp def times(num1, num2): return num1 + num2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# <test_power.py> import unittest from power import power, times class TestMyMethod(unittest.TestCase): def test_power(self): base = 2 exp = 3 # assert power(base, exp) == 8, "This should be 8" self.assertEqual(power(base, exp), 8) def test_times(self): num1 = 2 num2 = 3 # assert times(num1, num2) == 6, "This should be 6" self.assertEqual(times(num1, num2), 6) if __name__ == "__main__": unittest.main() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[~] $ python -m unittest test_power.py FF ==================================================================== FAIL: test_power (__main__.TestMyMethod) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/<username>/PycharmProjects/pythontest/test_power.py", line 10, in test_power self.assertEqual(power(base, exp), 8) AssertionError: 6 != 8 ==================================================================== FAIL: test_times (__main__.TestMyMethod) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/<username>/PycharmProjects/pythontest/test_power.py", line 16, in test_times self.assertEqual(times(num1, num2), 6) AssertionError: 5 != 6 ---------------------------------------------------------------------- # Ran 2 tests in 0.001s # # FAILED (failures=2) |
例外ケースのテスト
✅ 誤ったデータを入れたらきちんとエラーを返すかテストする
✅ 考えられる例外は無数にあるため、ある程度絞ってテストを実施する
✅withステートメントを使ってテストをする
unittestのassertメソッド一覧
- 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)
1 2 3 4 5 6 |
# <power.py> def power(base, exp): return base ** exp def times(num1, num2): return num1 * num2 |
1 2 3 4 5 6 7 8 9 10 11 |
class TestMyMethod(unittest.TestCase): def test_power(self): base = 2 exp = 3 self.assertEqual(power(base, exp), 8) # 正しくエラーを出すかテスト(OKが返ってくれば正しくエラーを出してくれている) with self.assertRaises(TypeError): power("3", "2") if __name__ == "__main__": unittest.main() |
1 2 3 4 5 6 |
<Command> [~] $ python -m unittest.py .. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK |
pytest
✅ pythonのテストフレームワークで、多くのプロジェクトが採用
✅ python標準のassertを使って簡潔に書ける
✅ 標準ライブラリではないので、$ pip install pytestでインストールする
👇pytestを使ってテストを実行する
1 2 3 4 5 6 7 |
# <power.py> def power(base, exp): return base ** exp def times(num1, num2): return num1 * num2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# test_power_pytest.py import pytest from power import power, times def test_power(): base = 2 exp = 3 assert power(base, exp) == 8 with pytest.raises(TypeError): power("3", "2") def test_times(): num1 = 2 num2 = 3 assert times(num1, num2) == 6 |
1 2 3 4 5 6 7 8 |
<Command> [~] $ pytest test_power_pytest.py # -vコマンドで詳細表示 =================== test session starts =========================== platform darwin -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 rootdir: /Users/<username>/PycharmProjects/pythontest collected 2 items test_power_pytest.py .. [100%] ==================== 2 passed in 0.01s ============================= |
テストカバレッジ
✅pytestにはカバレッジ(テストスクリプトでチェックできている割合)をレポートしてくれる
✅pytestのカバレッジを確認するにはpytest-covというパッケージをインストールする必要あり($ pip install pytest-cov)
✅カバーできている割合をパーセントで表すのが一般的
✅TDD(テストドリブンディべロップメント): テストコードに問題ないか確認しながら開発を進めていくこと
pytest-covの実行
[~] $ pytest <テストスクリプト(.py)> –cov=<対象のスクリプト>
: <対象のスクリプト>のテストカバレッジを表示
[~] $ pytest <テストスクリプト(.py)> –cov=<対象のスクリプト> –cov-report term-missing
: <対象のスクリプト>の実行されていない行を表示
devide関数を作りif文で結果が異なるコードがあると想定して確認してみる。
テストスクリプトでは割り切れる値が返り値になるテストを実施できているが、0を割る条件でのテストは実行できていないため、88%であり11行目にMissingが出ている。
1 2 3 4 5 6 7 8 9 10 11 12 |
# <power.py> def power(base, exp): return base ** exp def times(num1, num2): return num1 * num2 def devide(num1, num2): if num2 == 0: return None else: return num1 / num2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# test_power_pytest.py def test_power(): base = 2 exp = 3 assert power(base, exp) == 8 with pytest.raises(TypeError): power("3", "2") def test_times(): num1 = 2 num2 = 3 assert times(num1, num2) == 6 def test_devide(): num1 = 4 num2 = 2 assert devide(num1, num2) == 2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<Command> (venv) [~] $ pytest test_power_pytest.py --cov=power --cov-report term-missing ======================= test session starts ========================= platform darwin -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 rootdir: /Users/<username>/PycharmProjects/pythontest plugins: cov-2.12.1 collected 3 items test_power_pytest.py ... [100%] ---------- coverage: platform darwin, python 3.8.2-final-0 ----------- Name Stmts Miss Cover Missing ---------------------------------------- power.py 8 1 88% 11 ←実行されていない行が表示される(今回はif num2 == 0の箇所) ---------------------------------------- TOTAL 8 1 88% ======================== 3 passed in 0.05s ============================= |
テストスクリプトにif文の0を割る関数も確認するためにtest_devide_by_zero()関数を作成し、pytestを実行するとCoverは100%となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# test_power_pytest.py import pytest from power import power, times, devide ~省略~ def test_devide(): num1 = 4 num2 = 2 assert devide(num1, num2) == 2 def test_devide_by_zero(): num1 = 4 num2 = 0 assert devide(num1, num2) is None |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<Command> (venv) [~] $ pytest test_power_pytest.py --cov=power --cov-report term-missing ===================== test session starts =========================== platform darwin -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 rootdir: /Users/<username>/PycharmProjects/pythontest plugins: cov-2.12.1 collected 4 items test_power_pytest.py .... [100%] ---------- coverage: platform darwin, python 3.8.2-final-0 ----------- Name Stmts Miss Cover Missing ---------------------------------------- power.py 8 0 100% ---------------------------------------- TOTAL 8 0 100% =======================4 passed in 0.15s ======================= |
pytest-covの結果をcsvファイルで保存
上記のようなテストカバレッジの結果をhtml, xml, csvファイルとして出力することもできます。
$ pytest <テストスクリプト(.py)> –cov=<対象のスクリプト> –cov-report xml
: カバレッジ情報が載ったxmlファイルを作成し保存する
$ pytest <テストスクリプト(.py)> –cov=<対象のスクリプト> –cov-report xml –cov-append
: カバレッジ情報が載ったxmlファイルを作成し保存する(上書きではなく追加)
“pythonコードのテストについて”は以上になります、いかがだったでしょうか?_
テストコードををしっかり作って、開発を進めていくことの重要性を学ぶことができたんじゃないでしょうか👌
覚えることも案外多く大変だなと感じることが多いとは思いますが、現場で使いながら覚えていくのが一番いいんじゃないかなと思います◎
今回はこの辺で、バイバイ👋
assert <Trueとなるような値>, “<エラー(False)時のコメント>” : Falseであればエラー出力