2011-05-01 51 views
43

說我有以下模塊:如何測試或模擬「如果__name__ ==‘__main__’」內容

def main(): 
    pass 

if __name__ == "__main__": 
    main() 

我想寫的下半部分單元測試(我想實現100%的覆蓋率)。我發現執行import/__name__設置機制的內置模塊,但我無法弄清楚如何模擬或以其他方式檢查是否調用了函數。

這是我到目前爲止已經試過:

import runpy 
import mock 

@mock.patch('foobar.main') 
def test_main(self, main): 
    runpy.run_module('foobar', run_name='__main__') 
    main.assert_called_once_with() 

回答

34

我將選擇另一個替代方案,即從覆蓋率報告中排除if __name__ == '__main__',當然,只有在測試中已經有main()函數的測試用例時,纔可以這樣做。

至於爲什麼我選擇排除而不是爲整個腳本編寫一個新的測試用例是因爲如果我說你已經有了main()函數的測試用例,那麼你添加了另一個測試用例該腳本(僅用於100%覆蓋)將只是一個重複的腳本。

對於如何排除if __name__ == '__main__'你可以寫一個覆蓋配置文件,並添加部分報告:關於覆蓋配置文件

[report] 

exclude_lines = 
    if __name__ == .__main__.: 

更多信息,可以發現here

希望這可以幫助。

+0

Heya,我已經添加了一個新的答案,提供100%的測試覆蓋率(測試!),並且不需要忽略任何東西。讓我知道你的想法:http://stackoverflow.com/a/27084447/1423157謝謝。 – robru 2014-11-23 23:59:38

+0

對於那些想知道:'nose-cov'在下面使用coverage.py,所以具有上述內容的'.coveragerc'文件將工作得很好。 – Joscha 2015-11-12 04:58:37

+3

恕我直言,即使我發現它有趣和有用,這個答案不*實際給予OP的迴應。他想要測試主叫,不要跳過這個檢查。否則,腳本實際上可以完成除了實際預期的任何事情以外的所有事情,並且測試會顯示「OK,一切正常!」。主要功能可以完全通過單元測試,即使從未被實際調用過。 – iacopo 2017-02-05 10:25:24

9

您可以使用imp模塊,而不是import語句做到這一點。 import聲明的問題是'__main__'的測試作爲導入聲明的一部分運行,然後您有機會分配到runpy.__name__

例如,你可以使用imp.load_source()像這樣:

import imp 
runpy = imp.load_source('__main__', '/path/to/runpy.py') 

的第一個參數被分配到導入模塊的__name__

+2

imp模塊似乎很像我在問題中使用的runpy模塊。問題在於模塊無法(顯然)在模塊加載之後和代碼運行之前插入。你有什麼建議嗎? – Nikolaj 2011-05-01 19:54:40

2

一種方法是將模塊作爲腳本運行(例如os.system(...)),並將它們的stdout和stderr輸出與預期值進行比較。

+1

在子流程中運行腳本並期待coverage.py跟蹤所執行的行並不容易,因爲它聽起來很容易,因此可以在此處找到有關使此解決方案起作用的更多信息:http://nedbatchelder.com/code/coverage/ subprocess.html – mouad 2011-05-01 18:37:57

4

哇,我有點遲到了,但我最近就遇到了這個問題,我想,我想出了一個更好的解決方案,所以這裏是......

我工作的一個包含有十幾個劇本都與此完全相同copypasta結束模塊:

if __name__ == '__main__': 
    if '--help' in sys.argv or '-h' in sys.argv: 
     print(__doc__) 
    else: 
     sys.exit(main()) 

不可怕,肯定的,但不可測試無論是。我的解決辦法是在我的模塊中的一個寫一個新功能:

def run_script(name, doc, main): 
    """Act like a script if we were invoked like a script.""" 
    if name == '__main__': 
     if '--help' in sys.argv or '-h' in sys.argv: 
      sys.stdout.write(doc) 
     else: 
      sys.exit(main()) 

,然後把這種寶石在每個腳本文件的末尾:

run_script(__name__, __doc__, main) 

從技術上講,此功能將無條件是否運行您的腳本作爲模塊導入或作爲腳本運行。這是好的,但因爲該功能實際上並不任何東西,除非該腳本正在作爲腳本運行。因此,代碼覆蓋率看到函數運行,並說「是的,100%的代碼覆蓋率!」同時,我寫了三個測試覆蓋本身的功能:

@patch('mymodule.utils.sys') 
def test_run_script_as_import(self, sysMock): 
    """The run_script() func is a NOP when name != __main__.""" 
    mainMock = Mock() 
    sysMock.argv = [] 
    run_script('some_module', 'docdocdoc', mainMock) 
    self.assertEqual(mainMock.mock_calls, []) 
    self.assertEqual(sysMock.exit.mock_calls, []) 
    self.assertEqual(sysMock.stdout.write.mock_calls, []) 

@patch('mymodule.utils.sys') 
def test_run_script_as_script(self, sysMock): 
    """Invoke main() when run as a script.""" 
    mainMock = Mock() 
    sysMock.argv = [] 
    run_script('__main__', 'docdocdoc', mainMock) 
    mainMock.assert_called_once_with() 
    sysMock.exit.assert_called_once_with(mainMock()) 
    self.assertEqual(sysMock.stdout.write.mock_calls, []) 

@patch('mymodule.utils.sys') 
def test_run_script_with_help(self, sysMock): 
    """Print help when the user asks for help.""" 
    mainMock = Mock() 
    for h in ('-h', '--help'): 
     sysMock.argv = [h] 
     run_script('__main__', h*5, mainMock) 
     self.assertEqual(mainMock.mock_calls, []) 
     self.assertEqual(sysMock.exit.mock_calls, []) 
     sysMock.stdout.write.assert_called_with(h*5) 

布拉姆!現在您可以編寫一個可測試的main(),將其作爲腳本調用,具有100%的測試覆蓋率,並且不需要忽略覆蓋報告中的任何代碼。

+8

I欣賞尋找解決方案的創造力和毅力,但是如果你在我的團隊中,我會否決這種編碼方式.Python的一個優點是它具有高度的慣用性,'if __name__ == ...'是**這種方式讓模塊腳本,任何pythonista都會識別這行,並理解它的作用,你的解決方案只是混淆了明顯的原因,沒有什麼好的理由,除了搔癢智力瘙癢外如我所說:一個聰明的解決方案,但_clever_並不總是等於_correct_。 – mac 2015-03-09 09:47:39

+0

這很好,如果你只有一個模塊,或者每個模塊在作爲腳本被調用時都會有不同的結果,但正如我所說的,如果__name__ ==有十幾個*完全相同的文件。 ..'塊在最後,這是一個巨大的違反不重複自己當你需要在很多地方對它進行相同的修復時,d也使修復錯誤變得困難。統一這樣的邏輯提高了可測試性並減少了錯誤的可能性。如果您擔心人們不理解它,請將該函數命名爲「if_name_equals_main()」,並且人們會將其計算出來。 – robru 2015-03-10 17:21:18

+7

如果你在'if __name__ ...'下縮進的塊中有**邏輯**,那麼你做錯了,應該重構。 'if __name __...'下的唯一代碼行應該是:'main()'。 – mac 2015-03-10 23:04:14

相關問題