2016-02-08 25 views
10

foo是一個深度目錄嵌套的Python項目,包括各種子目錄中的〜30 unittest文件。在foosetup.py,我已經added a custom "test" command內部運行使用unittest發現傳遞參數(用於argparse)

python -m unittest discover foo '*test.py' 

注意,這裏使用unittest's discovery模式。


由於一些測試非常慢,我最近決定測試應該有「水平」。 this question的答案很好地解釋瞭如何讓unittest和​​彼此良好地工作。所以,現在,我可以運行個人單元測試文件,說foo/bar/_bar_test.py,與

python foo/bar/_bar_test.py --level=3 

,只有3級測試運行。

的問題是,我無法弄清楚如何通過自定義的標誌(在這種情況下,「--level = 3」使用發現,無論我嘗試失敗,如:

$ python -m unittest discover --level=3 foo '*test.py' 
Usage: python -m unittest discover [options] 

python -m unittest discover: error: no such option: --level 

$ python -m --level=3 unittest discover foo '*test.py' 
/usr/bin/python: No module named --level=3 

哪有我通過--level=3個別單元測試?如果可能的話,我想,以避免將不同級別的測試,以不同的文件。

賞金編輯

預賞金(精)解決方案,建議使用系統ENVIRO nment變量。這並不壞,但我正在尋找更乾淨的東西。

更改多個文件的測試運行(即蟒蛇-m單元測試發現富「* test.py」)到別的東西是好的,只要:

  1. 它可以生成一個報表多文件單元測試。
  2. 它可以以某種方式支持多個測試級別(使用問題中的技術或使用其他不同的機制)。

回答

2

使用單元測試發現這不通過指定參數時,但它實現了你正在努力去做。

這是leveltest.py。把它放在某處模塊搜索路徑(也許當前目錄或站點包):

import argparse 
import sys 
import unittest 

# this part copied from unittest.__main__.py 
if sys.argv[0].endswith("__main__.py"): 
    import os.path 
    # We change sys.argv[0] to make help message more useful 
    # use executable without path, unquoted 
    # (it's just a hint anyway) 
    # (if you have spaces in your executable you get what you deserve!) 
    executable = os.path.basename(sys.executable) 
    sys.argv[0] = executable + " -m leveltest" 
    del os 

def _id(obj): 
    return obj 

# decorator that assigns test levels to test cases (classes and methods) 
def level(testlevel): 
    if unittest.level < testlevel: 
     return unittest.skip("test level too low.") 
    return _id 

def parse_args(): 
    parser = argparse.ArgumentParser() 
    parser.add_argument('--level', type=int, default=3) 
    ns, args = parser.parse_known_args(namespace=unittest) 
    return ns, sys.argv[:1] + args 

if __name__ == "__main__": 
    ns, remaining_args = parse_args() 

    # this invokes unittest when leveltest invoked with -m flag like: 
    # python -m leveltest --level=2 discover --verbose 
    unittest.main(module=None, argv=remaining_args) 

這裏是你如何在一個例子testproject.py文件中使用它:

import unittest 
import leveltest 

# This is needed before any uses of the @leveltest.level() decorator 
# to parse the "--level" command argument and set the test level when 
# this test file is run directly with -m 
if __name__ == "__main__": 
    ns, remaining_args = leveltest.parse_args() 

@leveltest.level(2) 
class TestStringMethods(unittest.TestCase): 

    @leveltest.level(5) 
    def test_upper(self): 
     self.assertEqual('foo'.upper(), 'FOO') 

    @leveltest.level(3) 
    def test_isupper(self): 
     self.assertTrue('FOO'.isupper()) 
     self.assertFalse('Foo'.isupper()) 

    @leveltest.level(4) 
    def test_split(self): 
     s = 'hello world' 
     self.assertEqual(s.split(), ['hello', 'world']) 
     # check that s.split fails when the separator is not a string 
     with self.assertRaises(TypeError): 
      s.split(2) 

if __name__ == '__main__': 
    # this invokes unittest when this file is executed with -m 
    unittest.main(argv=remaining_args) 

然後,您可以運行測試通過直接運行testproject.py,如:

~roottwo\projects> python testproject.py --level 2 -v 
test_isupper (__main__.TestStringMethods) ... skipped 'test level too low.' 
test_split (__main__.TestStringMethods) ... skipped 'test level too low.' 
test_upper (__main__.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.000s 

OK (skipped=3) 

~roottwo\projects> python testproject.py --level 3 -v 
test_isupper (__main__.TestStringMethods) ... ok 
test_split (__main__.TestStringMethods) ... skipped 'test level too low.' 
test_upper (__main__.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=2) 

~roottwo\projects> python testproject.py --level 4 -v 
test_isupper (__main__.TestStringMethods) ... ok 
test_split (__main__.TestStringMethods) ... ok 
test_upper (__main__.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=1) 

~roottwo\projects> python testproject.py --level 5 -v 
test_isupper (__main__.TestStringMethods) ... ok 
test_split (__main__.TestStringMethods) ... ok 
test_upper (__main__.TestStringMethods) ... ok 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK 

通過使用單元測試發現這樣的:

~roottwo\projects> python -m leveltest --level 2 -v 
test_isupper (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_split (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.003s 

OK (skipped=3) 

~roottwo\projects> python -m leveltest --level 3 discover -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=2) 

~roottwo\projects> python -m leveltest --level 4 -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... ok 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=1) 

~roottwo\projects> python -m leveltest discover --level 5 -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... ok 
test_upper (testproject.TestStringMethods) ... ok 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK 

,或者通過指定測試用例來運行,如:

~roottwo\projects>python -m leveltest --level 3 testproject -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.002s 

OK (skipped=2) 
+0

所以,感謝您的回答,但我無法弄清楚這是否允許像'discover'這樣的功能來查看目錄中的所有文件,然後爲它們生成單個報告。 –

+0

它使用'unittest'來完成所有的測試。所以,是的,它提供了與'unittest'相同的報告。我的答案中的示例使用-v(詳細)標誌進行單元測試,以提供有關所有測試的詳細信息,包括哪些測試級別太低而跳過了哪些測試。 – RootTwo

+0

啊,我明白了 - 有趣。感謝您的回答 - 將更多地看待它。不勝感激! –

6

使用發現時無法傳遞參數。從發現 DiscoveringTestLoader類,刪除所有文件不匹配(消除了使用'* test.py --level = 3),並且在使用環境變量僅傳遞文件名到unittest.TextTestRunner

也許唯一的選擇到目前爲止

LEVEL=3 python -m unittest discoverfoo '*test.py' 
+0

環境變量是一個有趣的想法。謝謝。我仍然希望有一些不涉及他們的東西。 –

2

您遇到的問題是unittest參數解析器根本不理解此語法。因此,您必須在調用unittest之前刪除參數。

一個簡單的方法是創建一個包裝模塊(比如my_unittest.py),它查找你的額外參數,從sys.argv中去掉它們,然後調用unittest中的main條目。

現在好了......該包裝的代碼基本上與您已經用於單個文件的代碼相同!你只需要把它放到一個單獨的文件中。

編輯:添加下面的示例代碼的請求......

首先,新的文件,運行的UT(my_unittest.py):

import sys 
import unittest 
from parser import wrapper 

if __name__ == '__main__': 
    wrapper.parse_args() 
    unittest.main(module=None, argv=sys.argv) 

現在parser.py,其中有是在一個單獨的文件,以避免__main__模塊是爲全球基準的工作:

import sys 
import argparse 
import unittest 

class UnitTestParser(object): 

    def __init__(self): 
     self.args = None 

    def parse_args(self): 
     # Parse optional extra arguments 
     parser = argparse.ArgumentParser() 
     parser.add_argument('--level', type=int, default=0) 
     ns, args = parser.parse_known_args() 
     self.args = vars(ns) 

     # Now set the sys.argv to the unittest_args (leaving sys.argv[0] alone) 
     sys.argv[1:] = args 

wrapper = UnitTestParser() 

最後一個樣本測試用例(project_test.py)來測試這些參數是正確解析:

import unittest 
from parser import wrapper 

class TestMyProject(unittest.TestCase): 

    def test_len(self): 
     self.assertEqual(len(wrapper.args), 1) 

    def test_level3(self): 
     self.assertEqual(wrapper.args['level'], 3) 

現在證明:

$ python -m my_unittest discover --level 3 . '*test.py' 
.. 
---------------------------------------------------------------------- 
Ran 2 tests in 0.000s 

OK 
+0

好的,這是一個很好的觀點。實際上,我將它翻譯爲「編寫你自己的unittest包」,它建立在'unittest'上(我不想依賴一些本地的'my_unittest.py')。不過,一個好主意。謝謝! –

+0

@AmiTavory它不一定是單獨的包裹。你可以把它放在你的單元測試中,並簡單地將該python文件放在同一個測試包/目錄中/不管你做什麼來提供你的UT。 –

+0

如果你充實了'my_unittest'的內容,我會很樂意接受你的回答(並獎勵你的賞金)。 –