2014-01-10 198 views
17

假設我有這樣的Python代碼原始輸入:蟒蛇嘲諷的單元測試

def answer(): 
    ans = raw_input('enter yes or no') 
    if ans == 'yes': 
     print 'you entered yes' 
    if ans == 'no': 
     print 'you entered no' 

我如何寫這個單元測試?我知道我必須使用'模擬',但我不明白如何。任何人都可以舉一個簡單的例子嗎

+0

可能重複http://stackoverflow.com/questions/2617057/supply -inputs-to-python-unittests) – jonrsharpe

+0

我找不到答案 – user3156971

+1

三個答案之一*字面意思是*關於使用'mock'來測試'raw_input' – jonrsharpe

回答

24

您無法修補輸入,但可以將其包裝爲使用mock.patch()。這裏是一個解決方案:

from unittest.mock import patch 
from unittest import TestCase 


def get_input(text): 
    return input(text) 


def answer(): 
    ans = get_input('enter yes or no') 
    if ans == 'yes': 
     return 'you entered yes' 
    if ans == 'no': 
     return 'you entered no' 


class Test(TestCase): 

    # get_input will return 'yes' during this test 
    @patch('yourmodule.get_input', return_value='yes') 
    def test_answer_yes(self, input): 
     self.assertEqual(answer(), 'you entered yes') 

    @patch('yourmodule.get_input', return_value='no') 
    def test_answer_no(self, input): 
     self.assertEqual(answer(), 'you entered no') 

請記住,這個片段將只在Python版本3.3+

+8

@ArtOfWarfare mock在python3.3中是新的https: //docs.python.org/3/library/unittest.mock.html有一個backport https://pypi.python.org/pypi/mock – gawel

+1

你應該在你的答案中指定python版本。謝謝@gawel –

+1

你不需要包裝輸入。 '@patch('builtins.input',return_value ='yes')'應該這樣做。 – Finn

0
def answer(): 
    ans = raw_input('enter yes or no') 
    if ans == 'yes': 
     return 'you entered yes' 
    if ans == 'no': 
     return 'you entered no' 


def test_answer_yes(): 
    assert(answer() == 'you entered yes') 

def test_answer_no(): 
    assert(answer() == 'you entered no') 

origin_raw_input = __builtins__.raw_input 

__builtins__.raw_input = lambda x: "yes" 
test_answer_yes() 

__builtins__.raw_input = lambda x: "no" 
test_answer_no() 

__builtins__.raw_input = origin_raw_input 
18

好工作,首先,我覺得有必要指出的是,在有問題的原代碼,實際上有兩件事需要解決:

  1. raw_input(輸入副作用)需要被模擬。
  2. print(輸出副作用)需要檢查。

在單元測試的理想功能中,不會有副作用。一個函數可以簡單地通過傳遞參數來測試,並且它的輸出將被檢查。但是,我們經常想要測試那些功能並不理想的IE,像你這樣的功能。

那麼我們該怎麼做?那麼,在Python 3.3中,我上面列出的兩個問題變得微不足道,因爲unittest模塊獲得了模擬和檢查副作用的能力。但是,截至2014年初,只有30%的Python程序員已經轉向3.x,所以爲了其他70%的Python程序員仍然使用2.x,我將概述一個答案。以目前的速度,3.x直到2019年纔會超過2.x,而2.x直到2027年纔會消失。所以我認爲這個答案在未來幾年內將會有用。

我想一次解決上面列出的問題,因此我將首先將使用print作爲其輸出的函數更改爲使用return。沒有意外,下面是代碼:

def answerReturn(): 
    ans = raw_input('enter yes or no') 
    if ans == 'yes': 
     return 'you entered yes' 
    if ans == 'no': 
     return 'you entered no' 

所以我們需要做的是模擬raw_input。足夠簡單 - Omid Raha's answer to this very question向我們展示瞭如何通過使用我們的模擬實現來調試__builtins__.raw_input實現。除了他的回答沒有正確地組織成一個TestCase和功能,所以我會證明這一點。

import unittest  

class TestAnswerReturn(unittest.TestCase): 
    def testYes(self): 
     original_raw_input = __builtins__.raw_input 
     __builtins__.raw_input = lambda _: 'yes' 
     self.assertEqual(answerReturn(), 'you entered yes') 
     __builtins__.raw_input = original_raw_input 

    def testNo(self): 
     original_raw_input = __builtins__.raw_input 
     __builtins__.raw_input = lambda _: 'no' 
     self.assertEqual(answerReturn(), 'you entered no') 
     __builtins__.raw_input = original_raw_input 

小記只是關於Python命名約定 - 這是由解析器必需的,但不使用通常命名_變量,在lambda未使用的變量的情況下(通常是向用戶顯示在提示在這種情況下,你想知道爲什麼它是必需的),這是raw_input的情況。

