[英]Inheriting from unittest.TestCase for non-test functionality
我想編寫一個類來准確地使用unittest.TestCase.assertEqual
為測試set
相等性而表現出的行為來檢查集。 它會自動打印一條漂亮的消息,說明哪些元素僅在第一組中,哪些僅在第二組中。
我意識到我可以實現類似的行為,但是由於已經使用unittest.TestCase.assertEqual
很好地完成了操作,所以我只想利用它(所以請不要回答那些無用且已經很明顯的建議(但不適用於這種情況)) “不要用unittest.TestCase
解決此問題”)
這是我的SetChecker
類的代碼:
import unittest
class SetChecker(unittest.TestCase):
"""
SetChecker(set1, set2) creates a set checker from the two passed Python set
objects. Printing the SetChecker uses unittest.TestCase.assertEqual to test
if the sets are equal and automatically reveal the elements that are in one
set but not the other if they are unequal. This provides an efficient way
to detect differences in possibly large set objects. Note that this is not
a unittest object, just a wrapper to gain access to the helpful behavior of
unittest.TestCase.assertEqual when used on sets.
"""
EQUAL_MSG = "The two sets are equivalent."
def __init__(self, set1, set2, *args, **kwargs):
assert isinstance(set1, set)
assert isinstance(set2, set)
super(self.__class__, self).__init__(*args, **kwargs)
try:
self.assertEqual(set1, set2)
self._str = self.EQUAL_MSG
self._str_lines = [self._str]
self._indxs = None
except AssertionError, e:
self._str = str(e)
self._str_lines = self._str.split('\n')
# Find the locations where a line starts with 'Items '.
# This is the fixed behavior of unittest.TestCase.
self._indxs = [i for i,y in enumerate(self._str_lines)
if y.startswith('Items ')]
def __repr__(self):
"""
Convert SetChecker object into a string to be printed.
"""
return self._str
__str__ = __repr__ # Ensure that `print` and __repr__ do the same thing.
def runTest(self):
"""
Required by any sub-class of unittest.TestCase. Solely used to inherit
from TestCase and is not implemented for any behavior.
"""
pass
def in_first_set_only(self):
"""
Return a list of the items reported to exist only in the first set. If
the sets are equivalent, returns a string saying so.
"""
return (set(self._str_lines[1:self._indxs[1]])
if self._indxs is not None else self.EQUAL_MSG)
def in_second_set_only(self):
"""
Return a list of the items reported to exist only in the second set. If
the sets are equivalent, returns a string saying so.
"""
return (set(self._str_lines[1+self._indxs[1]:])
if self._indxs is not None else self.EQUAL_MSG)
當我在IPython中使用它時,它工作正常:
In [1]: from util.SetChecker import SetChecker
In [2]: sc = SetChecker(set([1,2,3, 'a']), set([2,3,4, 'b']))
In [3]: sc
Out[3]:
Items in the first set but not the second:
'a'
1
Items in the second set but not the first:
'b'
4
In [4]: print sc
Items in the first set but not the second:
'a'
1
Items in the second set but not the first:
'b'
4
In [5]: sc.in_first_set_only()
Out[5]: set(["'a'", '1'])
In [6]: sc.in_second_set_only()
Out[6]: set(["'b'", '4'])
但是現在我也想為此類編寫單元測試。 所以我做了一個TestSetChecker
類。 這是該代碼:
from util.SetChecker import SetChecker
class TestSetChecker(unittest.TestCase):
"""
Test class for providing efficient comparison and printing of
the difference between to sets.
"""
def setUp(self):
"""
Create examples for testing.
"""
self.set1 = set([1, 2, 3, 'a'])
self.set2 = set([2, 3, 4, 'b'])
self.set3 = set([1,2])
self.set4 = set([1,2])
self.bad_arg = [1,2]
self.expected_first = set(['1', 'a'])
self.expected_second = set(['4', 'b'])
self.expected_equal_message = SetChecker.EQUAL_MSG
self.expected_print_string = (
"Items in the first set but not the second:\n'a'\n1\n"
"Items in the second set but not the first:\n'b'\n4")
def test_init(self):
"""
Test constructor, assertions on args, and that instance is of proper
type and has expected attrs.
"""
s = SetChecker(self.set1, self.set2)
self.assertIsInstance(s, SetChecker)
self.assertTrue(hasattr(s, "_str"))
self.assertTrue(hasattr(s, "_str_lines"))
self.assertTrue(hasattr(s, "_indxs"))
self.assertEqual(s.__repr__, s.__str__)
self.assertRaises(AssertionError, s, *(self.bad_arg, self.set1))
def test_repr(self):
"""
Test that self-printing is correct.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
self.assertEqual(str(s1), self.expected_print_string)
self.assertEqual(str(s2), self.expected_equal_message)
def test_print(self):
"""
Test that calling `print` on SetChecker is correct.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
s1_print_output = s1.__str__()
s2_print_output = s2.__str__()
self.assertEqual(s1_print_output, self.expected_print_string)
self.assertEqual(s2_print_output, self.expected_equal_message)
def test_in_first_set_only(self):
"""
Test that method gives list of set elements found only in first set.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
fs1 = s1.in_first_set_only()
fs2 = s2.in_first_set_only()
self.assertEqual(fs1, self.expected_first)
self.assertEqual(fs2, self.expected_equal_message)
def test_in_second_set_only(self):
"""
Test that method gives list of set elements found only in second set.
"""
s1 = SetChecker(self.set1, self.set2)
s2 = SetChecker(self.set3, self.set4)
ss1 = s1.in_second_set_only()
ss2 = s2.in_second_set_only()
self.assertEqual(ss1, self.expected_second)
self.assertEqual(ss2, self.expected_equal_message)
if __name__ == "__main__":
unittest.main()
據我所知, TestSetChecker
與我編寫的許多其他單元測試類沒有區別(除了要測試的特定功能之外)。
但是,當我嘗試執行包含單元測試的文件時,看到一個非常不尋常的__init__
錯誤:
EMS@computer ~/project_dir/test $ python TestSetChecker.py
Traceback (most recent call last):
File "TestSetChecker.py", line 84, in <module>
unittest.main()
File "/opt/python2.7/lib/python2.7/unittest/main.py", line 94, in __init__
self.parseArgs(argv)
File "/opt/python2.7/lib/python2.7/unittest/main.py", line 149, in parseArgs
self.createTests()
File "/opt/python2.7/lib/python2.7/unittest/main.py", line 155, in createTests
self.test = self.testLoader.loadTestsFromModule(self.module)
File "/opt/python2.7/lib/python2.7/unittest/loader.py", line 65, in loadTestsFromModule
tests.append(self.loadTestsFromTestCase(obj))
File "/opt/python2.7/lib/python2.7/unittest/loader.py", line 56, in loadTestsFromTestCase
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
TypeError: __init__() takes at least 3 arguments (2 given)
在我的環境中,帶有Python unittest
源代碼的目錄是只讀的,因此我無法在其中添加pdb
或什至print
語句,以查看此時__init__
失敗的testCaseClass
或testCaseNames
是什么。
但是我看不到代碼中沒有為任何__init__
方法提供所需參數的地方。 我想知道這是否與某些繼承自unittest
類的幕后魔術有關,以及與我正在導入並實例化要為unit執行的文件中的類( SetChecker
)有關的事實試驗。
也許它會檢查現有名稱空間中繼承自TestCase
所有類? 如果是這樣,如何對單元測試進行單元測試?
我還嘗試首先使SetChecker
從object
繼承,並嘗試像混入一樣使用TestCase
,但是這產生了許多MRO錯誤,並且似乎比它值得的更頭痛。
我嘗試搜索此內容,但搜索起來很困難(因為__init__
參數似乎不是直接的問題)。
通過使SetChecker
僅從object
繼承,然后在SetChecker
內部提供從unittest.TestCase
繼承的內部類,我能夠解決此問題。
問題是unittest.main
檢查其運行模塊的整個名稱空間。 它發現模塊,從繼承任何類unittest.TestCase
將得到充分的測試套件處理(它會嘗試構建類的實例每個test_
方法,它可以找到,或者只是為了runTest
,如果發現沒有test_
方法) 。
在我的情況下,由於set參數是必需的,無論unittest.main
在做什么,它都傳遞了一些參數(可能是要當作測試的函數的名稱,在這種情況下為字符串"runTest"
),但是未能傳遞第二個必需的參數。 即使這與我的類的簽名一起起作用(例如,假設我用兩個set的tuple
替換了兩個不同的參數set1
和set2
),一旦嘗試對該字符串進行set操作,它將立即失敗。
似乎沒有一種簡單的方法可以告訴unittest.main
忽略某個或某些類。 因此,通過使SetChecker
只是其中具有 TestCase
的對象, unittest.main
不再查找該TestCase
並且不再在乎。
還有一個錯誤:在我的test_init
函數中,我使用assertRaises
期望可調用,但從未給我的SetChecker
類提供__call__
函數。
這是對SetChecker
類的修改,為我修復了此問題:
class SetChecker(object):
"""
SetChecker(set1, set2) creates a set checker from the two passed Python set
objects. Printing the SetChecker uses unittest.TestCase.assertEqual to test
if the sets are equal and automatically reveal the elements that are in one
set but not the other if they are unequal. This provides an efficient way
to detect differences in possibly large set objects. Note that this is not
a unittest object, just a wrapper to gain access to the helpful behavior of
unittest.TestCase.assertEqual when used on sets.
"""
EQUAL_MSG = "The two sets are equivalent."
class InternalTest(unittest.TestCase):
def runTest(self): pass
def __init__(self, set1, set2):
assert isinstance(set1, set)
assert isinstance(set2, set)
self.int_test = SetChecker.InternalTest()
try:
self.int_test.assertEqual(set1, set2)
self._str = self.EQUAL_MSG
self._str_lines = [self._str]
self._indxs = None
except AssertionError, e:
self._str = str(e)
self._str_lines = self._str.split('\n')
# Find the locations where a line starts with 'Items '.
# This is the fixed behavior of unittest.TestCase.
self._indxs = [i for i,y in enumerate(self._str_lines)
if y.startswith('Items ')]
@classmethod
def __call__(klass, *args, **kwargs):
"""
Makes the class callable such that calling it like a function is the
same as constructing a new instance.
"""
return klass(*args, **kwargs)
# Everything else below is the same...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.