Python の勉強 スロットゲーム編 〜その17:テスト(doctest)〜

プログラミング

プログラムを作成・修正したら、それが正しく動くのかどうか確認しなければなりません。これを “ソフトウェアテスト”(または単に “テスト”)といいます。スロットゲームを修正する際に簡単な動作確認をしました。

ところでソフトウェアテストには、いくつかの種類があります。以下はソフトウェア開発の「V字モデル」では以下の 4 つを定義しています。

  1. 単体テスト(ユニットテスト)  ←この記事で掘り下げる対象
    ※作成した関数が正しく動いているかを確認するテストです。
  2. 結合テスト
    ※多くの機能は関数を組み合わせて、ようやくアプリケーションとしての意味が出てきます。結合テストは、そんな風に関数同士、機能同士を組み合わせ(結合)して正しく動いているかを確認するテストです。
  3. システムテスト
    ※結合テストよりも大きな視野で、システムとして正しく動いているかを確認するテストです。
  4. 受入テスト
    ※作成したアプリケーションに対して、発注元のお客さんが確認する作業です。これで OK をいただかないと製品を受け取ってもらえません。

他にも「シナリオテスト」「性能テスト」「モンキーテスト」「回帰テスト」等々色んな種類や手法があります。どのフェーズで、どんな観点・粒度のテストがどれくらい必要かを決めるのは用意ではありません。”ソフトウェアのテストをする” ということが職業になる事実も分かります。

上記のテストの内、「単体テスト」に関しては毛色が違います。単体テストは “プログラムをテストするプログラムを作成する” のが主になるからです。ここでは以下の記事に記載されている 3 つの方法について、スロットゲームに適用する際にどうなるのかを簡単に勉強したいと思います。
test — Python 用回帰テストパッケージunittest — ユニットテストフレームワーク

  • doctest
  • unittest
  • pytest

doctest

その12でやった Pydoc の中にテスト用のプログラム(のようなもの)を記載してしまえ!という大胆なものです。簡易的なテストではあるものの、関数自体の使い方の例示もできるという優れた一面もあります。※この形式のテストは Python でしか見たこと無いのですが……他のプログラミング言語でもあるのでしょうか?もし知ってたら教えていただきたいです。
対話型シェル(ターミナルやコマンドプロンプトで “python” を入力すると始まるアレです)内でのデバッグの様子を模範した書き方になります。

  1. 結果が単純な場合
    1. >>> 」に続けて関数を使用する様子を記載
    2. 直後に期待される結果を記載する
  2. エラーが期待される場合
    1. >>> 」に続けて関数を使用する様子を記載(上と同じ)
    2. 途中を「...」で省略しつつ、エラーの内容を記載する

という書式となっております。
Pydoc が有効な場所であれば、ファイル先頭でも、関数内部でも記載可能です。また、「1」「2」を復数、繋げることもできます。

ただし printinput で表示するものも期待値として記載する必要があるようです。

例1:関数内に記載する、復数、エラー

def is_bets_valid(str_bets, player_coin):
    """掛けコイン数が有効(valid)な場合は True を返す(return)
    --> The function returns true if the bets is valid.

    Args:
        str_bets (str): 掛けコイン数
        player_coin (int): プレーヤーの所持コイン数

    Returns:
        tuple: [0] 掛けコイン数が有効な場合 True、それ以外は False
               [1] 検証結果が False だった場合のエラーメッセージ

    >>> is_bets_valid('100', 100)
    (True, None)

    >>> is_bets_valid('200', 100)
    (False, '掛けコイン数は半角数字で入力して下さい。')

    >>> is_bets_valid('0', 100)
    (False, '掛けコイン数は 1 枚以上を指定して下さい。')

    >>> is_bets_valid('101', 100)
    (False, '掛けコイン数は所持コイン数以下を指定して下さい。')

    >>> is_bets_valid('100', 'abc')
    Traceback (most recent call last):
        ...
    TypeError: '>' not supported between instances of 'int' and 'str'
    """
    if not re.search(r'^[0-9]+$', str_bets):
        return (False, '掛けコイン数は半角数字で入力して下さい。')

    bets = int(str_bets)
    if bets == 0:
        return (False, '掛けコイン数は 1 枚以上を指定して下さい。')
    elif bets > player_coin:
        return (False, '掛けコイン数は所持コイン数以下を指定して下さい。')
    else:
        return (True, None)

