在 Python 中寻找片状测试的根本原因
Hunt for root cause of flaky test in Python
有一个不稳定的测试,我们不知道根本原因是什么。
with pytest.raises(foo.geocoder.GeocodingFailed):
foo.geocoder.geocode('not a valid place')
有时不会发生异常。
我查看了文档 how to handle failures,但这没有帮助。
如何跟踪 geocode()
以便在没有发生异常时看到跟踪?
我查看了标准库的trace
模块。但是似乎没有简单的方法可以将跟踪作为字符串。
“踪迹”的意思是:在 gecode()
期间执行的所有行的踪迹。我希望看到带有缩进的方法调用和 returns 语句。我想忽略 Python 标准库中的行。
据我所知,像 pdb 这样的调试器在这里没有帮助,因为测试只有在 CI 中获得 运行 时才会失败,并且每月只有一两次。
trace
库没有帮助,因为它不能写入 string
或 StringIO
对象,它只能写入 real 个文件。
你可以做的是使用 sys.settrace()
并定义一个简单的函数,每次执行都会调用它。您将在此处找到文档:https://docs.python.org/3/library/sys.html#sys.settrace。
基本魔法是从 frame
对象中获取详细信息,记录在此处:https://docs.python.org/3/library/inspect.html.
给您一个想法的示例如下所示:
import sys
def tracer(frame, event, arg):
print(frame.f_code.co_name, frame.f_code.co_filename, frame.f_lineno, event, arg)
def bad_function(param: int):
if param == 20:
raise RuntimeError(f'that failed for {param}')
sys.settrace(tracer)
bad_function(1)
bad_function(20)
bad_function(2)
应该很容易将该信息存储到字符串中以供进一步调查,或者在出现异常时处理异常。
您尝试过将 pytest-repeat
与 --pdb
一起使用吗?
类似于 pytest test_geocode.py --count 10 --pdb
。如果您的 CI 在 docker 容器中工作并且您可以连接到容器,这可能会起作用。
或者,Jenkins 允许您在失败时暂停,然后以这种方式进行调查,但由于它不稳定并且每月只发生一次或两次,然后从字面上添加 --pdb
标志作为 [= 的一部分31=] 这样您就可以随时连接。您需要一种方法来通知您它已进入此流程,否则构建可能会超时。但如果您密切关注您的构建,这可能不是问题。
此外,如果您还没有使用 pytest-html
,可能值得整合以获得关于发生了什么(通过日志行)以及失败或仅使用
的一个很好的报告
pytest test_geocode.py --log-file=/path/to/log/file --log-file-level=DEBUG
.
将 pytest html 报告和/或日志文件作为人工制品附加到失败的构建中将允许更好的调试。
trace
模块通过 trace.Trace
class.
提供编程访问
在测试失败时,Trace
class' 控制台输出可见。
并且它有覆盖率报告可以写在选定的路径上。
我排除 sys.base_exec_prefix
,sys.base_prefix
不跟踪 Python 库模块。
import pytest
def f(x):
import random
choice = random.choice((True, False))
if choice:
raise ValueError
else:
return x * 2
def trace(f, *args, **kwargs):
import trace
import sys
tracer = trace.Trace(
ignoredirs=(sys.base_exec_prefix, sys.base_prefix), timing=True,
)
ret = tracer.runfunc(f, *args, **kwargs)
r = tracer.results()
r.write_results(coverdir="/tmp/xx-trace")
return ret
def test_f():
with pytest.raises(ValueError):
trace(f, 3)
覆盖率报告;
标有 >>>>>> 的行未执行,即未跟踪,带冒号的数字是执行计数。
>>>>>> import pytest
>>>>>> def f(x):
1: import random
1: choice = random.choice((True, False))
1: if choice:
>>>>>> raise ValueError
else:
1: return x * 2
>>>>>> def trace(f, *args, **kwargs):
>>>>>> import trace
>>>>>> import sys
>>>>>> tracer = trace.Trace(
>>>>>> ignoredirs=(sys.base_exec_prefix, sys.base_prefix), timing=True,
)
>>>>>> tracer.runfunc(f, *args, **kwargs)
>>>>>> r = tracer.results()
>>>>>> r.write_results(coverdir="/tmp/xx-trace")
>>>>>> def test_f():
>>>>>> with pytest.raises(ValueError):
>>>>>> trace(f, 3)
有一个不稳定的测试,我们不知道根本原因是什么。
with pytest.raises(foo.geocoder.GeocodingFailed):
foo.geocoder.geocode('not a valid place')
有时不会发生异常。
我查看了文档 how to handle failures,但这没有帮助。
如何跟踪 geocode()
以便在没有发生异常时看到跟踪?
我查看了标准库的trace
模块。但是似乎没有简单的方法可以将跟踪作为字符串。
“踪迹”的意思是:在 gecode()
期间执行的所有行的踪迹。我希望看到带有缩进的方法调用和 returns 语句。我想忽略 Python 标准库中的行。
据我所知,像 pdb 这样的调试器在这里没有帮助,因为测试只有在 CI 中获得 运行 时才会失败,并且每月只有一两次。
trace
库没有帮助,因为它不能写入 string
或 StringIO
对象,它只能写入 real 个文件。
你可以做的是使用 sys.settrace()
并定义一个简单的函数,每次执行都会调用它。您将在此处找到文档:https://docs.python.org/3/library/sys.html#sys.settrace。
基本魔法是从 frame
对象中获取详细信息,记录在此处:https://docs.python.org/3/library/inspect.html.
给您一个想法的示例如下所示:
import sys
def tracer(frame, event, arg):
print(frame.f_code.co_name, frame.f_code.co_filename, frame.f_lineno, event, arg)
def bad_function(param: int):
if param == 20:
raise RuntimeError(f'that failed for {param}')
sys.settrace(tracer)
bad_function(1)
bad_function(20)
bad_function(2)
应该很容易将该信息存储到字符串中以供进一步调查,或者在出现异常时处理异常。
您尝试过将 pytest-repeat
与 --pdb
一起使用吗?
类似于 pytest test_geocode.py --count 10 --pdb
。如果您的 CI 在 docker 容器中工作并且您可以连接到容器,这可能会起作用。
或者,Jenkins 允许您在失败时暂停,然后以这种方式进行调查,但由于它不稳定并且每月只发生一次或两次,然后从字面上添加 --pdb
标志作为 [= 的一部分31=] 这样您就可以随时连接。您需要一种方法来通知您它已进入此流程,否则构建可能会超时。但如果您密切关注您的构建,这可能不是问题。
此外,如果您还没有使用 pytest-html
,可能值得整合以获得关于发生了什么(通过日志行)以及失败或仅使用
pytest test_geocode.py --log-file=/path/to/log/file --log-file-level=DEBUG
.
将 pytest html 报告和/或日志文件作为人工制品附加到失败的构建中将允许更好的调试。
trace
模块通过 trace.Trace
class.
提供编程访问
在测试失败时,Trace
class' 控制台输出可见。
并且它有覆盖率报告可以写在选定的路径上。
我排除 sys.base_exec_prefix
,sys.base_prefix
不跟踪 Python 库模块。
import pytest
def f(x):
import random
choice = random.choice((True, False))
if choice:
raise ValueError
else:
return x * 2
def trace(f, *args, **kwargs):
import trace
import sys
tracer = trace.Trace(
ignoredirs=(sys.base_exec_prefix, sys.base_prefix), timing=True,
)
ret = tracer.runfunc(f, *args, **kwargs)
r = tracer.results()
r.write_results(coverdir="/tmp/xx-trace")
return ret
def test_f():
with pytest.raises(ValueError):
trace(f, 3)
覆盖率报告;
标有 >>>>>> 的行未执行,即未跟踪,带冒号的数字是执行计数。
>>>>>> import pytest
>>>>>> def f(x):
1: import random
1: choice = random.choice((True, False))
1: if choice:
>>>>>> raise ValueError
else:
1: return x * 2
>>>>>> def trace(f, *args, **kwargs):
>>>>>> import trace
>>>>>> import sys
>>>>>> tracer = trace.Trace(
>>>>>> ignoredirs=(sys.base_exec_prefix, sys.base_prefix), timing=True,
)
>>>>>> tracer.runfunc(f, *args, **kwargs)
>>>>>> r = tracer.results()
>>>>>> r.write_results(coverdir="/tmp/xx-trace")
>>>>>> def test_f():
>>>>>> with pytest.raises(ValueError):
>>>>>> trace(f, 3)