0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

プロパティーベーステストHypothesis、テストカバレッジ可視化Coverageを触ってみた。

Last updated at Posted at 2025-05-18

テスト用プログラム作ってるので他のテスト方法が気になった。

理想のプログラムテストの5でテスト方法を少し調べた時に下記の方法が気になり、触ってみることにしました。

  • 自動でテスト条件を作ってくれるプロパティーベーステストのHypothesis
  • テストのカバー率を可視化するCoverage

まずはテスト対象のプログラムはこれ。

test_target_math_utils.py
# test_target_math_utils.py

def is_even(n: int) -> bool:
    return n % 2 == 0

def is_prime(n: int) -> bool:
    if n <= 1:
        return False
    for i in range(2, int(n**0.5)+1):
        if n % i == 0:
            return False
    return True

プロパティーベーステストのHypothesis。

あー、これってテスト駆動開発みたいにテストプログラム作ると、その引数を自動生成してくれる作りなんですね。個人的にはこの方法って、簡単な関数しかテストできないんじゃないだろうかと思ってしまう。

複雑な計算の関数をテストする場合、テスト関数もその複雑な計算と同様の計算をしないと答えに辿りつかないわけで。例えば、2人で同じ仕様の関数を別のアプローチで作って照合するみたいなことでもするんだろうか?

少し疑問もありつつ動かしてみる。

test_math_utils.py
# test_math_utils.py

from hypothesis import given, strategies as st
import test_target_math_utils

@given(st.integers())
def test_is_even_property(n):
    result = test_target_math_utils.is_even(n)
    print(f"Testing is_even({n}) => {result}")
    assert result == (n % 2 == 0)

@given(st.integers(min_value=0, max_value=100))
def test_is_prime_returns_bool(n):
    result = test_target_math_utils.is_prime(n)
    print(f"Testing is_prime({n}) => {result}")
    assert isinstance(result, bool)

起動するとこんな感じ。
hypothesis以外になんか、pytestも使ってるので、これもpip installしておく必要あり。

pytest -s Sources/Test/test_math_utils.py
========================================================================= test session starts ==========================================================================
platform darwin -- Python 3.9.10, pytest-8.3.5, pluggy-1.6.0
rootdir: /Users/Documents/study/Project/ProcessAutomation
plugins: hypothesis-6.131.18
collected 2 items                                                                                                                                                      

