1

我想编写一个类来检查集合,该类完全使用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)

包含 Pythonunittest源代码的目录在我的环境中是只读的,因此我无法在其中添加pdb甚至print语句来查看此时某些失败的内容testCaseClass或内容。testCaseNames__init__

但是我在我的代码中看不到任何地方我未能为任何__init__方法提供所需的参数。我想知道这是否与某些幕后魔术有关,这些魔术与继承自的类unittest以及我在SetChecker要为单元测试执行的文件中导入和实例化一个类 () 的事实有关。

也许它会检查现有命名空间中继承自的所有类TestCase?如果是这样,您如何对单元测试进行单元测试?

我还尝试先从SetChecker继承object并尝试TestCase像混合一样使用,但这会产生很多 MRO 错误,而且看起来比它的价值更令人头疼。

我试过搜索这个,但搜索起来是一个困难的错误(因为它似乎不是一个简单的__init__参数问题)。

4

1 回答 1

0

SetChecker我可以通过只继承 from来解决这个问题object,然后在内部SetChecker提供一个继承自的内部类unittest.TestCase

问题是unittest.main检查运行它的模块的整个命名空间。它在该模块中找到的任何继承自的类unittest.TestCase都将获得完整的测试套件处理(它将尝试为test_它可以找到的每个方法构造该类的实例,或者仅runTest在它没有找到test_方法的情况下)。

在我的例子中,由于 set 参数是必需的,无论它在unittest.main做什么,它都在传递一些参数(可能是要视为测试的函数的名称,在这种情况下是 string "runTest")但未能传递第二个必需的参数。即使这适用于我的类的签名(例如,假设我用 2 个集合替换了两个不同的参数)set1,一旦它尝试使用该字符串进行集合操作,它也会立即失败。set2tuple

似乎没有一种简单的方法可以告诉unittest.main忽略某个或多个类。因此,通过只制作一个具有内部SetChecker的对象,不再发现它并且不再关心它。 TestCaseunittest.mainTestCase

还有另一个错误:在我的test_init函数中,我使用assertRaiseswhich 需要一个可调用的,但从未给我的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...
于 2013-11-05T17:16:43.137 回答