前回までで、テストするときに利用するモジュールとassert文を紹介しました。今回は、それらを使っていよいよ関数のテスト(テストプログラム)を書いてみましょう。

assert文を使った関数のテスト

関数のテストでは、関数に引数を渡して実行し、その戻り値が期待する値かどうかをassert文で判定します。

では、まずはどのような引数に対して関数を実行するのか考えましょう。

「main.py」では、match.check_match関数に対して次の表のようなケースを試していました。

引数 patterns 引数 text 期待する戻り値
["富士山.*高さ" "東京.*区.*いくつ"] "ねぇ、富士山の高さは?" True
["富士山.*高さ" "東京.*区.*いくつ"] "東京の区っていくつあるの?" True

この表では、「引数patterns」と「引数text」がmatch.check_match関数に渡す引数を表し、それらを引数として関数実行した結果、期待する戻り値が「期待する戻り値」に書かれています。

それではこの表のケースに対して、assert文を使ってmatch.check_match関数のテストプログラムを書いてみましょう。

# match_test.py - matchモジュールのテスト
import match

def test_check_match():
    patterns = ["富士山.*高さ", "東京.*区.*いくつ"]

    assert match.check_match(patterns, "ねぇ、富士山の高さは?") == True
    assert match.check_match(patterns, "東京の区っていくつあるの?") == True

# テスト関数の実行
test_check_match()

プログラム冒頭では、import 文によってcheck_match関数が定義されているmatchモジュールをインポートしています。

そして、続くtest_check_match関数でテストを実行するための関数を定義しています。この関数では、初めに変数patternsに正規表現のパターンのリストを束縛します。そして続く2つのassert文内で、文字列「ねぇ、富士山の高さは?」「東京の区っていくつあるの?」のそれぞれを変数patternsとともに引数としてmatch.check_match関数に渡して関数実行しています。

match.check_match 関数の戻り値はブール値であり、マッチしている場合は「True」を返します。したがって、assert文でmatch.check_match関数の戻り値を「True」と比較することで、文字列がパターンにマッチしているかどうかを判定することになるわけです。

最後に「テスト関数の実行」とコメントしている部分でtest_check_match関数を実行することで、テストが実行されます。

それではこのテストプログラムを実行してみましょう。

$ python match_test.py

何も表示されませんね。何も表示されないというのは例外が発生しなかったということであり、「全てのケースに対して関数が期待した戻り値だった」ということを意味します。

もし例外が発生してしまった場合は、関数が期待していない戻り値を返したということなので、関数の実装、もしくはテストプログラムに誤りがあることがわかります。試しに、今実装したテストプログラム「match_test.py」に、パターンにマッチしないケースを追加してみましょう。

# match_test.py - matchモジュールのテスト
import match

def test_check_match():
    patterns = ["富士山.*高さ", "東京.*区.*いくつ"]

    assert match.check_match(patterns, "ねぇ、富士山の高さは?") == True
    assert match.check_match(patterns, "東京の区っていくつあるの?") == True
    # パターンにマッチしないケースを追加
    assert match.check_match(patterns, "東京っていくつの区があるの?") == True

# テスト関数の実行
test_check_match()

実行してみると次のような結果が得られます。

$ python match_test.py
Traceback (most recent call last):
  File "match_test.py", line 13, in 
    test_check_match()
  File "match_test.py", line 10, in test_check_match
    assert match.check_match(patterns, "東京っていくつの区があるの?") == True
AssertionError

例外が発生しました! 「File “match_test.py”, line 10, in test_match_accept」という出力から、追加した行で例外が発生したことがわかります。

なぜ追加した行で例外が発生したのでしょうか。match.check_match関数は文字列がパターンにマッチする場合には「True」を、そうでない場合には「False」を返すのでしたね。したがって、マッチしないことを確認するには次のように関数の戻り値が「False」と一致していることを確認しなければなりません。

assert match.check_match(patterns, "東京っていくつの区があるの?") == False

先ほどのプログラムでは、戻り値が「True」と一致していることを確認していました。それによって例外が発生してしまっていたのです。

それでは、match_test.pyを修正しましょう。

# match_test.py - matchモジュールのテスト
import match

def test_check_match():
    patterns = ["富士山.*高さ", "東京.*区.*いくつ"]

    assert match.check_match(patterns, "ねぇ、富士山の高さは?") == True
    assert match.check_match(patterns, "東京の区っていくつあるの?") == True
    # パターンにマッチしないケースを追加
    # False と比較することで、マッチしていないことをチェックする
    assert match.check_match(patterns, "東京っていくつの区があるの?") == False

# テスト関数の実行
test_check_match()

実行すると何も表示されないため、関数が期待した戻り値を返したことがわかります。

$ python match_test.py

上記のように、テストプログラム「match_test.py」を実行することで、関数が期待した戻り値を返すことを誰でも確認できるようになります。「関数が期待する動作をすること」を確認するには、テストプログラムが例外を出さないことを確認すればよいため、以前のように関数の出力を目視で確認する必要がなくなったわけです。

おわりに

今回まで3回に渡り、関数をテストする方法について紹介しました。初めに手動テストの問題点を把握した後、テストで用いるモジュールやassert文を紹介しました。そして今回はついにテストを実装し、いつ誰が行っても同じ品質で関数の動作を確認できるようになりました。

プログラミングの際には、テストプログラムを書いて正しく動作するかどうかを確認しながら進めていくことが極めて重要になります。興味がある方は「テスト駆動開発」という用語でWebページを検索すると、関連する内容をより知ることができますので、ぜひ調べてみてください。

本連載では、今後もテストを書きながら対話システムを作っていく予定ですので、今回の内容をしっかりと理解しておいてください。

著者紹介


株式会社NTTドコモ
R&Dイノベーション本部 サービスイノベーション部
阿部憲幸

2015年京都大学大学院理学研究科数学・数理解析専攻修了。 同年、NECに入社。 2016年から国立研究開発法人情報通信研究機構出向。 2018年より現職。 自然言語処理、特に対話システムの研究開発に従事。 毎日話したくなるAIを夢見て日夜コーディングに励む。
GitHub:https://github.com/noriyukipy