3
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

競プロ用にテストを書く(VSCode + Python + pytest)

Last updated at Posted at 2019-12-28

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 のように、入力例や回答例のコピペが違ってもある程度は、ヘルパー関数側で吸収するようにしました。

テスト結果の出力

image.png

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 が出ています。

参考文献等

3
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?