【问题标题】:Should NUnit "theory" assumptions include algorithm detailsNUnit“理论”假设是否应该包括算法细节
【发布时间】:2013-10-13 23:50:13
【问题描述】:

假设我想将我的 NUnit 参数化测试方法更改为理论。就理论而言,他们应该定义断言将通过的所有假设/前提条件。根据 NUnit 文档:

[将理论与参数化测试进行比较时]另一方面,理论做出一般性陈述,即它的所有断言都将适用于满足某些假设的所有论点。

但据我了解,这意味着被称为 PUT 的代码应该基本上转换为假设。 完全

那么有理论有什么意义呢?因为我们的算法会被写两次。首先是可测试的代码,其次是理论假设。因此,如果我们在算法中引入错误,我们的代码和测试都可能有相同的错误。 那有什么意义呢?

更好理解的例子

假设我们有一个只支持数字的校验和方法,我们想使用理论对其进行测试。让我们写一个理论:

static Regex rx = new Regex(@"^\d+$", RegexOptions.Compiled);

[Theory]
public void ChecksumTheory(string value)
{
    Assume.That(!string.IsNotNullOrWhiteSpace(value));
    Assume.That(value.Length > 1); // one single number + checksum = same number twice
    Assume.That(rx.IsMatch(value));

    var cc = new ChecksumValidator();

    bool result = cc.ValidateValue(value);

    Assert.IsTrue(result); // not really as algorithm assumptions are missing
}

这是一个非常好的理论,除了没有实际实现测试代码算法并将其表达为一组假设,它的断言仍然不会通过,因为没有明确的算法假设,我们无法知道验证的结果会的。

其他信息

当我们只需要提供关于输入状态的假设即检查特定值是否正确设置或它们的组合是否相关时,理论似乎相当简单和简洁:

[Theory]
public void Person_ValidateState(Person input)
{
    Assume.That(input.Age < 110);
    Assume.That(input.Birth < input.Death || input.Death == null);
    ...
}

问题

  1. 如果需要为所有断言提供足够的假设以使其通过,为什么还要编写单元测试理论?
  2. 如果我们不想通过提供所有算法假设来重新发明轮子,我们如何提供正确的假设?
  3. 如果不是这样,我应该如何重写我的理论以使其成为 NUnit 理论的一个很好的例子?
  4. 测试理论的预期用途是什么(由他们的创造者)?

【问题讨论】:

    标签: unit-testing nunit theory


    【解决方案1】:

    理论与参数化测试

    我还打算在我的测试中引入假设,而不是使用参数化测试。但是由于类似的想法,我仍然没有开始。

    假设的目标是通过应用过滤器,将给定的输入描述为来自不可数(或者说是庞大但完整)的一组值的子集。这样,您上面的代码是绝对正确的,但是在这种情况下,您将不得不为负面结果测试编写几个类似的测试 - 例如。当cc.ValidateValue(...) 的结果为false 时。再一次 - 为了便于理解 - 我仍然会依靠精心挑选的参数来对这个微不足道的函数进行参数化测试。

    另一方面,假设对于更复杂的业务逻辑的测试可能很有用。想象一下,你有一个装满豪华汽车的车库,你想在某个偏远的地形上踩油门——也让我们想象这是一个业务需求,所以你需要为它编写测试(这将是多么酷!)。然后你可以写一个这样的测试:

    [Theory]
    public void CarCanDriveOnMuddyGround(Car car)
    {
        Assume.That(car.IsFourWheelDrive);
        Assume.That(car.HasMotor);
        Assume.That(car.MaxSpeed > 50);
        Assume.That(car.Color != "white");
    
        bool result = car.DriveWithGivenSpeedOn<MuddyGround>(50);
    
        Assert.IsTrue(result);
    }
    

    了解这与 BDD 方法有何密切关系?像你一样,我也不太相信使用假设进行简单的单元测试。但我确信根据不同的测试级别(单元、集成、系统、用户接受度)对测试功能(参数化、断言)使用不同的方法是一个好主意。

    关于假设中的算法细节

    再次考虑您的具体问题。现在我明白你的意思了。用我的话来说:您需要假设一个给定的值会给出一个肯定的结果,然后才能断言它给出一个肯定的结果。对?我认为你找到了一个很好的例子,为什么理论并不总是有效。

    无论如何,我都尝试在一个稍微简单的示例中解决它(为了便于阅读)。但我承认这不是很有说服力:

    public class TheoryTests
    {
        [Datapoints]
        public string[] InvalidValues = new[] { null, string.Empty };
    
        [Datapoints]
        public string[] PositiveValues = new[] { "good" };
    
        [Datapoints]
        public string[] NegativeValues = new[] { "Bad" };
    
        private bool FunctionUnderTest(string value)
        {
            return value.ToLower().Equals(value);
        }
    
        [Theory]
        public void PositiveTest(string value)
        {
            Assume.That(!string.IsNullOrEmpty(value));
    
            var result = FunctionUnderTest(value);
    
            Assert.True(result);
        }
    
        [Theory]
        public void PassingPositiveTest(string value)
        {
            Assume.That(!string.IsNullOrEmpty(value));
            Assume.That(!NegativeValues.Contains(value));
    
            var result = FunctionUnderTest(value);
    
            Assert.True(result);
        }
    }
    

    PositiveTest 显然会失败,因为缺少算法假设。 请参阅PassingPositiveTest 正文中的第二行,它可以防止测试失败。当然,缺点是这实际上是一个基于示例的测试,而不是一个纯粹的基于理论的测试。欢迎提出更好的想法。

    【讨论】:

    • 我编辑了我的问题以添加有关仅提供基于输入状态的假设的理论的附加信息。这相当微不足道,这正是您在示例中提供的内容。但是,在我的示例中必须对算法做出假设的理论怎么样:验证给定输入的校验和?
    • 好的,这是向前迈出的一步,但是通过使用通常的参数化测试,您现在完成的方式会好得多,因为您将提供正面和负面以及预期的结果。我真的看不出理论适合什么地方?
    • 这就是重点!这两种技术都有其优缺点,对于您的场景,参数化测试更有意义。在我看来,没有最好的解决方案。但是,如果您想对所有测试采用简单的 Theory 方式,那么这是解决此问题的有效workaround
    • 我现在处境尴尬。你是唯一一个回答我问题的人,但我没有得到任何有价值的答案。我应该奖励你赏金吗?如果我接受你的回答,这意味着其他人会在那里期待有价值的信息,但他们不会。
    • 我会把它当作一种恭维。
    猜你喜欢
    • 2010-09-21
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多