簡體   English   中英

如何使用Unittest測試來測試Python腳本中的標准輸入和標准輸出?

[英]How can I test the standard input and standard output in Python Script with a Unittest test?

我正在嘗試測試Python腳本(2.7),在其中使用標准輸入(用raw_input()讀取並用簡單的打印方式寫),但是我沒有發現如何做,並且我確定這個問題是很簡單的。

這是我腳本的非常非常恢復的代碼:

def example():
    number = raw_input()
    print number

if __name__ == '__main__':
    example()

我想編寫一個單元測試來檢查這一點,但是我找不到如何做。 我正在嘗試使用StringIO和其他方法,但是我找不到真正做到這一點的解決方案。

有人有主意嗎?

PD:當然,在真實的腳本中,我使用具有幾行和其他類型數據的數據塊。

非常感謝。

編輯:

非常感謝您提供的第一個具體答案,它非常完美,僅在導入StringIO遇到了一些問題,因為我正在執行導入StringIO,並且需要像from StringIO import StringIO一樣from StringIO import StringIO (我不明白為什么會這樣) ),但它可能會起作用。

但是我用這種方法發現了另一個問題,在我的項目中,我需要用這種方法測試腳本(在您的支持下,腳本可以很好地工作),但是我想這樣做:我有一個經過大量測試的文件一個腳本,所以我打開文件並讀取包含其結果塊的信息塊,並且我想這樣做,代碼將能夠處理檢查其結果的塊並與其他對象進行同樣的操作...

像這樣:

class Test(unittest.TestCase):
    ...
    #open file and process saving data like datablocks and results
    ...
    allTest = True
    for test in tests:
        stub_stdin(self, test.dataBlock)
        stub_stdouts(self)
        runScrip()
        if sys.stdout.getvalue() != test.expectResult:
            allTest = False

    self.assertEqual(allTest, True)

我知道也許單元測試現在沒有意義,但是您可以對我想要的做個想法。 因此,這種方式失敗了,我也不知道為什么。

典型的技術包括使用所需項目sys.stdin標准sys.stdinsys.stdout 如果您不關心Python 3的兼容性,則可以使用StringIO模塊,但是,如果您想向前思考並願意將其限制為Python 2.7和3.3+,則可以同時以這種方式同時支持Python 2和3 io模塊需要做很多工作(但需要做一些修改,但暫時不要考慮)。

假設您已經有了一個unittest.TestCase ,則可以創建一個實用程序函數(或同一類中的方法),該函數將替代sys.stdin / sys.stdout 首先進口:

import sys
import io
import unittest

在我最近的一個項目我做這個stdin的,它需要一個str用於輸入用戶(或通過管道另一程序)將進入你的作為標准輸入:

def stub_stdin(testcase_inst, inputs):
    stdin = sys.stdin

    def cleanup():
        sys.stdin = stdin

    testcase_inst.addCleanup(cleanup)
    sys.stdin = StringIO(inputs)

至於stdout和stderr:

def stub_stdouts(testcase_inst):
    stderr = sys.stderr
    stdout = sys.stdout

    def cleanup():
        sys.stderr = stderr
        sys.stdout = stdout

    testcase_inst.addCleanup(cleanup)
    sys.stderr = StringIO()
    sys.stdout = StringIO()

請注意,在這兩種情況下,它都接受一個測試用例實例,並調用其addCleanup方法,該方法添加了cleanup函數調用,該調用將在測試方法持續時間結束時將其重置為原來的位置。 效果是,從在測試用例中調用它到結束為止, sys.stdout和friends將被io.StringIO版本替換,這意味着您可以輕松檢查其值,而不必擔心留下一團糟

最好以這個為例。 要使用它,您可以簡單地創建一個測試用例,如下所示:

class ExampleTestCase(unittest.TestCase): 

    def test_example(self):
        stub_stdin(self, '42')
        stub_stdouts(self)
        example()
        self.assertEqual(sys.stdout.getvalue(), '42\n')

現在,在Python 2中,僅當StringIO類來自StringIO模塊時,此測試才能通過,而在Python 3中,則不存在這樣的模塊。 您可以做的是使用io模塊的版本並進行修改,使其在接受的輸入方面稍微寬松一些,以便unicode編碼/解碼將自動完成,而不是觸發異常(例如print語句)沒有以下內容,Python 2中的代碼將無法很好地工作)。 我通常這樣做是為了實現Python 2和3之間的交叉兼容性。

class StringIO(io.StringIO):
    """
    A "safely" wrapped version
    """

    def __init__(self, value=''):
        value = value.encode('utf8', 'backslashreplace').decode('utf8')
        io.StringIO.__init__(self, value)

    def write(self, msg):
        io.StringIO.write(self, msg.encode(
            'utf8', 'backslashreplace').decode('utf8'))

