Python unittest:如何指定自定义相等谓词?

Python unittest : how to specify custom equality predicate?

这可能是一个简单的问题;我想在 Python unittest 测试用例中使用自定义相等运算符。例如,假设我想测试一个 "number-to-string" 函数,并且我想执行不区分大小写的字符串比较。

以下是我想写的内容:

class MyTest(unittest.TestCase):
    def testFoo(self):
        self.assertCheck(ci_string_eq,i2s(24),"Twenty-Four")

问题是 assertCheck 不是问题。

一些明显的解决方法:

我希望我遗漏了一些明显的东西?

非常感谢!

编辑:有些人建议我覆盖 __eq__。这不是我想要的。具体来说,我的代码的客户使用 __eq__ 方法来确定 是否应考虑两个对象 "the same"(参见 "extensional equality")。 不过,出于测试目的,我想使用不同的谓词进行测试。 所以覆盖 __eq__ 并不能解决我的问题。

Here's the full list of supported assertions in Python 3.6's unittest module.

如您所见,没有采用自定义谓词进行评估的断言,但您可以通过将 自定义错误消息 传递给您的断言来获得更有帮助的错误消息通过 msg 参数的方法。

例如:

class MyTest(unittest.TestCase):
  def testFoo(self):
    self.assertEqual(i2s(24),"Twenty-Four",msg="i2s(24) should be 'Twenty-Four'")

如果这对您来说还不够,您真的不需要深入研究 unittest:您可以定义一个 class 来扩展 unittest 的测试用例您需要的方法,即:

class CustomTestCase(unittest.TestCase):
  def assertCheck(self):
    ...

然后您将测试定义为:

class MyTest(CustomTestCase):
  def testFoo(self):
    self.assertCheck(...)

您可以覆盖 return 值 class 的 __eq__ 方法,而无需通过子class 对原始单位进行卷积。

class Helper(FooReturnValueClass):

  def __init__(self, obj=None, **kwargs):
     self.obj = obj
     # any other attrs
     # probably good idea to skip calling super

  def __eq__(self, other):
    # logic

class MyTest(unittest.TestCase):

    def testFoo(self):
      expect = self.Helper(...)
      actual = ClassUnderTest.foo(...)
      self.assertEqual(expect, foo) # Order is important

好消息是,无需任何复杂的连接即可使用您自己的规则进行自定义断言。只需进行比较,收集任何有用的信息,然后在需要时调用 fail(msg)。这将处理您需要的任何报告。

当然,我很懒,连有用的信息都不喜欢收集。我经常发现有用的是从预期数据和实际数据中去除不相关的东西,然后使用常规 assertEquals(expected, actual).

下面是这两种技术的示例,以及使用 longMessage 包含上下文的奖励技术:

# file scratch.py

from unittest import TestCase
import sys

def convert(i):
    results = 'ONE TOO THREE'.split()
    return results[i-1]


class FooTest(TestCase):
    def assertResultEqual(self, expected, actual):
        expected_lower = expected.lower()
        actual_lower = actual.lower()
        if expected_lower != actual_lower:
            self.fail('Results did not match: {!r}, {!r}, comparing {!r}, {!r}'.format(
                expected,
                actual,
                expected_lower,
                actual_lower))

    def assertLazyResultEqual(self, expected, actual):
        self.assertEqual(expected.lower(), actual.lower())

    def assertLongLazyResultEqual(self, expected, actual):
        self.longMessage = True
        self.assertEqual(expected.lower(),
                         actual.lower(),
                         'originals: {!r}, {!r}'.format(expected, actual))

    def test_good_convert(self):
        expected = 'One'

        s = convert(1)

        self.assertResultEqual(expected, s)
        self.assertLazyResultEqual(expected, s)
        self.assertLongLazyResultEqual(expected, s)

    def test_bad_convert(self):
        expected = 'Two'

        s = convert(2)

        self.assertResultEqual(expected, s)

    def test_lazy_bad_convert(self):
        expected = 'Two'

        s = convert(2)

        self.assertLazyResultEqual(expected, s)

    def test_long_lazy_bad_convert(self):
        expected = 'Two'

        s = convert(2)

        self.assertLongLazyResultEqual(expected, s)

生成以下输出,包括上下文以及通过和失败计数的报告。

$ python -m unittest scratch
F.FF
======================================================================
FAIL: test_bad_convert (scratch.FooTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/don/workspace/scratch/scratch.py", line 43, in test_bad_convert
    self.assertResultEqual(expected, s)
  File "/home/don/workspace/scratch/scratch.py", line 18, in assertResultEqual
    actual_lower))
AssertionError: Results did not match: 'Two', 'TOO', comparing 'two', 'too'

======================================================================
FAIL: test_lazy_bad_convert (scratch.FooTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/don/workspace/scratch/scratch.py", line 50, in test_lazy_bad_convert
    self.assertLazyResultEqual(expected, s)
  File "/home/don/workspace/scratch/scratch.py", line 21, in assertLazyResultEqual
    self.assertEqual(expected.lower(), actual.lower())
AssertionError: 'two' != 'too'
- two
?  ^
+ too
?  ^


======================================================================
FAIL: test_long_lazy_bad_convert (scratch.FooTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/don/workspace/scratch/scratch.py", line 57, in test_long_lazy_bad_convert
    self.assertLongLazyResultEqual(expected, s)
  File "/home/don/workspace/scratch/scratch.py", line 27, in assertLongLazyResultEqual
    'originals: {!r}, {!r}'.format(expected, actual))
AssertionError: 'two' != 'too'
- two
?  ^
+ too
?  ^
 : originals: 'Two', 'TOO'

----------------------------------------------------------------------
Ran 4 tests in 0.002s

FAILED (failures=3)

如果自定义比较适用于特定 class,那么您可以 add a custom equality operator 用于 class。如果你在你的 setUp() 方法中这样做,那么所有的测试方法都可以用那个 class 调用 assertEquals(),你的自定义比较将被调用。

内置的 unittest 模块有一个特定的方法,称为 addTypeEqualityFunc。你可以阅读它 here。 你只需要编写你的相等函数并传递它,然后像往常一样简单地使用 assertEqual 方法。