Sources/Test/test_math_utils.py Testing is_even(0) => True
Testing is_even(-71817893133481016899362877663145486773) => False
Testing is_even(-19021) => False
Testing is_even(1659534479) => False
Testing is_even(50) => True
Testing is_even(-29259) => False
Testing is_even(-3939634080555406904) => True
Testing is_even(54) => True
Testing is_even(8) => True
Testing is_even(6179) => False
Testing is_even(20433) => False
Testing is_even(-15665) => False
Testing is_even(8541) => False
Testing is_even(-23) => False
Testing is_even(12007) => False
Testing is_even(20013) => False
Testing is_even(-502) => True
Testing is_even(-109) => False
Testing is_even(21005) => False
Testing is_even(26010) => True
Testing is_even(105) => False
Testing is_even(-5908588771663957071) => False
Testing is_even(101) => False
Testing is_even(28099) => False
Testing is_even(5033152888165806409) => False
Testing is_even(75961965) => False
Testing is_even(-20591) => False
Testing is_even(11281) => False
Testing is_even(19185) => False
Testing is_even(-3565) => False
Testing is_even(-66) => True
Testing is_even(-1) => False
Testing is_even(-106) => True
Testing is_even(3075547436370167308) => True
Testing is_even(-104) => True
Testing is_even(90) => True
Testing is_even(7710) => True
Testing is_even(102468792108170126311179414789846708505) => False
Testing is_even(35) => False
Testing is_even(16763) => False
Testing is_even(108) => True
Testing is_even(-76) => True
Testing is_even(-14339) => False
Testing is_even(13477) => False
Testing is_even(-1404) => True
Testing is_even(-2216) => True
Testing is_even(-2558) => True
Testing is_even(-1424) => True
Testing is_even(42) => True
Testing is_even(-31172) => True
Testing is_even(1204) => True
Testing is_even(-124) => True
Testing is_even(-31015) => False
Testing is_even(-27615) => False
Testing is_even(10079) => False
Testing is_even(-2730) => True
Testing is_even(87) => False
Testing is_even(-8116420234147600904) => True
Testing is_even(1048530737) => False
Testing is_even(-25579) => False
Testing is_even(12672) => True
Testing is_even(6419) => False
Testing is_even(-51) => False
Testing is_even(21729) => False
Testing is_even(68) => True
Testing is_even(60) => True
Testing is_even(-31034) => True
Testing is_even(31216) => True
Testing is_even(13510) => True
Testing is_even(-5) => False
Testing is_even(-1088958148) => True
Testing is_even(11) => False
Testing is_even(-101) => False
Testing is_even(12790) => True
Testing is_even(-16508) => True
Testing is_even(698) => True
Testing is_even(27465) => False
Testing is_even(-6403) => False
Testing is_even(36) => True
Testing is_even(92) => True
Testing is_even(13208) => True
Testing is_even(-30172) => True
Testing is_even(7046) => True
Testing is_even(29748) => True
Testing is_even(-58) => True
Testing is_even(3) => False
Testing is_even(5879) => False
Testing is_even(-8754) => True
Testing is_even(20831) => False
Testing is_even(86) => True
Testing is_even(8440) => True
Testing is_even(7576) => True
Testing is_even(-26659) => False
Testing is_even(-4060709688130422517) => False
Testing is_even(77) => False
Testing is_even(690267564) => True
Testing is_even(4822) => True
Testing is_even(-8301) => False
Testing is_even(23744) => True
Testing is_even(16282) => True
.Testing is_prime(0) => False
Testing is_prime(63) => False
Testing is_prime(91) => False
Testing is_prime(28) => False
Testing is_prime(54) => False
Testing is_prime(72) => False
Testing is_prime(96) => False
Testing is_prime(24) => False
Testing is_prime(67) => True
Testing is_prime(82) => False
Testing is_prime(55) => False
Testing is_prime(92) => False
Testing is_prime(98) => False
Testing is_prime(23) => True
Testing is_prime(31) => True
Testing is_prime(64) => False
Testing is_prime(58) => False
Testing is_prime(35) => False
Testing is_prime(33) => False
Testing is_prime(81) => False
Testing is_prime(80) => False
Testing is_prime(78) => False
Testing is_prime(43) => True
Testing is_prime(4) => False
Testing is_prime(10) => False
Testing is_prime(41) => True
Testing is_prime(34) => False
Testing is_prime(71) => True
Testing is_prime(38) => False
Testing is_prime(75) => False
Testing is_prime(60) => False
Testing is_prime(74) => False
Testing is_prime(3) => True
Testing is_prime(44) => False
Testing is_prime(11) => True
Testing is_prime(86) => False
Testing is_prime(25) => False
Testing is_prime(77) => False
Testing is_prime(30) => False
Testing is_prime(32) => False
Testing is_prime(37) => True
Testing is_prime(97) => True
Testing is_prime(100) => False
Testing is_prime(40) => False
Testing is_prime(95) => False
Testing is_prime(68) => False
Testing is_prime(45) => False
Testing is_prime(19) => True
Testing is_prime(15) => False
Testing is_prime(83) => True
Testing is_prime(2) => True
Testing is_prime(57) => False
Testing is_prime(29) => True
Testing is_prime(20) => False
Testing is_prime(65) => False
Testing is_prime(79) => True
Testing is_prime(85) => False
Testing is_prime(52) => False
Testing is_prime(89) => True
Testing is_prime(56) => False
Testing is_prime(46) => False
Testing is_prime(36) => False
Testing is_prime(14) => False
Testing is_prime(12) => False
Testing is_prime(13) => True
Testing is_prime(84) => False
Testing is_prime(21) => False
Testing is_prime(5) => True
Testing is_prime(62) => False
Testing is_prime(61) => True
Testing is_prime(39) => False
Testing is_prime(48) => False
Testing is_prime(69) => False
Testing is_prime(99) => False
Testing is_prime(90) => False
Testing is_prime(49) => False
Testing is_prime(17) => True
Testing is_prime(73) => True
Testing is_prime(87) => False
Testing is_prime(70) => False
Testing is_prime(22) => False
Testing is_prime(16) => False
Testing is_prime(18) => False
Testing is_prime(42) => False
Testing is_prime(26) => False
Testing is_prime(7) => True
Testing is_prime(1) => False
Testing is_prime(53) => True
Testing is_prime(76) => False
Testing is_prime(51) => False
Testing is_prime(6) => False
Testing is_prime(47) => True
Testing is_prime(66) => False
Testing is_prime(93) => False
Testing is_prime(8) => False
Testing is_prime(59) => True
Testing is_prime(9) => False
Testing is_prime(50) => False
Testing is_prime(27) => False
Testing is_prime(88) => False
.

