在最近的一次代码审核中,我偶然发现了一个不易立即发现的问题 - 使用了assertTrue()
代替assertEqual()
基本上导致了< em>什么都不测试。这是一个简化的例子:
from unittest import TestCase
class MyTestCase(TestCase):
def test_two_things_equal(self):
self.assertTrue("a", "b")
这里的问题是测试会通过;从技术上讲,代码是有效的,因为assertTrue
has this optional msg
argument(在这种情况下得到"b"
值。)
我们能不能依靠审核代码的人来发现这类问题?有没有办法自动检测使用flake8
或pylint
进行静态代码分析?
答案 0 :(得分:10)
Python现在有一个类型提示系统,可以进行静态代码分析。使用此系统,您可以要求像assertTrue
这样的函数的第一个参数始终是布尔值。问题是assertTrue
不是由您定义的,而是由unittest包定义的。不幸的是,unittest包没有添加类型提示。虽然有一个相当简单的方法:只需定义自己的包装器。
from unittest import TestCase
class TestCaseWrapper(TestCase):
def assertTrue(self, expr: bool, msg=None): #The ": bool" requires that the expr parameter is boolean.
TestCase.assertTrue(self, expr, msg)
class MyTestCase(TestCaseWrapper):
def test_two_things_equal(self):
self.assertTrue("a", "b") #Would give a warning about the type of "a".
然后你可以像这样运行类型检查器:
python -m mypy my_test_case.py
这应该会给你一个关于如何&#34; a&#34;是一个字符串,而不是布尔值。关于这一点的好处是它可以在自动化测试框架中自动运行。此外,PyCharm会检查您的代码中的类型,如果您提供它们并突出显示任何错误。
答案 1 :(得分:5)
几年前,我想出了一种确保测试质量的通用方法/方法。测试规范可以简化为两个条款:
据我所知,虽然要求1.经常被执行,但很少关注要求2.
典型地
实际情况可能是(某些)测试包含会阻止它们捕获代码中的错误的错误。因此,看到测试通过不应该对关心系统质量的人表现出很大的安宁,直到他们确信测试确实能够检测出他们针对 1 设计的问题。 。一个简单的方法是实际引入这些问题并检查它们是否不会被测试忽视!
在TDD(测试驱动开发)中,这个想法只是部分遵循 - 建议在代码之前添加测试,看它失败(它应该,因为还没有代码)然后通过写入修复它代码。但是由于缺少代码而导致测试失败并不意味着在代码错误的情况下它也会失败(这似乎适用于您的情况)!
因此,测试套件的质量可以测量为它能够检测到的错误的百分比。任何逃避测试套件的合理 2 错误都表明了一个覆盖该场景的新测试用例(或者,如果测试套件应该捕获该错误,则会发现测试套件中的错误)。这也意味着套件的每个测试必须能够捕获至少一个错误(否则,该测试完全没有意义)。
我正在考虑实施一个有助于采用这种方法的软件系统(即允许在代码库中注入和维护人为错误并检查测试如何响应它们)。这个问题充当了触发器,我将立即开始研究它。希望在一周内把东西放在一起。请继续关注!
修改强>
该工具的原型版现已在https://bitbucket.org/leon_manukyan/trit处提供。我建议克隆存储库并运行演示流程。
1 对于更广泛的系统/情况(通常都与安全性/安全性有关),此声明的更通用版本是正确的:
针对某些事件设计的系统必须针对此类事件进行例行测试,否则很容易降级,无法对感兴趣的事件做出反应。
举个例子 - 你家里有火警系统吗?你什么时候见证它上次工作?如果在火灾期间保持沉默怎么办?现在去房间里抽烟吧!
2 在此方法的范围内,类似于后门的错误(例如,当传入的URL等于{{1}时,该功能行为错误 })不合理
答案 2 :(得分:3)
针对此类问题的一种解决方案是使用"mutation testing"。这个想法是通过在代码中引入一些小变化来自动生成代码的“突变体”。然后你的测试套件针对这些突变体运行,如果它们很好,它们中的大多数应该被杀死,这意味着你的测试套件检测到突变并且测试失败。
变异测试实际上评估了测试的质量。在你的例子中,没有突变体被杀死,你很容易发现测试有问题。
在python中,有几个可用的变异框架:
答案 3 :(得分:2)
快速解决方案是提供一个检查正确性的Mixin:
import unittest
class Mixin(object):
def assertTrue(self, *args, **kwargs):
if len(args) > 1:
# TypeError is just an example, it could also do some warning/logging
# stuff in here.
raise TypeError('msg should be given as keyword parameter.')
super().assertTrue(*args, **kwargs)
class TestMixin(Mixin, unittest.TestCase): # Mixin before other parent classes
def test_two_things_equal(self):
self.assertTrue("a", "b")
Mixin还可以检查传递的表达式是否是布尔值:
class Mixin(object):
def assertTrue(self, *args, **kwargs):
if type(args[0]) is bool:
raise TypeError('expression should be a boolean')
if len(args) > 1:
raise TypeError('msg should be given as keyword parameter.')
super().assertTrue(*args, **kwargs)
然而,这不是静态的,它需要手动更改测试类(添加Mixin)并运行测试。此外,它会抛出大量的误报,因为将消息作为关键字参数传递并不常见(至少不是我见过的地方)并且在很多情况下你想要检查表达式的隐含真实性而不是明确的bool
。当if a
为a
,list
等时,喜欢检查非空虚dict
您还可以使用一些setUp
,teardown
代码来更改特定类的assertTrue
方法:
import unittest
def decorator(func):
def wrapper(*args, **kwargs):
if len(args) > 1:
raise TypeError()
return func(*args, **kwargs)
return wrapper
class TestMixin(unittest.TestCase):
def setUp(self):
self._old = self.assertTrue
self.assertTrue = decorator(self.assertTrue)
def tearDown(self):
self.assertTrue = self._old
def test_two_things_equal(self):
self.assertTrue("a", "b")
但是在应用这些方法之前要谨慎一点:在改变现有测试之前一定要小心。不幸的测试有时很难被记录,因此他们测试的内容以及测试方法并不总是很明显。有时测试没有意义,改变它是安全的,但有时候它会以一种奇怪的方式测试一个特定的功能,当你改变它时,你会改变测试的内容。因此,至少确保在更改测试用例时没有更改覆盖率。如有必要,请确保通过更新方法名称,方法文档或内联注释来阐明测试目的。