【问题标题】:Repeated single or multiple tests with Nose用鼻子重复单次或多次测试
【发布时间】:2016-01-11 18:50:29
【问题描述】:

类似于this question,我想让Nose 运行一个测试(或所有测试)n 次——但不是并行。

我在一个项目中有几百个测试;有些是一些简单的单元测试。其他的是具有某种程度的并发性的集成测试。经常在调试测试时,我想更努力地“打”一个测试; bash 循环可以工作,但会产生很多混乱的输出——没有更好的单个“。”每次通过测试。能够在选定的测试中击败一些试验似乎是要求 Nose 做的一件很自然的事情,但我在文档中的任何地方都没有找到它。

让 Nose 执行此操作的最简单方法是什么(除了 bash 循环)?

【问题讨论】:

  • 如果你说出你为什么要这样做可能会有所帮助。另外,'n'会变化吗?如果是这样,你想如何指定它? (例如,在您运行测试时在命令行上?)为什么没有 Bash?这似乎非常适合这项工作。
  • 我扩展了问题的基本原理。

标签: python nose


【解决方案1】:

你可以write a nose test as a generator,然后nose会运行每个函数 产生:

def check_something(arg):
    # some test ...

def test_something():
    for arg in some_sequence:
        yield (check_something, arg)

使用nose-testconfig,您可以将测试运行次数作为命令行参数:

from testconfig import config

# ...

def test_something():
    for n in range(int(config.get("runs", 1))):
        yield (check_something, arg)

你会从命令行调用例如

$ nosetests --tc=runs:5

...不止一次运行。

或者(但也使用nose-testconfig),您可以编写一个装饰器:

from functools import wraps
from testconfig import config

def multi(fn):
    @wraps(fn)
    def wrapper():
        for n in range(int(config.get("runs", 1))):
            fn()
    return wrapper

@multi
def test_something():
    # some test ...

然后,如果您想将测试分成不同的组,每个组都有自己的命令行参数来表示运行次数:

from functools import wraps
from testconfig import config

def multi(cmd_line_arg):
    def wrap(fn):
        @wraps(fn)
        def wrapper():
            for n in range(int(config.get(cmd_line_arg, 1))):
                fn()
        return wrapper
    return wrap

@multi("foo")
def test_something():
    # some test ...

@multi("bar")
def test_something_else():
    # some test ...

你可以这样称呼:

$ nosetests --tc=foo:3 --tc=bar:7

【讨论】:

  • 您的示例中实际上并未使用换行。需要吗?
  • 很好发现 :-) 是的,否则装饰函数将没有正确的名称,鼻子也找不到它。已修复(以及其他几个小错误)。
  • 太棒了!赞成答案——尽管它仍然不是我想要的,因为我必须装饰每一个 O(200) 测试。开始怀疑我是否应该咬紧牙关,看看是否可以为它编写一个鼻子插件。再次感谢。
  • 我按照上面的例子,但是我的测试失败了,说wrapper() takes no arguments (1 given)
  • @DarthOpto 是的,我的回答中给出的代码假定您的测试函数不带参数。如果你对鼻子的使用足够先进,以至于你有测试函数确实接受参数——这是我从未听说过的——你也应该有能力了解为什么你会得到这个结果(以及如何修复它)。
【解决方案2】:

您必须编写一个脚本来执行此操作,但您可以在命令行上重复测试名称 X 次。

nosetests testname testname testname testname testname testname testname

等等。

【讨论】:

    【解决方案3】:

    我最终使用的解决方案是创建 sh 脚本 run_test.sh:

    var=0
    while $1; do
        ((var++))
        echo "*** RETRY $var"
    done
    

    用法:

    ./run_test.sh "nosetests TestName"
    

    它无限运行测试,但在第一个错误时停止。

    【讨论】:

      【解决方案4】:

      一种方法是在测试本身:

      改变这个:

      class MyTest(unittest.TestCase):
      
        def test_once(self):
            ...
      

      到这里:

      class MyTest(unittest.TestCase):
      
        def assert_once(self):
            ...
      
        def test_many(self):
            for _ in range(5):
                self.assert_once()
      

      【讨论】:

      • 当然,这很简单——我通常很乐意每个测试运行一次,但如果我怀疑单个测试(或测试类)偶尔会失败,最好让测试框架给我那个选项。
      【解决方案5】:

      不应有理由多次运行测试。重要的是您的测试是确定性的(即,给定代码库的相同状态,它们总是产生相同的结果。)如果不是这种情况,那么不要多次运行测试,您应该重新设计测试和/或代码,这样他们就可以了。

      例如,测试间歇性失败的一个原因是测试和被测代码 (CUT) 之间的竞争条件。在这种情况下,一个天真的反应是在测试中添加一个大的“voodoo sleep”,以“确保”在测试开始断言之前完成 CUT。

      这很容易出错,因为如果您的 CUT 由于任何原因(硬件功率不足、加载的盒子、繁忙的数据库等)变慢,那么它会偶尔失败。在这种情况下,更好的解决方案是让您的测试等待一个事件,而不是休眠。

      您可以选择任何活动。有时,您可以使用的事件已经生成(例如 Javascript DOM 事件,Selenium 测试可以使用的“pageRendered”类型的事件。)其他时候,您可能适合向 CUT 添加代码,这会引发完成后的事件(也许您的架构涉及对此类事件感兴趣的其他组件。)

      通常,您需要重新编写测试,以便它尝试检测您的 CUT 是否已完成执行(例如,输出文件是否存在?),如果不存在,则休眠 50 毫秒,然后重试.最终它会超时并失败,但只有在很长一段时间后才会这样做(例如,CUT 预期执行时间的 100 倍)

      另一种方法是使用'onion/hexagonal/ports'n'adaptors'原则设计你的CUT,它坚持你的业务逻辑应该没有所有外部依赖。这意味着您的业务逻辑可以使用普通的亚毫秒单元测试进行测试,它永远不会触及网络或文件系统。完成此操作后,您需要的端到端系统测试要少得多,因为它们现在就像集成测试一样,不需要尝试通过 UI 处理业务逻辑的每个细节和边缘案例.这种方法还将在其他领域产生巨大的好处,例如改进的 CUT 设计(减少组件之间的依赖关系),测试更容易编写,并且运行整个测试套件所花费的时间大大减少。

      使用上述方法可以完全消除测试不可靠的问题,我建议这样做,不仅可以改进您的测试,还可以改进您的代码库和设计能力。

      【讨论】:

      • 投反对票,因为 OP 没有问是否他应该多次运行测试,他问怎么做
      • 绝对!但是,这就是“添加评论”按钮的用途。或者,既然您似乎也回答了这个问题,为什么不在此处添加您的警告?
      • 此外,如果集成测试涉及并发性,则很少像我的那样具有确定性。
      • @JohnJ 我同意很难编写确定性的集成测试。但也值得长期努力思考如何让它们变得更加如此。例如,让他们等待事件而不是插入“voodoo sleeps”。此外,考虑通过在纯单元测试中测试业务逻辑来减少问题,从而大大减少您需要编写的涉及 UI 或数据库等的集成测试的数量。
      • @ire_and_curses 我不明白。您链接到的帖子说:“如果您可以对问题提供截然不同的答案,那么可能需要多个单独的答案......将它们放在同一个帖子中会使投票变得困难。”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多