無論如何,這是混亂和多餘的。所以我將通過添加contextmanager來消除重複,這將允許簡單的with陳述。

from contextlib import contextmanager 

@contextmanager 
def mockRawInput(mock): 
    original_raw_input = __builtins__.raw_input 
    __builtins__.raw_input = lambda _: mock 
    yield 
    __builtins__.raw_input = original_raw_input 

class TestAnswerReturn(unittest.TestCase): 
    def testYes(self): 
     with mockRawInput('yes'): 
      self.assertEqual(answerReturn(), 'you entered yes') 

    def testNo(self): 
     with mockRawInput('no'): 
      self.assertEqual(answerReturn(), 'you entered no') 

我認爲這很好地回答了第一部分。在第二部分 - 檢查print。我發現這更棘手 - 我很想聽聽有沒有人有更好的答案。

不管怎麼說,在print聲明不能被覆蓋,但如果你使用print()功能,而不是(你應該)和from __future__ import print_function,你可以使用以下命令:

class PromiseString(str): 
    def set(self, newString): 
     self.innerString = newString 

    def __eq__(self, other): 
     return self.innerString == other 

@contextmanager 
def getPrint(): 
    promise = PromiseString() 
    original_print = __builtin__.print 
    __builtin__.print = lambda message: promise.set(message) 
    yield promise 
    __builtin__.print = original_print 

class TestAnswer(unittest.TestCase): 
    def testYes(self): 
     with mockRawInput('yes'), getPrint() as response: 
      answer() 
      self.assertEqual(response, 'you entered yes') 

    def testNo(self): 
     with mockRawInput('no'), getPrint() as response: 
      answer() 
      self.assertEqual(response, 'you entered no') 

這裏棘手的一點是,你需要到yield迴應with塊之前的迴應。但是,在調用with塊中的print()之前,您無法知道該響應的結果。如果字符串是可變的,這將很好,但它們不是。所以取而代之的是一個小承諾或代理類 - PromiseString。它只做兩件事 - 允許設置一個字符串(或者其他任何東西),讓我們知道它是否等於不同的字符串。 A PromiseStringyield ed,然後設置爲with塊內通常爲print的值。

希望你能感謝我寫下來的所有詭計,因爲我花了大約90分鐘的時間把這個晚上放在一起。我測試了所有這些代碼,並驗證了它與Python 2.7一起工作。

5

剛跑過同樣的問題,但我只是嘲笑出__builtin__.raw_input

只在Python 2上測試過。pip install mock如果您還沒有安裝該軟件包。

from mock import patch 
from unittest import TestCase 

class TestAnswer(TestCase): 
    def test_yes(self): 
     with patch('__builtin__.raw_input', return_value='yes') as _raw_input: 
      self.assertEqual(answer(), 'you entered yes') 
      _raw_input.assert_called_once_with('enter yes or no') 

    def test_no(self): 
     with patch('__builtin__.raw_input', return_value='no') as _raw_input: 
      self.assertEqual(answer(), 'you entered no') 
      _raw_input.assert_called_once_with('enter yes or no') 

另外,使用該庫​​,可以簡化兩項測試:

from genty import genty, genty_dataset 
from mock import patch 
from unittest import TestCase 

@genty 
class TestAnswer(TestCase): 
    @genty_dataset(
     ('yes', 'you entered yes'), 
     ('no', 'you entered no'), 
    ) 
    def test_answer(self, expected_input, expected_answer): 
     with patch('__builtin__.raw_input', return_value=expected_input) as _raw_input: 
      self.assertEqual(answer(), expected_answer) 
      _raw_input.assert_called_once_with('enter yes or no') 
2
我使用Python 3.4

,不得不上述適應的答案。我的解決方案將通用代碼納入自定義runTest方法中,並向您展示如何修補input()print()。下面是運行的代碼: 進口單元測試 從IO導入StringIO的 從unittest.mock進口貼片

def answer(): 
    ans = input('enter yes or no') 
    if ans == 'yes': 
     print('you entered yes') 
    if ans == 'no': 
     print('you entered no') 


class MyTestCase(unittest.TestCase): 
    def runTest(self, given_answer, expected_out): 
     with patch('builtins.input', return_value=given_answer), patch('sys.stdout', new=StringIO()) as fake_out: 
      answer() 
      self.assertEqual(fake_out.getvalue().strip(), expected_out) 

    def testNo(self): 
     self.runTest('no', 'you entered no') 

    def testYes(self): 
     self.runTest('yes', 'you entered yes') 

if __name__ == '__main__': 
    unittest.main() 
的[電源輸入到Python單元測試(
+1

我用'nose2'(不直接使用'unittest')調整你的答案,這對我很好。需要注意的一點是,如果將'fakeout.getvalue()'更改爲'fakeout.getvalue()。strip()',則可以避免傳遞額外的換行符。 –