質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

5246閲覧

「1つのファイル」へのテストはどんなモジュール、方法を使えばよいでしょうか

uni8inu

総合スコア127

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

2クリップ

投稿2016/11/02 09:58

###質問
Pythonではunittestモジュールを使ったユニットテストが一般的なテスト方法だと思います。調査した所、関数、あるいはクラスへのテストはunittestモジュールでできることはわかりました。
ただ、unittestを使って標準入出力を含む「1つの.pyファイルへのテスト」ができるのかが分かりませんでした。
このような場合は、どのようにテストすれば良いのか(どんなテストモジュールならいけるか)ご存知でしたらご教示願えますでしょうか?

###具体的な例
下記のようなname.pyファイルがあり、標準入力値"wanko"で標準出力"wanko wanko"が期待されているという状況があった場合、どんなテストモジュールを使ってテストをやればよいか?

python

1#name.pyのコード 2name = input() 3print(name,name)

###質問の背景
Pythonビギナーです。
現在paizaでPyhonのコードのオンラインジャッジを行っております。
オンラインジャッジのシステムでは標準入力を受け取り、標準出力へ正しい結果が出ているかを判定しております。提出コードへ変更を加えずにテストを行いたいと考えており、この度質問いたしました。

###試したこと
自作でsubprocessとPepoenを利用しstdin,stdoutを操作して、
ファイルに対してテストができるコードを書きましたが、なんというか車輪の再開発感がもりもりしています。
つらいです。

python

1#ファイルに対してテストを行うモジュール 2import subprocess 3from subprocess import Popen,PIPE 4 5# -------ここから変更-------- 6#テストを行うファイル名 7test_filename = "name.py" 8 9#""" """の間に標準入力テストデータを貼り付ける 10input_d =""" 11wanko 12""" 13#""" """の間に期待する標準出力結果を貼り付ける 14expect_d = """ 15wanko wanko 16""" 17# -------ここまで-------- 18run_cmd = "python " + test_filename 19 20input_d = input_d[1:] # スライスで行頭削除 : 改行単位でinput()が実行される 21expect_d = expect_d[1:] # スライスで行頭削除 : stdoutは行末に改行を含む 22 23p = Popen(run_cmd, 24 stdin=PIPE,stdout=PIPE,stderr=subprocess.PIPE, 25 universal_newlines=True) 26 27p.stdin.write(input_d) # stdin実行 28stdout = p.communicate()[0] # stdout取得 29 30#結果の表示 31print("[{} stdout]\n{}".format(test_filename,stdout)) 32print("[expect]\n{}".format(expect_d)) 33 34if(expect_d != stdout): 35 print("***TEST NG***") 36else: 37 print("!!!TEST OK!!!")

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

車輪の再開発感が~の下りを最初に読んだときに思い浮かんだのは、シェルで

diff <(python foo.py < IN.txt) OUT.txt

でした。

unittest を使うなら、入出力とロジック部分を分離して…等と書こうとしてたのですが、
やりたいことは、関数のロジックの単体テストではなく、入出力を用いた受入テストの方なのでしょうか。

  • Python内で標準入力の mock は sys.stdin へのストリームの代入で可能です。
  • 同様に標準出力のキャプチャも sys.stdout 差し替えで可能ですが、
    標準出力のキャプチャ&テストは、小規模なものであれば doctest を用いると楽です。

python

1# File: my_code.py 2name = input() 3print(name, name)

python

1#!/usr/bin/env python 2 3# File: test_my_code.py 4# 実行方法: python -m doctest -v test_my_code.py 5 6import sys 7from io import StringIO 8from runpy import run_module 9 10def test_my_code(stdin): 11 """ 12 >>> test_my_code("wanko\\n") 13 wanko wanko 14 15 >>> test_my_code("nyaa\\n") 16 nyaa nyaa 17 18 """ 19 sys.stdin = StringIO(stdin) 20 run_module("my_code")

規模が大きくなってくると、doctest では記述しにくいこともありますが
コードの実行が完了するまで待ってから、結果を比較するテスト方法では、
メモリ的にも時間的にも無駄が多くなるので、
テスト対象のコードをイテレータを返すように変更する等、
テストしやすいように元コードの設計を変える工夫が必要になってきます。


ちなみに、再利用可能なライブラリではないのですが、
Python自身が使ってるテストコードで test.support 以下に
サブプロセスを用いた方法や、標準入出力のキャプチャ等の実装があり、
同じ様なことをやっているので、参考になるかと思います。

投稿2016/11/04 05:42

編集2016/11/04 05:46
teamikl

総合スコア8681

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

uni8inu

2016/11/04 07:14

非常に参考になりました! 確かに簡易的にはシェルで結果取ってDiffすればよい・・・盲点でした。 IDEに頼りすぎていて、シェルを使うという脳が死んでおりました。 用語がわからず説明がうまく出来ませんでしたが、ご指摘の通り、知りたかったのは「入出力を用いた受入テスト」はどうやるのか?という事だったようです。 頂いたアドバイスを参考に、最適な方法を選択したいと思います。ありがとうございました。
guest

