출력 파일을 디스크에 쓰는 Python 함수가 있습니다.
Python unittest
모듈을 사용하여 단위 테스트를 작성하고 싶습니다 .
파일의 동등성을 어떻게 주장해야합니까? 파일 내용이 예상 내용과 다른 경우 오류가 발생하고 + 차이점 목록이 표시됩니다. Unix diff 명령 의 출력에서와 같습니다 .
공식 또는 권장 방법이 있습니까?
답변:
가장 간단한 방법은 출력 파일을 작성한 다음 해당 내용을 읽고 금 (예상) 파일의 내용을 읽고 단순한 문자열 동등성과 비교하는 것입니다. 동일한 경우 출력 파일을 삭제하십시오. 다른 경우 주장을 제기하십시오.
이런 식으로 테스트가 완료되면 실패한 모든 테스트가 출력 파일로 표시되고 타사 도구를 사용하여 골드 파일과 비교할 수 있습니다 ( Beyond Compare 는이 기능이 훌륭합니다).
자신 만의 diff 출력을 제공하고 싶다면 Python stdlib에 difflib 모듈이 있음을 기억하십시오. Python 3.1의 새로운 unittest 지원에는 다음 assertMultiLineEqual
과 유사한 diff를 표시하는 데 사용 하는 메서드가 포함되어 있습니다 .
def assertMultiLineEqual(self, first, second, msg=None):
"""Assert that two multi-line strings are equal.
If they aren't, show a nice diff.
"""
self.assertTrue(isinstance(first, str),
'First argument is not a string')
self.assertTrue(isinstance(second, str),
'Second argument is not a string')
if first != second:
message = ''.join(difflib.ndiff(first.splitlines(True),
second.splitlines(True)))
if msg:
message += " : " + msg
self.fail("Multi-line strings are unequal:\n" + message)
파일 이름을 받아들이고 파일 자체를 여는 것보다 출력 함수가 명시 적으로 파일 핸들 (또는 파일과 유사한 객체 )을 받아들이도록하는 것을 선호 합니다. 이렇게하면 단위 테스트의 출력 함수에 개체를 전달한 다음 해당 개체 의 내용을 다시 호출 ( 호출 후 )하고 예상 출력과 비교할 수 있습니다.StringIO
.read()
StringIO
.seek(0)
예를 들어 다음과 같은 코드를 전환합니다.
##File:lamb.py
import sys
def write_lamb(outfile_path):
with open(outfile_path, 'w') as outfile:
outfile.write("Mary had a little lamb.\n")
if __name__ == '__main__':
write_lamb(sys.argv[1])
##File test_lamb.py
import unittest
import tempfile
import lamb
class LambTests(unittest.TestCase):
def test_lamb_output(self):
outfile_path = tempfile.mkstemp()[1]
try:
lamb.write_lamb(outfile_path)
contents = open(tempfile_path).read()
finally:
# NOTE: To retain the tempfile if the test fails, remove
# the try-finally clauses
os.remove(outfile_path)
self.assertEqual(result, "Mary had a little lamb.\n")
이렇게 코딩하려면
##File:lamb.py
import sys
def write_lamb(outfile):
outfile.write("Mary had a little lamb.\n")
if __name__ == '__main__':
with open(sys.argv[1], 'w') as outfile:
write_lamb(outfile)
##File test_lamb.py
import unittest
from io import StringIO
import lamb
class LambTests(unittest.TestCase):
def test_lamb_output(self):
outfile = StringIO()
# NOTE: Alternatively, for Python 2.6+, you can use
# tempfile.SpooledTemporaryFile, e.g.,
#outfile = tempfile.SpooledTemporaryFile(10 ** 9)
lamb.write_lamb(outfile)
outfile.seek(0)
content = outfile.read()
self.assertEqual(content, "Mary had a little lamb.\n")
이 접근 방식은 예를 들어 파일에 쓰지 않고 다른 버퍼에 모든 파일 류 객체를 허용하기 때문에 출력 기능을 더 유연하게 만드는 추가 이점이 있습니다.
using StringIO
은 테스트 출력의 내용이 주 메모리에 맞을 수 있다고 가정합니다. 매우 큰 출력의 경우 임시 파일 접근 방식 (예 : tempfile.SpooledTemporaryFile )을 사용할 수 있습니다 .
내 테스트 전용 임시 폴더 인 경우에도 항상 디스크에 파일을 쓰지 않으려 고합니다. 실제로 디스크를 건드리지 않으면 테스트가 훨씬 빨라집니다. 특히 코드에서 파일과 많이 상호 작용하는 경우 더욱 그렇습니다.
다음과 같은 파일에 "놀라운"소프트웨어가 있다고 가정합니다 main.py
.
"""
main.py
"""
def write_to_file(text):
with open("output.txt", "w") as h:
h.write(text)
if __name__ == "__main__":
write_to_file("Every great dream begins with a dreamer.")
write_to_file
메서드 를 테스트하려면 다음과 같은 폴더의 파일에 다음과 같이 작성할 수 있습니다 test_main.py
.
"""
test_main.py
"""
from unittest.mock import patch, mock_open
import main
def test_do_stuff_with_file():
open_mock = mock_open()
with patch("main.open", open_mock, create=True):
main.write_to_file("test-data")
open_mock.assert_called_with("output.txt", "w")
open_mock.return_value.write.assert_called_once_with("test-data")
콘텐츠 생성과 파일 처리를 분리 할 수 있습니다. 이렇게하면 임시 파일을 엉망으로 만들고 나중에 정리하지 않고도 내용이 올바른지 테스트 할 수 있습니다.
콘텐츠의 각 줄을 생성 하는 생성기 메서드 를 작성하면 파일을 열고 다음을 호출하는 파일 처리 메서드를 가질 수 있습니다.file.writelines()
일련의 줄로 . 두 메서드는 동일한 클래스에있을 수도 있습니다. 테스트 코드는 생성기를 호출하고 프로덕션 코드는 파일 핸들러를 호출합니다.
다음은 세 가지 테스트 방법을 모두 보여주는 예입니다. 일반적으로 테스트 할 클래스에서 사용할 수있는 메서드에 따라 하나만 선택합니다.
import os
from io import StringIO
from unittest.case import TestCase
class Foo(object):
def save_content(self, filename):
with open(filename, 'w') as f:
self.write_content(f)
def write_content(self, f):
f.writelines(self.generate_content())
def generate_content(self):
for i in range(3):
yield u"line {}\n".format(i)
class FooTest(TestCase):
def test_generate(self):
expected_lines = ['line 0\n', 'line 1\n', 'line 2\n']
foo = Foo()
lines = list(foo.generate_content())
self.assertEqual(expected_lines, lines)
def test_write(self):
expected_text = u"""\
line 0
line 1
line 2
"""
f = StringIO()
foo = Foo()
foo.write_content(f)
self.assertEqual(expected_text, f.getvalue())
def test_save(self):
expected_text = u"""\
line 0
line 1
line 2
"""
foo = Foo()
filename = 'foo_test.txt'
try:
foo.save_content(filename)
with open(filename, 'rU') as f:
text = f.read()
finally:
os.remove(filename)
self.assertEqual(expected_text, text)
제안에 따라 다음을 수행했습니다.
class MyTestCase(unittest.TestCase):
def assertFilesEqual(self, first, second, msg=None):
first_f = open(first)
first_str = first_f.read()
second_f = open(second)
second_str = second_f.read()
first_f.close()
second_f.close()
if first_str != second_str:
first_lines = first_str.splitlines(True)
second_lines = second_str.splitlines(True)
delta = difflib.unified_diff(first_lines, second_lines, fromfile=first, tofile=second)
message = ''.join(delta)
if msg:
message += " : " + msg
self.fail("Multi-line strings are unequal:\n" + message)
파일을 읽고 쓰는 데 필요한 많은 함수가 있으므로 다시 사용할 수있는 assert 메서드가 필요하므로 하위 클래스 MyTestCase를 만들었습니다. 이제 테스트에서 unittest.TestCase 대신 MyTestCase를 하위 클래스로 지정합니다.
당신이 그것에 대해 어떻게 생각하십니까?
open
설명 된대로 호출을 조롱하는 것입니다. 이 페이지의 다른 답변에서 사용unittest.mock
(엔리코 M에서 답을 참조)