哇。今晚我发现使用unittest
模块编写的Python单元测试不能很好地与trace
模块下的覆盖率分析相匹配。这是foobar.py
中最简单的单元测试:
import unittest
class Tester(unittest.TestCase):
def test_true(self):
self.assertTrue(True)
if __name__ == "__main__":
unittest.main()
如果我使用python foobar.py
运行此操作,我会收到此输出:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
大。现在我也想进行覆盖测试,所以我用python -m trace --count -C . foobar.py
再次运行它,但现在我明白了:
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
不,Python,它不行 - 你没有进行我的测试!似乎在trace
的上下文中以某种方式运行unittest
的测试检测机制。这是我提出的(疯狂)解决方案:
import unittest
class Tester(unittest.TestCase):
def test_true(self):
self.assertTrue(True)
class Insane(object):
pass
if __name__ == "__main__":
module = Insane()
for k, v in locals().items():
setattr(module, k, v)
unittest.main(module)
这基本上是一种解决方法,它通过伪造顶级模块的副本来提升顶层模块的抽象,不可命名的名称。然后,我可以将该名称传递给unittest.main()
,以便回避trace
对其产生的任何影响。无需向您显示输出;它看起来就像上面的成功例子。
所以,我有两个问题:
这里发生了什么?为什么trace
搞砸了unittest
?
是否有更容易和/或更少的疯狂方法来解决这个问题?
答案 0 :(得分:8)
更简单的解决方法是将模块名称明确传递给unittest.main
:
import unittest
class Tester(unittest.TestCase):
def test_true(self):
self.assertTrue(True)
if __name__ == "__main__":
unittest.main(module='foobar')
trace
在unittest
中混淆了测试发现,因为trace
如何加载正在运行的模块。 trace
读取模块源代码,编译它,并在__name__
全局设置为'__main__'
的上下文中执行它。这足以使大多数模块的行为就像它们被称为主模块一样,但实际上并没有改变在Python解释器中注册为__main__
的模块。当unittest
要求__main__
模块扫描测试用例时,它实际上从命令行调用trace
模块,当然这不包含单元测试。
coverage.py
采用不同的方法实际替换__main__
中名为sys.modules
的模块。
答案 1 :(得分:3)
我不知道为什么trace
无法正常工作,但coverage.py会这样做:
$ coverage run foobar.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
$ coverage report
Name Stmts Miss Cover
----------------------------
foobar 6 0 100%
答案 2 :(得分:0)
我喜欢Theran的答案,但至少在Python 3.6上有一些问题:
如果我运行了foobar.py
很好,但是如果我运行了foobar.py Sometestclass
,只执行了Sometestclass
,trace不会选择运行任何测试。
我的解决方法是在适当的时候指定defaultTest:
请记住,单元测试通常以
运行 python foobar.py <-flags and options> <TestClass.testmethod>
,因此定向测试始终是最后一个参数,除非它是一个unittest选项,在这种情况下,它以-
开头。还是foobar.py文件本身。
lastarg = sys.argv[-1]
#not a flag, not foobar.py either...
if not lastarg.startswith("-") and not lastarg.endswith(".py"):
defaultTest = lastarg
else:
defaultTest = None
unittest.main(module=os.path.splitext(os.path.basename(__file__))[0], defaultTest=defaultTest)
无论如何,现在跟踪只执行所需的测试,如果没有另外指定,则全部执行。