0

ベストアンサー

「1つの.pyファイルへのテスト」をテストも実装も含めて1つのファイル・・・と解釈しました。そうでなくとも、テスト対象をimportすれば同じことができますので、それで説明します。

unittest.mockを使います。モックとはケータイショップに置いてある中身の入ってない、あの模型のようなものです。これを使うとテストしたい対象が依存している副作用をテスト用のものに置き換えられます。下層のオブジェクトの副作用を含んだテストではなく、その関数の副作用だけを検証できます。Incremental Testと呼ばれます。

はい、うんちくはいいですね。こんな風に使います。

python

1import unittest 2import unittest.mock as mock 3from contextlib import redirect_stdout 4import io 5 6def main(): 7 name = input() 8 print(name, name) 9 10class TestMain(unittest.TestCase): 11 @mock.patch('__main__.input', return_value='hoge') 12 def test_main0(self, *args, **kwarg): 13 f = io.StringIO() 14 with redirect_stdout(f): 15 main() 16 self.assertEqual(f.getvalue(), 'hoge hoge\n') 17 18if __name__ == '__main__': 19 import sys 20 if len(sys.argv) == 2 and sys.argv[1] == 'test': 21 unittest.main(argv=[sys.argv[0]]) 22 else: 23 main()

この例ではinput()'hoge'を返すだけの関数に置き換えられています。return_valueだけでなく、side_effectで関数を指定することもできます。詳しくはドキュメントをご覧ください。

追記

なるほどimport時に実行されるコードの副作用ですか。一応できなくはないみたいですが、なんかちょっと危なっかしい気もします。そういうことならsubprocessを使った方法もあながち悪くないかもしれませんね。

python

1#wanko.py 2name = input() 3print(name,name)

python

1import sys 2import io 3import unittest 4import unittest.mock as mock 5 6class TestWanko(unittest.TestCase): 7 def setUp(self): 8 self.orig_stdout = sys.stdout 9 sys.stdout = io.StringIO() 10 11 def tearDown(self): 12 sys.stdout = self.orig_stdout 13 if 'wanko' in sys.modules: 14 del sys.modules['wanko'] 15 16 @mock.patch('builtins.input', return_value='hoge') 17 def test0(self, *args, **kwarg): 18 import wanko 19 self.assertEqual(sys.stdout.getvalue(), 'hoge hoge\n') 20 21 @mock.patch('builtins.input', return_value='wan') 22 def test1(self, *args, **kwarg): 23 import wanko 24 self.assertEqual(sys.stdout.getvalue(), 'wan wan\n') 25 26unittest.main()

投稿2016/11/03 12:01

編集2016/11/04 00:58
sharow

総合スコア1151

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

uni8inu

2016/11/03 15:53

ご回答ありがとうございます。mockを知らなかったので大変参考になりました。 "テスト対象をimportすれば同じことができます"の部分ですが、確かに参考コードのmain()部分をモジュール化した所うまくいきました。 ただ、元々やりたかったことが「テスト対象のモジュールが素の実行文しかないもの」に対するテストです。質問の内容の書き方が悪く、申し訳ないです。以下のようなモジュールへのテストをしたいのです。 ```python #my_code.py : 素の実行文しかないモジュール name = input print(name,name) ``` 通常の、「モジュール内の関数などへのテスト」はテストケースにimportした時点でモジュールがインスタンス化されて、モックのデコレーターが効くと思っております。 一方、「素の実行文を持つモジュール」はimport時点で即座に処理が動き出し、mock化するチャンスが無いように思われます。試しに改造した下記のコードではうまく行きませんでした。 ```python #教えていただいたサンプルコードをmy_code.pyを読み出すように改造 #略 class TestMain(unittest.TestCase): @mock.patch('my_code.input', return_value='hoge') def test_main0(self, *args, **kwarg): f = io.StringIO() with redirect_stdout(f): import my_code #モジュール実行後にインスタンス化されるため、mockが効かない? self.assertEqual(f.getvalue(), 'hoge hoge\n') #break point #略 ``` self.assertEqualにブレークポイントを貼ると、モジュールインスタンスはきちんとMagicMocを持っているようです。しかし、my_code.py内の処理はすでに実行済み・・・。 input()があとからmoc化されても時すでに遅し!と言った感じになっておりました。 素の実行文を持つモジュールに対しては、mocで対応できる範囲外、という感じでしょうか?
sharow

2016/11/04 01:02

コメントだとマークダウンが効かないので回答に追記しました。 inputのような組み込み関数は builtins.input でモック化できるようです。
uni8inu

2016/11/04 06:45

builtins.input!これです。ありがとうございます。 追記いただいたコードで、知りたかったテストの動作になりました。
sharow

2016/11/04 07:46

teamiklさん方法も参考にしてください。 unittest内でもimportでなくrunpyを使ったほうがよさそうです。 私はrunpy知りませんでした。これを使うとdel sys.modules['wanko']とかは要らなくなりますね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問