पायथन के 'यूनीटेस्ट' का उपयोग करके फाइलों को लिखने वाले कार्यों की इकाई परीक्षण कैसे करें


83

मेरे पास एक पायथन फ़ंक्शन है जो डिस्क पर आउटपुट फ़ाइल लिखता है।

मैं पायथन के unittestमॉड्यूल का उपयोग करके इसके लिए एक इकाई परीक्षण लिखना चाहता हूं ।

मुझे फ़ाइलों की समानता कैसे सुनिश्चित करनी चाहिए? मैं एक त्रुटि प्राप्त करना चाहूंगा यदि फ़ाइल सामग्री मतभेद की अपेक्षित एक + सूची से भिन्न होती है। यूनिक्स के उत्पादन में के रूप में diff आदेश।

क्या ऐसा करने का कोई आधिकारिक या अनुशंसित तरीका है?

जवाबों:


50

सबसे सरल बात यह है कि आउटपुट फ़ाइल लिखना है, फिर इसकी सामग्री को पढ़ें, सोने की सामग्री (अपेक्षित) फ़ाइल को पढ़ें, और सरल स्ट्रिंग समानता के साथ उनकी तुलना करें। यदि वे समान हैं, तो आउटपुट फ़ाइल हटा दें। यदि वे अलग-अलग हैं, तो जोर लगाएं।

इस तरह, जब परीक्षण किए जाते हैं, तो हर असफल परीक्षण का आउटपुट फ़ाइल के साथ प्रतिनिधित्व किया जाएगा, और आप उन्हें सोने की फ़ाइलों के खिलाफ अलग करने के लिए एक तृतीय-पक्ष उपकरण का उपयोग कर सकते हैं ( इसके अलावा तुलना इसके लिए अद्भुत है)।

यदि आप वास्तव में अपना स्वयं का अलग आउटपुट प्रदान करना चाहते हैं, तो याद रखें कि पायथन stdlib में difflib मॉड्यूल है। पायथन 3.1 में नए unittest सपोर्ट में एक assertMultiLineEqualविधि शामिल है जो इसे दिखाने के लिए इसका उपयोग करता है, इसी तरह:

    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)

नहीं, सबसे अच्छा तरीका है समग्र एक फ़ाइल में लिखने जो धीमी गति से हो सकता है और त्रुटियों की संभावना (prod env विंडोज बनाम OSX की तरह परीक्षा / सीआई env से पूरी तरह से अलग हो सकता है,) है, लेकिन इसके बजाय करने के लिए कॉल उपहास करने के लिए कर सकते हैं करने के लिए नहीं है openके रूप में वर्णित इस पृष्ठ पर अन्य उत्तरों में, unittest.mock(एनरिको एम से उत्तर देखें)
एरिक

71

मुझे लगता है कि फ़ाइल नाम को स्वीकार करने और फ़ाइल को स्वयं खोलने के बजाय आउटपुट फ़ंक्शन को फ़ाइल हैंडल (या फ़ाइल-जैसी ऑब्जेक्ट ) को स्पष्ट रूप से स्वीकार करना पसंद है । इस तरह, मैं अपने यूनिट टेस्ट में आउटपुट फ़ंक्शन के लिए ऑब्जेक्ट पास कर सकता हूं, फिर उस ऑब्जेक्ट से कॉल वापस (एक कॉल के बाद ) और मेरी अपेक्षित आउटपुट के साथ तुलना कर सकता हूं।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")

इस दृष्टिकोण से आपके आउटपुट फ़ंक्शन को अधिक लचीला बनाने का अतिरिक्त लाभ होता है यदि, उदाहरण के लिए, आप तय करते हैं कि आप किसी फ़ाइल को लिखना नहीं चाहते हैं, लेकिन कुछ अन्य बफ़र, क्योंकि यह सभी फ़ाइल जैसी वस्तुओं को स्वीकार करेगा।

ध्यान दें कि StringIOपरीक्षण आउटपुट की सामग्री मुख्य मेमोरी में फिट हो सकती है। बहुत बड़े आउटपुट के लिए, आप एक अस्थायी फ़ाइल दृष्टिकोण (जैसे, tempfile.SpooledTemporaryFile ) का उपयोग कर सकते हैं ।


2
यह बेहतर है तो डिस्क पर एक फ़ाइल लिखना। यदि आप टन के unittests चला रहे हैं, तो IO डिस्क सभी प्रकार की समस्याओं का कारण बनता है, विशेष रूप से उन्हें साफ करने की कोशिश कर रहा है। मेरे पास डिस्क पर लिखने का परीक्षण था, लिखित फ़ाइलों को हटाने वाला आंसू। टेस्ट एक समय में ठीक काम करेगा, फिर रन ऑल जब असफल हो जाए। कम से कम विजुअल स्टूडियो और PyTools के साथ एक विन मशीन पर। इसके अलावा, गति।
पार करें

