テストデータの省力化

目次

提供されるテストデータをコードの試し実行の度にコピペするのは面倒くさい。そこで、テストデータを Python のコメントとしてコードに埋め込み、それを自動的に抽出とリダイレクトしてテストを行うラッパーを作成した。


問題点

AtCoder では、作成したコードに問題がないかテストするためのデータがいくつか提供されます。このデータをプログラムに与えて、同時に提供される出力と答え合わせをします。

AtCoder のテストデータはファイルではなく、標準入力を通じて与えます。そのためテストデータ毎に

  1. コードを実行する。
  2. テストデータをターミナルに paste する。
  3. 出力を確認する。

を繰り返す必要があります。一度で済めばよいのですが、たいてい何回もこれを繰り返す必要があり面倒くさい。

解決方法

そこでこのプログラムにテストデータを与える操作を自動化することにしました。その方法には 2 つの方法が考えられます。

StringIO

簡単な方法は、Python と VSCode で競プロ - 標準入力の簡易化とサンプルケース判定の自動化 -のように io module の StringIO を使用することです。

しかし、この方法では 2 つ問題点があります。

  1. 複数のテストデータを自動的に与えてテスト出来ない。
  2. input()の入力元が変更されているので、作成したコードをそのまま AtCoder に回答として提出するとエラーになってしまう。

2 番目の問題に対しては、そのまま提出できるように try except でキャッチと条件分岐をするようにもしてみたのですが、やっぱり面倒くさい感じでした。

外部 wrapper

そこで、テストデータはプログラムに記述しつつ、作成したコードを実行する外部 wrapper スクリプトを ChatGPT と相談しながら作成しました。

具体的には、作成したコードからテストデータを取り出し、それをテスト毎に分割して作成したコードに入力します。

import re
import subprocess
import sys


def extract_test_data(filename):
    with open(filename, 'r', encoding='utf-8') as file:
        content = file.read()

    match = re.search(r'"""TEST_DATA(.*?)"""', content, re.DOTALL)
    if match:
        return match.group(1)
    else:
        return None


def run_prog_with_data(prog_name, data):
    blocks = data.strip().split("\n\n")  # Split into blocks by empty lines
    for index, block in enumerate(blocks):
        print(f"Input {index}")
        print(block)
        process = subprocess.Popen(["python3", prog_name], stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        stdout, stderr = process.communicate(input=block)
        print("")
        print(f"Output {index}")
        print(stdout, stderr)
        if process.returncode != 0:
            break


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python3 validate.py <filename>")
        sys.exit(1)

    filename = sys.argv[1]
    extracted_data = extract_test_data(filename)
    if extracted_data is not None:
        # Use filename as program name
        run_prog_with_data(filename, extracted_data)
    else:
        print("TEST_DATA not found.")

この wrapper をvalidate.pyとすると、ABC_399/A.pyを試すには次のように実行します。文法ミスなど例外が発生した場合には、そこでテストを終了します。

$ python3 validate.py ABC_399/A.py
Input 0
6
abcarc
agcahc

Output 0
2

Input 1
7
atcoder
contest

Output 1
7

Input 2
8
chokudai
chokudai

Output 2
0

Input 3
10
vexknuampx
vzxikuamlx

Output 3
4