一瞬でこんだけテストしてくれるのはちょっといいな。

でも、引数と結果をセットで表示するのが標準じゃなくて、今回も自分で表示を作ったのだけど、どうなんだろって思っちゃう。テストプログラムを手動で作ってるので、テストプログラムが正常に動いてない可能性もあるのだから、結果見ないと不安じゃないのか?
この辺、世界標準的にテスト条件と結果のデータ表示方法を1つ決めてくれてもいいんじゃない?

テスト駆動プログラミング全体的にそんな雰囲気がある気もしていて、今一つ素晴らしいと思えないとこがある。
関数とテストがセットであるべきなのに異論は無いんだけど・・

  1. テストすべき引数のパターンを決める。結果も想定する。
  2. テスト関数を作る。
     →難解な関数のテスト関数は作りきれるの?難解な関数ほどバグが多いんだけど・・
  3. 関数の動作結果が正しいかをテスト関数で確認する。
     →テスト関数、本当に正しく動作してるの?

個人的には下記の流れが現実的ではないのかと思ってる。

  1. テストすべき引数のパターンを決める。可能なら結果も想定する。
  2. 関数の動作条件と結果を表示し、人かAIが正しいかどうかを検証する。
     →上記が担保された上で、テスト関数も導入するのには異論なし

特に、AIが精度を上げてきたので、現在となってはTest Programの重要性は落ちてきてるかも。
今後は引数と結果の可視化やAIにデータを渡すことの方が重要になるんじゃ無いのかな?

テストカバレッジ可視化Coverage

普通はコンソールで実行するものみたいですけど、面倒だからPythonでコンソールコマンドを実行させます。

# test_coverage.py

import subprocess
import webbrowser
import os
import sys

def run_coverage(test_file_path: str = "Sources/Test/test_math_utils.py"):
    try:
        # 1. coverage 実行(coverage コマンドを Python 経由で呼び出し)
        subprocess.run([sys.executable, "-m", "coverage", "run", "-m", "pytest", test_file_path], check=True)

        # 2. HTMLレポート生成
        subprocess.run([sys.executable, "-m", "coverage", "html"], check=True)

        # 3. HTMLファイルをブラウザで開く
        report_path = os.path.abspath("htmlcov/index.html")
        webbrowser.open(f"file://{report_path}")
        print(f"✅ カバレッジレポートを開きました: {report_path}")

    except subprocess.CalledProcessError as e:
        print("❌ エラーが発生しました:", e)
        sys.exit(1)

if __name__ == "__main__":
    # 任意のファイルを引数で渡せるように
    test_file = sys.argv[1] if len(sys.argv) > 1 else "Sources/Test/test_math_utils.py"
    run_coverage(test_file)
========================================================================= test session starts ==========================================================================
platform darwin -- Python 3.9.10, pytest-8.3.5, pluggy-1.6.0
rootdir: /Users/Documents/study/Project/ProcessAutomation
plugins: hypothesis-6.131.18
collected 2 items                                                                                                                                                      

Sources/Test/test_math_utils.py ..                                                                                                                               [100%]

========================================================================== 2 passed in 0.35s ===========================================================================
Wrote HTML report to htmlcov/index.html
✅ カバレッジレポートを開きました: /Users/Documents/study/Project/ProcessAutomation/htmlcov/index.html

Coverage report: 2% Show/hide keyboard shortcuts 
 hide covered
Files Functions Classes
coverage.py v7.8.0, created at 2025-05-18 18:41 +0900
File	statements	missing	excluded	coverage
Sources/Test/test_math_utils.py	12	0	0	100%
Sources/Test/test_target_math_utils.py	9	0	0	100%
/Users/***/_pydevd_bundle/pydevd_net_command.py	90	67	0	26%
/Users/***/_pydevd_bundle/pydevd_io.py	151	124	0	18%
/Users/***/_pydevd_bundle/_debug_adapter/pydevd_schema.py	6838	6760	0	1%
/Users/***/_pydevd_bundle/pydevd_comm.py	1171	1169	0	1%
/Users/***/_pydevd_bundle/pydevd_constants.py	411	407	0	1%
/Users/***/_pydevd_bundle/pydevd_net_command_factory_json.py	325	321	0	1%
Total	9007	8848	0	2%

おお、何かそれらしきものが出てきた。
テストターゲットが100%だから、いい感じのテストができたのかな?
原理は分からないけど、カバー率は重要だよね。
→これも本当は根拠知りたいんだけど・・と思いつつ。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?