1
हालांकि यह अलग-अलग फ़ंक्शन का परीक्षण करने के लिए एक अच्छा समाधान है, यह वास्तविक इंटरफ़ेस का परीक्षण करते समय अभी भी परेशानी है कि आपका प्रोग्राम प्रदान करता है (उदाहरण के लिए एक CLI उपकरण)
Joost

1
मुझे त्रुटि मिली: TypeError: यूनिकोड तर्क अपेक्षित है, 'str' मिला
cn123h

मैं यहाँ आया था क्योंकि मैं विभाजन के लिए इकाई परीक्षण लिखने की कोशिश कर रहा हूँ और फ़ाइल-दर-फ़ाइल विभाजन विभाजन पढ़ रहा हूँ। इसके लिए फ़ाइल पथ को पार्स करने की आवश्यकता होती है, जिसके परिणामस्वरूप (अंततः) परिणामी पांडा डेटाफ़्रेम के विभाजन के उचित मूल्य को निर्दिष्ट करने के लिए कुंजी / मान जोड़े प्राप्त करने के लिए। एक बफर के लिए लेखन, जबकि अच्छा है, मुझे विभाजन मूल्यों के लिए पार्स करने की क्षमता नहीं देता है।
PMende

1
@PMende ऐसा लगता है कि आप एक एपीआई के साथ काम कर रहे हैं जिसे वास्तविक फाइल सिस्टम के साथ सहभागिता की आवश्यकता है। इकाई परीक्षण हमेशा परीक्षण का उपयुक्त स्तर नहीं होते हैं। इकाई परीक्षणों के स्तर पर अपने कोड के सभी भागों का परीक्षण नहीं करना ठीक है; एकीकरण या सिस्टम परीक्षणों का उपयोग किया जाना चाहिए, जहां उपयुक्त हो, भी। हालांकि, उन भागों को शामिल करने की कोशिश करें, और जब भी संभव हो सीमाओं के बीच केवल सरल मूल्यों को पारित करें। Youtube.com/watch?v=eOYal8elnZk
gotgenes

20
import filecmp

फिर

self.assertTrue(filecmp.cmp(path1, path2))

2
द्वारा डिफ़ॉल्ट यह एक करता shallowतुलना जो चेक केवल फ़ाइलों मेटाडाटा (mtime, आकार, आदि)। कृपया shallow=Falseअपने उदाहरण में जोड़ें ।
16:59 बजे अकाल

2
इसके अतिरिक्त, परिणाम कैश हैं
२०'०

12

मैं हमेशा डिस्क पर फ़ाइलों को लिखने से बचने की कोशिश करता हूं, भले ही यह मेरे परीक्षणों के लिए समर्पित एक अस्थायी फ़ोल्डर हो: वास्तव में डिस्क को छूने से आपके परीक्षण बहुत तेज नहीं होते हैं, खासकर यदि आप अपने कोड में फ़ाइलों के साथ बहुत अधिक बातचीत करते हैं।

मान लीजिए कि आपके पास फ़ाइल में सॉफ़्टवेयर का यह "अद्भुत" टुकड़ा है 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")

3

आप फ़ाइल को संभालने से कंटेंट जेनरेशन को अलग कर सकते हैं। इस तरह, आप परीक्षण कर सकते हैं कि सामग्री अस्थायी फ़ाइलों के साथ गड़बड़ करने और बाद में उन्हें साफ किए बिना सही है।

यदि आप एक जनरेटर विधि लिखते हैं जो सामग्री की प्रत्येक पंक्ति का उत्पादन करती है, तो आपके पास एक फ़ाइल हैंडलिंग विधि हो सकती है जो एक फ़ाइल और कॉल खोलती है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)

क्या आप उसके लिए उदाहरण कोड प्रदान कर सकते हैं? यह दिलचस्प लगता है।
buhtz

1
मैंने तीनों दृष्टिकोणों के लिए एक उदाहरण जोड़ा, @buhtz।
डॉन किर्कबी

-1

सुझावों के आधार पर मैंने निम्नलिखित कार्य किए।

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)

मैंने एक उपवर्ग MyTestCase बनाया है क्योंकि मेरे पास बहुत सारे फ़ंक्शंस हैं जिन्हें फ़ाइलों को पढ़ने / लिखने की आवश्यकता है इसलिए मुझे वास्तव में पुन: प्रयोज्य करने की विधि की आवश्यकता है। अब मेरे परीक्षणों में, मैं unittest.TestCase के बजाय MyTestCase को उपवर्गित करूंगा।

आपने इस बारे में क्या सोचा?


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.