如何为不返回任何内容的纯方法编写测试?


13

我有一堆处理值验证的类。例如,一个RangeValidator类检查一个值是否在指定范围内。

每个验证器类都包含两个方法:is_valid(value),该方法返回TrueFalse取决于值,并ensure_valid(value)检查指定的值,如果值有效,则不执行任何操作,或者,如果值与预定义的规则不匹配,则抛出特定的异常。

当前有两种与此方法关联的单元测试:

  • 传递无效值并确保引发异常的代码。

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • 传递有效值的那个。

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

尽管第二项测试已完成工作-如果抛出异常,则失败,如果ensure_valid没有抛出异常,则成功,但assert内部没有s 的事实看起来很奇怪。读过此类代码的人会立即问自己,为什么要进行似乎无所事事的测试。

当测试不返回值且没有副作用的方法时,这是当前的做法吗?还是应该以其他方式重写测试?或者只是发表评论以解释我在做什么?



11
讲究点,但是如果您有一个不带任何参数(保存为self引用)且不返回结果的函数,则它不是纯函数。
David Arno

10
@DavidArno:这不是一个很重要的问题,它直达问题的核心:该方法很难测试,因为它不纯正。
约尔格W¯¯米塔格

@DavidArno更有趣的一点是,您可以使用“不执行任何操作”方法(假设“不返回任何结果”被解释为“返回一个单位类型”,这void在许多语言中都被调用并且具有愚蠢的规则。)或者,您可以拥有无​​限个循环(即使“不返回结果” 实际上意味着不返回结果也有效
。–德里克·埃尔金斯

任何语言中都有一个类型的纯函数,unit -> unit该函数将unit视为标准数据类型,而不是某种神奇的特殊情况。不幸的是,它只是返回单位而没有执行任何其他操作。
Phoshi

Answers:


21

大多数测试框架都明确声明了“不抛出”,例如Jasmine expect(() => {}).not.toThrow();和nUnit,朋友也都有。


1
而且,如果测试框架没有这样的断言,那么总是有可能创建一个做完全相同的事情的本地方法,从而使代码不言自明。好主意。
Arseni Mourzenko '17

7

这在很大程度上取决于所使用的语言和框架。说到NUnit,有Assert.Throws(...)方法。您可以向他们传递lambda方法:

Assert.Throws(() => rangeValidator.EnsureValid(-5))

这是在中执行的Assert.Throwstry { } catch { }如果捕获到异常,则对lambda的调用很可能被一个块包装,并且断言失败。

如果您的框架没有提供这些方法,则可以通过自己包装调用来解决此问题(我用C#编写):

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

这使意图更加清晰,但在一定程度上使代码混乱。(当然,您可以将所有这些东西包装在一个方法中。)最后由您自己决定。

编辑

正如Doc Brown在评论中指出的那样,问题并不是要指出该方法抛出了,而是没有抛出。在NUnit中也有一个断言

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))

1

只需添加一条注释即可清楚说明为什么不需要断言以及为什么您没有忘记它。

正如您在其他答案中看到的那样,其他任何事情都会使代码更加复杂和混乱。有了注释,其他程序员将知道测试的意图。

话虽如此,这种测试应该是一个例外(无双关语)。如果您发现自己定期编写类似的文章,则可能是测试告诉您设计不是最佳的。


1

您还可以断言某些方法已正确调用(或未调用)。

例如:

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

在您的测试中:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.