プログラミングゲームの解答コードを投稿しよう! の関連記事です.
難しいプログラミング問題に取り組むとき,手元での確認・検証は重要です.
この記事では,自分のpaizaプログラミング用のローカルテスト環境を紹介します.
プログラム言語はPythonです.Rank D問題:カジノを例として取り上げます.
2行での要約:
test_main.py のinput1
,output1
,input2
,output2
,def main()
の中身を書き換えて,
ローカル環境でテストやデバッグ実行しよう.
出題・解答 環境
最初に,出題・解答 環境を確認します.
電脳少女はpaizaと画面構成など一部異なりますが,以下の要素を共通して持ちます.
- 問題の概要
- 入力される値
- 期待する出力
- 条件
- 入力例1
- 出力例1
- 入力例2
- 出力例2
また,Python3の「解答コード入力欄」は,最初に以下が書かれています.
# coding: utf-8
# 自分の得意な言語で
# Let's チャレンジ!!
input_line = input()
print("XXXXXX")
そして,「提出前動作確認」ボタンを押すと,アウトプットが XXXXXX
だと確認できます.
提出前動作確認では,標準入力に対して「入力例1」が与えられ,「出力例1」を標準出力する事が期待されています.自由な入出力での動作確認(テスト)はできません.
この標準入力stdin
と標準出力stdout
をどう扱うかが,ローカルテストの鍵となります.
オンラインテスト環境
オンラインテスト環境は,paiza.IOを使うのがベストでしょう.
他にもいろいろなサービスがありますが,公式がpaiza.IOを推奨しています.
標準入力と標準出力は,ブラウザの後ろ側で良い感じに対応されています.
ローカルテスト環境
準備
まず,Pythonが既に実行可能であることを前提とします.
ローカルテストにはpytest
を利用します.
pip install pytest
でpytest
がインストールできます.
必要に応じて以下のコマンドを実行し,pytest
のバージョンが表示されることを確認してください.
$ python --version
Python 3.12.4
$ pytest --version
pytest 8.3.4
$ python -m pytest --version
pytest 8.3.4
次に,テストファイル test_main.py を用意します.
import pytest
input1 = """
入力例1
""".strip()
output1 = """
出力例1
""".strip()
input2 = """
入力例2
""".strip()
output2 = """
出力例1
""".strip()
def main():
解答
# 以下は固定
def test_main(monkeypatch) -> None:
check(monkeypatch, main, input1, output1)
check(monkeypatch, main, input2, output2)
def check(monkeypatch, func: None, input: str, output: str) -> None:
import io
stdin = io.StringIO(input)
stdout = io.StringIO()
with monkeypatch.context() as m:
m.setattr("sys.stdin", stdin)
m.setattr("sys.stdout", stdout)
func()
assert stdout.getvalue() == output + "\n"
if __name__ == "__main__":
pytest.main([__file__])
このファイルは,すべての問題で使いまわします.
問題毎にファイルの中身を書き換えて,ローカルテストを実行します.
使い方
準備したテストファイル test_main.py の日本語部分を適切なものに書き換えます.
Rank D問題:カジノの場合,ファイルを以下のように書き換えます.
import pytest
input1 = """
- 入力例1
+ 1
+ 5
+ 2
""".strip()
output1 = """
- 出力例1
+ 46
""".strip()
input2 = """
- 入力例2
+ 8
+ 2
+ 80
""".strip()
output2 = """
- 出力例1
+ 818
""".strip()
def main():
- 解答
+ print(sum(i * int(input()) for i in [1, 5, 10]))
# 以下は固定
~~~省略~~~
そして,以下のどちらかのコマンドでテストを実行します.
pytest test_main.py
or
python test_main.py
緑の文字で1 passed in 0.02s
のように表示されたら,テストは成功です.
赤の文字で1 failed in 0.15s
のように表示されたら,テストは失敗です.
その場合,short test summary info
など,失敗に関する情報が表示されます.
実際の使い方としては,まず最初に問題の入出力例をブラウザからコピペします.
そして,解答をmain
関数の中に書きます.解答の試行錯誤は,main
関数内で実施します.
オンライン環境より実行速度が速いので,頻繁にテストを実行しても問題ありません.
解答がテストを通ったら,それをブラウザに張り付けて,提出します.
自分の場合,ブラウザでは「解答コード入力欄」も含め,文字のタイピングは一切行いません.
コピーとペーストだけ実施し,タイピングは test_main.py に対してのみ実施しています.
test_main.py の解説
ここからは,test_main.py を解説します.
test_main.py は,以下の設計方針,自身の要望に基づいて作成されています.
- すべてを1ファイルで完結させる
- 編集箇所をなるべく近くでまとめる
- ローカルテスト環境と本番のブラウザとのやり取りは,コピペのみで済ませる
- 入出力例に対する改変を不要にする(タブ,インデント,改行,[]の有無など)
- 入出力例1,2を2箇所以上に書かない
この設計に基づき, test_main.py の書き換え部分は,現在のかたちになりました.
すべてを1ファイルで完結させるとすると,入力例と出力例は,変数にする必要があります.
複数行の入力例をそのままコピペするには,以下の書き方がベストでした.
input1 = """
入力例1
""".strip()
入力例と出力例の間に3行が挟まりますが,これは許容範囲としました.
試行錯誤が必要な解答は,解答をmain
関数内で書くことにしました.
余分なインデントが入りますが,コピペで解答を提出できたため,良しとしました.
変数input1
,output1
,input2
,output2
とmain
関数ができたら,これらを結びつける処理が必要です.
最終的に,pytest
のmonkeypatch
を利用するのがベストだと判断しました.
monkeypatch
を使うと,sys.stdin
やsys.stdout
を差し替えることが出来ます.
テストの核心部分は,check
関数として汎用的にまとめました.
def check(monkeypatch, func: None, input: str, output: str) -> None:
import io
stdin = io.StringIO(input)
stdout = io.StringIO()
with monkeypatch.context() as m:
m.setattr("sys.stdin", stdin)
m.setattr("sys.stdout", stdout)
func()
assert stdout.getvalue() == output + "\n"
check
関数は,monkeypatch
,関数func
,入力例input
,出力例output
が入力になります.
出力はありませんが,内部でテストを実施します.
内部では,最初にio
モジュールをインポートして,代替のstdin
とstdout
を作成します.
stdin
には,input
をセットします.
そして,monkeypatch.context()
を利用し,sys.stdin
とsys.stdout
をそれぞれstdin
とstdout
に置き換え,func
を実行します.
最後に,func
の標準出力をstdout.getvalue()
で受け取り,それがoutput
と一致するかテストします.
test_main
関数は,check
関数へ渡す関数と変数を制御します.
def test_main(monkeypatch) -> None:
check(monkeypatch, main, input1, output1)
check(monkeypatch, main, input2, output2)
以下は,常にpytest
経由でmain
関数を呼び出すための設定です.
if __name__ == "__main__":
pytest.main([__file__])
2行目をmain()
に変えると,直接main
関数を呼び出すようになります.
変更後にpython test_main.py
を実行すると,多くの場合に標準入力待ちとなります.
VS Code
ローカルテスト環境の一部として,エディタも紹介します.
自分はエディタとしてVS Codeを使っており,デバッグ実行(F5)をよく利用しています.
ブレークポイントを設定することで,プログラムの途中経過がわかり,とても便利です.
DEBUG CONSOLEは,変数の中身を表示可能で,簡単な計算も実行できるため,これも便利です.
VS Codeなら,以下の表示が可能です.
終わりに
自分のpaiza用ローカルテスト環境を紹介しました.
オンラインのコーディング環境も便利になってきていますが,まだ,ローカル環境の方が便利だと考えています.
是非, test_main.py をコピーして使ってみてください.