例2:ファイル先頭に記載する、print() の結果を確認する

"""スロットゲーム

簡単なスロットゲームです。ゲームスタート時に保有しているコインを増やしましょう。
掛けコイン数を入力すると、スロットの結果が表示されます。
絵柄が揃うと、そのパターンに応じた配当倍率でコインを獲得することができます。

>>> show_start_message()
--------------
スロットゲーム
--------------
"""

例3:input() にも対応

def ask_player_name():
    """プレーヤーの名前を尋ねる
    (This function is to be called to ask player's name.)

    プレーヤーの名前を入力(input)してもらい、挨拶を表示(print)する
    入力されたプレーヤーの名前を返す(return)

    Returns:
        str: プレーヤーの名前

    >>> import io, sys; sys.stdin = io.StringIO('king')
    >>> ask_player_name()
    あなたの名前を入力して下さい: こんにちは king さん
    'king'
    """
    player_name = input('あなたの名前を入力して下さい: ')
    print(f'こんにちは {player_name} さん')
    return player_name

doctest を実行する

「game016.py」をコピペして「game017.py」を作成してください。作成した game017.py に上記テストを記載したら、実際に実行してみましょう。
新しいターミナルを開いて、以下のコマンドを入力します。

python -m doctest -v my_slotgame/game017.py

※「-m doctest」は使用するモジュールの指定、「-v」は詳細を表示するための doctest のオプションです。

上記の情報は「-v」オプションを指定したこと表示される情報です。試しに「-v」を除いて実行してみると、何も表示されないことが分かると思います。

テスト失敗したら?

本当なら「135」を取得する関数に対して、「155」を期待値として記載した場合はどうなるでしょうか?

def calculate_coin(coin, bets, division):
    """掛けたコイン数(bets)と、配当倍率(division)を使用してプレーヤーの所持コイン数を精算する
    (This function is to be called to calculate (players's) coin.)

    Args:
        coin (int): プレーヤーの精算前の所持コイン数
        bets (int): 掛けコイン数
        division (int): 配当倍率

    Returns:
        int: 精算後のプレーヤーの所持コイン数

    >>> calculate_coin(100, 5, 7)           # 本当は 135 が正解
    155
    """
    coin = coin + bets * division
    return coin

game017.py を更新して、改めて doctest を実行してみましょう。
※詳細が表示されると見づらくなるので「-v」オプションは省いています。

実際の結果と、期待値が違うと警告が表示されます。結果と期待値が違うということはプログラムが間違っているのか、仕様の理解が間違っているのか……どちらにしろ望ましい状態ではありませんので確認・修正が必要になります。

ところで、テストをプログラムで記載するのもなかなか大変です。何故、このようなモジュールが存在し、それにはどんなメリットがあるのでしょうか?
それは何回もテストを実施した際に、ようやく現れます。手作業でテストをすると、毎回、同じだけのコストがかかります。しかしプログラムで準備しておくと、2回目以降はコマンドを入力するだけの手間しかかかりません。

手動にしろ、自動(プログラム)にしろ、テストをするというのはとても大切な作業です。
受験や資格試験では 80 点も取れば合格でしょう。しかしシステムは常に 100 点を求められています。10 回の内 8 回しか、無事に目的地につけない自動運転の車なんて乗りたくないですよね。プログラムをするのと同様に、システムテストで確認をするということも大切だということを是非、心に留めておいて下さい。

コメント

タイトルとURLをコピーしました