设计可测试的功能代码

时间:2013-06-16 17:00:17

标签: c# f# functional-programming functional-testing c#-to-f#

我喜欢编写纯函数的想法,但我无法理解将它们组合在一起的方法,从而导致可测试的代码。我习惯于提取类,然后适当地进行存根,并且觉得我缺少对函数式编程的一些关键洞察力。

这是一个我从目前面临的问题中瘦下来的例子。

我想列出一份日期清单,并根据“机会”标准对其进行过滤。

在C#中它看起来像:

    static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
    {
        return dates.Where(ds => HasOpportunity(ds)).ToList();
    }

    static bool HasOpportunity(List<DateTime> dates)
    {
        var threshold = 0.05D;

        var current = OpportunityProbability(dates, DateTime.Now);
        var previous = OpportunityProbability(dates, DateTime.Now.Subtract(TimeSpan.FromDays(30)));

        return previous >= threshold && current < threshold;
    }

    static double OpportunityProbability(List<DateTime> dates, DateTime endDate)
    {
        // does lots of math
        return 0.0D;
    }

所以在提示中我们知道如何测试OpportunityProbability。我遇到的问题是在HasOpportunity中,并在链(Opportunities)的上方。{/ p>

我知道如何测试HasOpportunity的唯一方法是删除OpportunityProbability,但我不能这样做。我不想创建虚假数据来满足OpportunityProbability的设计,以便测试HasOpportunity。所以即使这两个函数都是纯粹的,它也是不可测试的,我觉得它的设计很糟糕。

因此我觉得我正在设计糟糕的功能代码:)

我对HasOpportunity的关注主要是布尔测试。给定两个双精度和阈值进行比较并返回结果。为了获得这两个双打,它使用一个需要日期和日期列表的功能。这导致HasOpportunity也负责确定日期(DateTime.Now和之前30天)。也许我可以解决这个问题:

    static bool HasOpportunity(double probability1, double probability2)
    {
        var threshold = 0.05D;

        return probability2 >= threshold && probability1 < threshold;
    }

所以这显然是可以测试的。我甚至可以提高门槛:

    static bool HasOpportunity(double threshold, double probability1, double probability2)
    {
        return probability2 >= threshold && probability1 < threshold;
    }

所以这更通用。

我这样做时遇到的问题是我刚刚将事情提升到Opportunities

    static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
    {
        return dates.Where(ds => {
            var current = OpportunityProbability(ds, DateTime.Now);
            var previous = OpportunityProbability(ds, DateTime.Now.Subtract(TimeSpan.FromDays(30)));
            return HasOpportunity(0.05D, current, previous);
        }).ToList();
    }

这是我不知道下一步要做的地方。

任何想法哦功能性霸主?请帮我在C#写F#,提前谢谢!

更新

所以再迈出一步,我可以得到:

    static List<List<DateTime>> Opportunities(double threshold, DateTime currentDate, DateTime previousDate, List<List<DateTime>> dates)
    {
        return dates.Where(ds => {
            var current = OpportunityProbability(ds, currentDate);
            var previous = OpportunityProbability(ds, previousDate);
            return HasOpportunity(threshold, current, previous);
        }).ToList();
    }

所以我仍然不知道如何测试它,但很好的是,这个函数的参数最终定义了机会是什么:

  • 阈值
  • 第一次约会
  • 第二次约会

然后给出一份日期列表,它可以为您提供机会。

2 个答案:

答案 0 :(得分:2)

您是否考虑过使用高阶函数?将OpportunityProbability函数传递给HasOpportunity。

static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
{
    return dates.Where(ds => HasOpportunity(ds, OpportunityProbability, OpportunityProbability)).ToList();
}

static bool HasOpportunity(List<DateTime> dates, Func<List<DateTime>, DateTime, double> currentProb, Func<List<DateTime>, DateTime, double> prevProb)
{
    var threshold = 0.05D;

    var current = currentProb(dates, DateTime.Now);
    var previous = prevProb(dates, DateTime.Now.Subtract(TimeSpan.FromDays(30)));

    return previous >= threshold && current < threshold;
}

static double OpportunityProbability(List<DateTime> dates, DateTime endDate)
{
    // does lots of math
    return 0.0D;
}

现在,您可以相互独立地测试OpportunityProbability和HasOpportunity(在HasOpportunity的情况下,您可以“存根”第二个和最后一个参数。如果您想要更多分离,您也可以将OpportunityProbability传递给商机。

答案 1 :(得分:0)

我认为你应该投入一些古老的面向对象,并尊重单一责任模式。一种可能的方法是创建类:

    使用方法OpportunityCalculator

  • double OpportunityProbability(List<DateTime> dates, DateTime endDate) 使用方法OpportunityFilter

  • bool HasOpportunity(double threshold, double probability1, double probability2)

这些类可以独立测试:

  • OpportunityCalculator抽象出复杂的数学。
  • 在测试OpportunityFilter时,您可以删除OpportunityCalculator。您的测试将围绕这样一个事实,即计算器将使用正确的参数进行两次咨询。