Pythonで競プロをやってみて、せっかくならテストを書いてみようと思いました。
環境
- Python: 3.7.5
- pytest: 5.3.2
- VSCode: 1.41.1
ディレクトリ構成
ファイルの置き場所等を先に示しておきます。
.
├── .vscode
│ └── settings.json
├── abc086
│ ├── __init__.py
│ ├── a.py
│ └── test_a.py
├── conftest.py
└── pytest.ini
VSCodeの設定
.vscode/settings.json
にVSCode側の設定を書き込みます。
..vscode/settings.json
{
"python.pythonPath": "${workspaceFolder}/.venv/bin/python3",
"python.testing.pytestArgs": [],
"python.testing.pytestEnabled": true
}
pytestの設定
ルートディレクトリとなる場所に
pytest.ini
-
conftest.py
を設置します。
pytest.ini
は好みの設定をしてください。
pytest.ini
[pytest]
addopts = -v
junit_family=legacy
入力と出力をチェックする関数を conftest.py
に書きました。
conftest.py
import re
import pytest
reg_ss = re.compile(r'^\s+', re.MULTILINE)
reg_nl = re.compile(r'\n')
reg_ex_nl = re.compile(r'^\n')
@pytest.fixture
def check_result(capsys):
def _check_result(app, func, inp, ans):
def preprocess(text):
regs = (reg_ex_nl, reg_ss)
for r in regs:
text = r.sub('', text)
return text
inp_list = reg_nl.split(preprocess(inp))
ans = preprocess(ans)
app.input = lambda: inp_list.pop(0)
func()
out, err = capsys.readouterr()
assert out == ans
assert err == ''
return _check_result
check_result
がヘルパー関数です。ちなみに、 capsys
は出力を扱うpytestのfixtureです。(Capturing of the stdout/stderr output — pytest documentation)
実際のテスト
AtCoderのABC過去問題の86のAを引用します。
リンク: A - Product
回答コード
a.py
# -*- coding: utf-8 -*-
def main():
a, b = map(int, input().split())
print('Odd' if (a * b) % 2 else 'Even')
def wrong():
a, b = map(int, input().split())
print('Even' if (a * b) % 2 else 'Odd')
if __name__ == "__main__":
main()
※ wrong()
はこの記事用の関数で、誤答を出力する関数なので、通常は書かないです。
テストコード
test_a.py
import abc086.a as app
def test_1(check_result):
inp = '''
3 4
'''
ans = '''
Even
'''
check_result(app, app.main, inp, ans)
def test_2(check_result):
inp = '''
1 21
'''
ans = '''
Odd
'''
check_result(app=app, func=app.wrong, inp=inp, ans=ans)
test_2
のように、入力例や回答例のコピペが違ってもある程度は、ヘルパー関数側で吸収するようにしました。
テスト結果の出力
python /Users/john/.vscode/extensions/ms-python.python-2019.11.50794/pythonFiles/testing_tools/run_adapter.py discover pytest -- --rootdir /Users/john/dev/atcoder/ABC -s --cache-clear
============================= test session starts ==============================
platform darwin -- Python 3.7.5, pytest-5.3.2, py-1.8.0, pluggy-0.13.1 -- /Users/john/dev/atcoder/ABC/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /Users/john/dev/atcoder/ABC, inifile: pytest.ini
collecting ... collected 2 items
abc086/test_a.py::test_1 PASSED [ 50%]
abc086/test_a.py::test_2 FAILED [100%]
=================================== FAILURES ===================================
____________________________________ test_2 ____________________________________
check_result = <function check_result.<locals>._check_result at 0x10d8e3170>
def test_2(check_result):
inp = '''
1 21
'''
ans = '''
Odd
'''
> check_result(app=app, func=app.wrong, inp=inp, ans=ans)
abc086/test_a.py:23:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
app = <module 'abc086.a' from '/Users/john/dev/atcoder/ABC/abc086/a.py'>
func = <function wrong at 0x10d8e37a0>, inp = '\n1 21\n ', ans = 'Odd\n'
def _check_result(app, func, inp, ans):
def preprocess(text):
regs = (reg_ex_nl, reg_ss)
for r in regs:
text = r.sub('', text)
return text
inp_list = reg_nl.split(preprocess(inp))
ans = preprocess(ans)
app.input = lambda: inp_list.pop(0)
func()
out, err = capsys.readouterr()
> assert out == ans
E AssertionError: assert 'Even\n' == 'Odd\n'
E - Even
E + Odd
conftest.py:24: AssertionError
- generated xml file: /var/folders/_p/blhkz_jj069cj8_zgmhd3dd40000gn/T/tmp-9833OeiIvOjzde9.xml -
========================= 1 failed, 1 passed in 0.05s ==========================
test_1
は正常に通りました。
test_2
は期待する回答と違うので、AssertionError
が出ています。
参考文献等
-
Mocking input and output for Python testing
- ヘルパー関数の
main.input
周りを参考にしました。
- ヘルパー関数の
-
pytest ヘビー🐍ユーザーへの第一歩 - エムスリーテックブログ
- 結果の出力が
AssertionError
しか出ていないと思って、モジュールからヘルパー関数に書き換えたときの参考にしました。
- 結果の出力が