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
不是问题。
一些明显的解决方法:
- 使用 assertTrue;问题是测试用例失败变得不透明
并且没有帮助; "expected True, got False"。死气沉沉
- 深入研究单元测试并自行扩展;好吧,我希望避免这种情况:)
我希望我遗漏了一些明显的东西?
非常感谢!
编辑:有些人建议我覆盖 __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
方法。
这可能是一个简单的问题;我想在 Python unittest
测试用例中使用自定义相等运算符。例如,假设我想测试一个 "number-to-string" 函数,并且我想执行不区分大小写的字符串比较。
以下是我想写的内容:
class MyTest(unittest.TestCase):
def testFoo(self):
self.assertCheck(ci_string_eq,i2s(24),"Twenty-Four")
问题是 assertCheck
不是问题。
一些明显的解决方法:
- 使用 assertTrue;问题是测试用例失败变得不透明 并且没有帮助; "expected True, got False"。死气沉沉
- 深入研究单元测试并自行扩展;好吧,我希望避免这种情况:)
我希望我遗漏了一些明显的东西?
非常感谢!
编辑:有些人建议我覆盖 __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
方法。