5

有没有一种巧妙的方法可以在 Python 脚本中注入故障?我想避免在源代码中添加以下内容:

failure_ABC = True
failure_XYZ = True
def inject_failure_ABC():
    raise Exception('ha! a fake error')
def inject_failure_XYZ():
    # delete some critical file
    pass

# some real code
if failure_ABC:
    inject_failure_ABC()
# some more real code
if failure_XYZ:
    inject_failure_XYZ()
# even more real code

编辑: 我有以下想法:插入“故障点”作为特制评论。编写一个简单的解析器,该解析器将在 Python 解释器之前调用,并将生成带有实际故障代码的实际检测 Python 脚本。例如:

#!/usr/bin/parser_script_producing_actual_code_and_calls python
# some real code
# FAIL_123
if foo():
    # FAIL_ABC
    execute_some_real_code()
else:
    # FAIL_XYZ
    execute_some_other_real_code()

脚本将任何以 开头的FAIL_内容视为故障点,并根据配置文件启用/禁用故障。你怎么看?

4

3 回答 3

3

在测试错误处理时,最好的方法是将可能引发错误的代码隔离在一个可以在测试中覆盖的新方法中:

class ToTest:
    def foo(...):
        try:
            self.bar() # We want to test the error handling in foo()
        except:
            ....

    def bar(self):
        ... production code ...

在您的测试用例中,您可以使用引发您要测试的异常的代码进行扩展ToTest和覆盖。bar()

编辑您真的应该考虑将大型方法拆分为较小的方法。它将使代码更易于测试、理解和维护。查看测试驱动开发,了解如何改变开发过程的一些想法。

关于您使用“失败评论”的想法。这看起来是一个很好的解决方案。有一个小问题:您必须编写自己的 Python 解析器,因为 Python 在生成字节码时不会保留注释。

所以你可以花几个星期来写这个,也可以花几个星期来让你的代码更容易测试。

但是,有一个区别:如果您不一直走下去,解析器将毫无用处。此外,所花费的时间不会改进您的代码。大部分工作将用于解析器和工具。因此,经过这么长时间,您仍然需要改进代码、添加失败注释并编写测试。

通过重构代码,您可以随时停止,但到目前为止所花费的时间将是有意义的,不会浪费。您的代码将随着您所做的第一次更改而开始变得更好,并且会不断改进。

编写一个复杂的工具需要时间,它会有自己的错误需要修复或解决。这些都不会在短期内改善您的情况,您也不能保证它会在长期内改善。

于 2013-01-10T10:27:27.983 回答
3

您可以使用模拟库,例如unittest.mock,也存在许多第三方库。然后,您可以模拟您的代码使用的某些对象,以便它抛出您的异常或以您希望的任何方式运行。

于 2013-01-10T10:33:37.717 回答
2

If you only want to stop your code at some point, and fall back to interactive interpreter, one can use:

assert 1==0

But this only works if you do not run python with -O

Edit Actually, my first answer was to quick, without really understanding what you want to do, sorry.

Maybe your code becomes already more readable if you do parameterization through parameters, not through variable/function suffices. Something like

failure = {"ABC": False, "XYZ":False}

#Do something, maybe set failure

def inject_failure(failure):
    if not any(failure.values()):
        return
    if failure["ABC"]:
        raise Exception('ha! a fake error')
    elif failure["XYZ"]:
        # delete some critical file
        pass

inject_failure(failure)
于 2013-01-10T10:24:16.417 回答