現在將示例函數以及此答案中的每個代碼片段插入一個文件,您將獲得可在Python 2和3中運行的自包含單元測試(盡管您需要在Python 3中將print作為函數調用)以針對stdio進行測試。

還有一點要注意:如果每個測試方法都需要stub_函數調用,則始終可以將其stub_TestCasesetUp方法中。

當然,如果您想使用各種與模擬相關的庫來存根stdin / stdout,則可以隨意這樣做,但是如果這是您的目標,則這種方式不依賴任何外部依賴項。


對於第二個問題,測試用例必須以某種方式編寫,必須將它們封裝在一個方法中,而不是在類級別,否則原始示例將失敗。 但是,您可能想要執行以下操作:

class Test(unittest.TestCase):

    def helper(self, data, answer, runner):
        stub_stdin(self, data)
        stub_stdouts(self)
        runner()
        self.assertEqual(sys.stdout.getvalue(), answer)
        self.doCleanups()  # optional, see comments below

    def test_various_inputs(self):
        data_and_answers = [
            ('hello', 'HELLOhello'),
            ('goodbye', 'GOODBYEgoodbye'),
        ]

        runScript = upperlower  # the function I want to test 

        for data, answer in data_and_answers:
            self.helper(data, answer, runScript)

您可能要調用doCleanups的原因是為了防止清理堆棧深入到所有data_and_answers對所在的位置,但是這會將所有內容從清理堆棧中彈出,因此如果您有其他需要清理的內容,最后,這可能最終會帶來問題-您可以隨意離開,因為所有與stdio相關的對象都將以相同順序還原到最后,因此真正的對象將始終在那里。 現在我要測試的功能:

def upperlower():
    raw = raw_input()
    print (raw.upper() + raw),

因此,是的,我所做的一些解釋可能會有所幫助:請記住,在TestCase類中,該框架嚴格依賴實例的assertEqual和朋友來使其起作用。 因此,為了確保在正確的級別上進行測試,您真的想一直調用這些斷言,以便在發生錯誤時(輸入/答案顯示得不正確)顯示有用的錯誤消息,而不是直到最后,就像您對for循環所做的一樣(這將告訴您出了點問題,但不完全是數百個錯誤中的哪個,現在您很生氣)。 也是helper方法-您可以隨意調用它,只要它不以test開頭就可以,因為這樣,框架將嘗試將其作為一個整體運行,並且將嚴重失敗。 因此,只要遵循此約定,您就可以在測試用例中基本包含模板來運行測試-然后可以像我一樣在帶有大量輸入/輸出的循環中使用它。

至於您的其他問題:

只是我在導入StringIO時遇到了一些問題,因為我正在執行導入StringIO,並且需要從StringIO導入,就像從StringIO導入StringIO(我不太明白為什么),但事實上,它確實可行。

好吧,如果您看一下我的原始代碼,我確實向您展示了如何import io ,然后通過定義class StringIO(io.StringIO)覆蓋io.StringIO類。 但是它對您有用,因為您嚴格地從Python 2進行此操作,而鑒於不到5年將不支持Python 2,因此我會盡一切可能將針對Python 3的答案作為目標。 想想將來可能正在閱讀這篇文章的與您有類似問題的用戶。 無論如何,是的, from StringIO import StringIO的原始作品起作用,因為這是StringIO模塊中的StringIO類。 雖然from cStringIO import StringIO應該可以正常工作,因為它可以導入C版本的StringIO模塊。 之所以起作用,是因為它們都提供了足夠接近的接口,因此它們基本上可以按預期工作(當然,除非您嘗試在Python 3下運行它)。

再次,將所有這些與我的代碼放在一起,應該得到一個自包含的工作測試腳本 請記住要閱讀文檔並遵循代碼的形式,而不要發明自己的語法並希望事情能夠起作用(以及確切地說,為什么您的代碼不起作用,因為“測試”代碼是在類所在的位置定義的)正在構建中,因此所有這些操作都是在Python導入模塊時執行的,並且由於測試運行所需的所有東西甚至都不可用(即類本身甚至還不存在),因此只是因抽搐而死)。 我想,即使您面臨的問題確實很普遍,在這里問問題也有幫助,沒有一個快速簡單的名稱來搜索您的確切問題確實使您很難弄清楚哪里出了問題。 :)無論如何,祝您好運,並且對您努力測試代碼也有好處。


還有其他方法,但是鑒於我在SO上查看的其他問題/答案似乎無濟於事,所以我希望這一方法能解決這一問題。 其他參考:

自然地,只需使用Python 3.3+中可用的unittest.mockpypi上原始/滾動反向端口版本 即可完成所有這些操作,但是鑒於這些庫隱藏了一些實際發生的復雜情況,它們最終可能會失敗隱藏有關實際發生(或需要發生)或重定向實際發生方式的一些詳細信息。 如果需要,可以閱讀unittest.mock.patch並稍微轉到StringIO修補sys.stdout部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM