【问题标题】:Unit testing with multiple inputs具有多个输入的单元测试
【发布时间】:2012-02-11 12:19:52
【问题描述】:

我一直在努力解决单元测试问题,并且我正在尝试处理一个函数的单元测试,该函数的返回值取决于一堆参数。但是信息量很大,有点不知所措..

考虑以下几点:

我有一个类Article,它有一个价格集合。它有一个方法GetCurrentPrice,它根据一些规则确定当前价格:

public class Article
{
    public string Id { get; set; }
    public string Description { get; set; }
    public List<Price> Prices { get; set; }

    public Article()
    {
        Prices = new List<Price>();
    }

    public Price GetCurrentPrice()
    {
        if (Prices == null)
            return null;

        return (
            from
            price in Prices

            where

            price.Active &&
            DateTime.Now >= price.Start &&
            DateTime.Now <= price.End

            select price)
            .OrderByDescending(p => p.Type)
            .FirstOrDefault();
    }
}

PriceType 枚举和 Price 类:

public enum PriceType
{
    Normal = 0,
    Action = 1
}

public class Price
{
    public string Id { get; set; }
    public string Description { get; set; }
    public decimal Amount { get; set; }
    public PriceType Type { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public bool Active { get; set; }
}

我想为GetCurrentPrice 方法创建一个单元测试。基本上我想测试所有可能出现的规则组合,所以我必须创建多篇文章来包含各种价格组合才能获得全面覆盖。

我正在考虑这样的单元测试(伪):

[TestMethod()]
public void GetCurrentPriceTest()
{
    var articles = getTestArticles();
    foreach (var article in articles)
    {
        var price = article.GetCurrentPrice();
        // somehow compare the gotten price to a predefined value
    } 
}
  • 我读过“多个断言是邪恶的”,但我不需要 他们在这里测试所有条件?或者我需要一个单独的单元 按条件测试?

  • 如何为单元测试提供一组测试数据? 我应该模拟存储库吗?如果这些数据还包括 期望值?

【问题讨论】:

    标签: c# unit-testing mocking


    【解决方案1】:

    在此示例中您没有使用存储库,因此无需模拟任何内容。您可以做的是为不同的可能输入创建多个单元测试:

    [TestMethod]
    public void Foo()
    {
        // arrange
        var article = new Article();
        // TODO: go ahead and populate the Prices collection with dummy data
    
    
        // act
        var actual = article.GetCurrentPrice();
    
        // assert
        // TODO: assert on the actual price returned by the method
        // depending on what you put in the arrange phase you know
    }
    

    等等,您可以添加其他单元测试,您只需更改每个可能输入的 arrangeassert 阶段。

    【讨论】:

    • 在这种情况下,unit 是什么?我只想知道GetCurrentPrice 是否正常工作。每个不同的不同输入都是unit,方法的正确性是否取决于所有这些单元测试的通过?还是方法本身就是单位?
    • @diggingforfire,没有unit。但那是因为您的代码的设计方式是没有unit。你的Article 类做了两件事:它具有保存一些数据的属性,它包含一个操作这些数据的方法。所以你不能单独对这两个进行单元测试。如果你想分离这些职责,你可以在一个单独的存储库类中定义 GetCurrentPrice,它将 Article 实例作为参数。
    • 这不会导致领域模型贫乏吗?我更喜欢 Article 本身包含该逻辑。不过,这似乎使单元测试更加困难..
    • @diggingforfire,是的,它可能会导致领域模型贫乏。您正在使用很好的 DDD 方法。但是为了在这种情况下对这个文章类进行单元测试,您应该在每个单元测试中准备它的不同实例,以涵盖您尝试测试的方法的不同可能输出。
    • 这是有道理的(就像 Ivan 说的那样)。我将如何为每个测试提供数据?在这种情况下,我是否会将其硬核到单元测试中?
    【解决方案2】:

    您不需要多个断言。您需要多个测试,每个测试只有一个断言。

    【讨论】:

    • 我不同意这里。我认为只要您期望相同的结果,您就可以组合测试数据。当我可以在迭代模式中的单个测试中实现相同的事情时,我没有理由编写 100 个单元测试。这是假设每个的测试结果应该是相同的。这里的一个例子是一个键值对字典,它被用作传递给方法的每个键的测试数据——方法返回值应该是键值对的值。我看不出这为什么无效。
    • 在一天结束时 - 执行相同的数据评估 - 如果它确实失败 - 我必须弄清楚为什么 - 所以启动调试器并调查测试。相同的结果 - 更容易维护.....
    • @tsells:我首先创建一个单元测试,然后是第二个单元测试,然后我重构。我很可能会达到迭代测试的水平,尽管这样做的缺点是一个测试失败会导致其余测试无法执行。
    • John - 请记住我的第一句话 - 你期待相同类型的结果 - 所以很可能当错误被引入该区域时 - 它会影响多个测试。另请注意 - 我不使用任何类型的 BDD / TDD 来驱动我的代码。我在编写代码时对其进行测试。我不能终生创建一个失败的测试,然后编写代码使其工作。这对我来说效率低下 - 但这是另一个对话。
    【解决方案3】:

    每个启动条件和单个断言的新测试,f.e.

    [Test]
    public void GetCurrentPrice_PricesCollection1_ShouldReturnNormalPrice(){...}
    
    [Test]
    public void GetCurrentPrice_PricesCollection2_ShouldReturnActionPrice(){...}
    

    并且还测试边界

    对于单元测试我使用模式

    MethodName_UsedData_ExpectedResult()
    

    【讨论】:

    • 所以你会对每一个可能的输入和它各自的输出进行测试?这听起来合乎逻辑。似乎需要大量的手动编码才能将其全部设置好。有什么方法可以轻松地为测试提供此类数据?
    • 可以说,我感兴趣的不是测试一切(几乎不可能)。您应该特别关注边界(例如日期相同但价格类型不同的价格)。普通的东西不值得测试很多。例如,我大量使用工厂来创建有效的 PriceCollection,但您必须编写更多代码,但是当您开始重构解决方案时,您会被单元测试所覆盖。
    • Resharper 对你的单元测试模式大喊大叫。只是好奇 - 为什么你需要方法名称 - 你看不出测试中调用了什么方法?
    • 是的,我知道建议,阅读书籍等 - 我只是还没有喝过“纯粹主义”果汁 - 我更像是一个现实主义者。 :)
    • 为什么不在那里添加呢?这对我来说不是一个错误。当您查看测试结果时,什么更有意义,只有 usedData_returnFalse() 或 CreateMethod_usedData_returnFalse()?我知道但关于 resharper 但那是约定而不是错误或错误:-) 我曾经这样做过,我知道这不是最复杂的方式,但对我来说它有效
    【解决方案4】:

    我认为您需要数据驱动测试。在 vsts 中有一个名为 Datasource 的属性,使用它你可以发送一个测试方法多个测试用例。确保您不使用多个断言。这是一个 MSDN 链接http://msdn.microsoft.com/en-us/library/ms182527.aspx

    希望这会对你有所帮助。

    【讨论】:

    • 为什么是数据驱动?这不是测试数据库。
    • 当我们需要测试具有多个输入的方法时,我们会使用数据驱动测试。它可以是任何方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多