c# - 正则表达式、捕获组和单元测试的良好设计

标签 c# regex unit-testing moq

在一个项目中,我正在尝试使用正则表达式来区分各种类型的句子,并将它们映射到函数来处理这些句子。

这些句子处理函数中的大多数都从句子本身获取参数,由正则表达式中的捕获组解析出来。

例如:“我为 2 个 cookies 支付了 20 美元”与我的解析树(字典)中的一个正则表达式相匹配。正则表达式将匹配提取 $20 作为组“价格”,并将 2 作为组“金额”。目前我正在映射到正确的 Handler 函数并按如下方式调用它:

foreach(KeyValuePair<Regex, Type> pair in sentenceTypes)
{
    Match match = pair.Key.Match(text);
    if(match.Success)
    {
        IHandler handler = handlerFactory.CreateHandler(pair.Value);
        output = handler.Handle(match);
    }
}

简单处理程序类的示例。

public class NoteCookiePriceHandler
    {
        public string Handle(Match match)
        {
            double payment = Convert.ToDouble(match.Result("${payment}"));
            int amount = Convert.ToInt32(match.Result("${amount}"));

            double price = payment / amount;
            return "The price is $" + price;
        }
    }

当我意识到我实际上无法模拟 Match 对象或 Regex 时,我正尝试使用 Moq 设置一些单元测试来提供帮助。仔细想想,总的来说,设计似乎有些缺陷,因为我依赖于正确解析命名组并在没有良好接口(interface)的情况下传递给 Handler 类。

我正在寻找有关更有效设计的建议,以用于将参数正确传递给映射的处理程序函数/类,因为传递 Match 对象似乎有问题。

如果做不到这一点,我们将不胜感激任何帮助找出有效地模拟 Regex 或 Match 的方法,并且至少帮助我解决我的短期问题。它们都缺少默认构造函数,因此我很难让 Moq 为它们创建对象。

编辑:我最终通过为我的匹配组传递一个字符串字典而不是(无法起订量的)匹配对象本身,至少解决了模拟问题。我对这个解决方案不是特别满意,所以建议仍然会受到赞赏。

foreach(KeyValuePair<Regex, Type> pair in sentenceTypes)
        {
            match = pair.Key.Match(text);
            if(match.Success)
            {
                IHandler handler= handlerFactory.CreateHandler(pair.Value);
                foreach (string groupName in pair.Key.GetGroupNames())
                {
                    matchGroups.Add(groupName, match.Groups[groupName].Value);
                }
                interpretation = handler.Handle(matchGroups);

最佳答案

避免不良设计的一种方法是从良好设计的原则入手,而不是简单地从您希望解决的问题入手。这就是测试驱动开发在改变代码质量方面如此强大的原因之一。这种思维方式在 TDD 之前就已经存在了,尽管它的名称是:按契约(Contract)设计。请允许我演示:

您希望理想的处理程序是什么样子的?这个怎么样:

interface IHandler {
    String handle();
}

实现:

public class NoteCookiePriceHandler : IHandler
{  
    private double payment;
    private int amount;

    public NoteCookiePriceHandler(double payment, int amount) {
        this.payment = payment;
        this.amount = amount;
    }

    public String handle() {
        return "The price is $" + payment / amount;
    }
}

现在从这个理想的设计开始,也许从这个设计的测试开始。我们如何获得要发送给处理程序的句子的句子输入?好吧,计算机科学中的所有问题都可以通过另一层间接来解决。假设句子解析器不直接创建处理程序,而是使用工厂创建处理程序:

interface HandlerFactory<T> where T: IHandler  {
    T getHandler(KeyValuePair<String, String> captureGroups);
}

然后您可以为每个处理程序创建一个工厂,但很快您就会找到一种创建通用工厂的方法。例如,使用反射,您可以将捕获组名称与构造函数参数匹配。根据构造函数参数的数据类型,您可以自动让通用处理程序工厂将字符串转换为正确的数据类型。通过创建一些假的处理程序并要求工厂使用一些键值对字符串输入来填充它们,这一切都可以轻松测试。

关于c# - 正则表达式、捕获组和单元测试的良好设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16683282/

相关文章:

javascript - 需要识别正则表达式中的不匹配字符

c# - 正则表达式匹配 C# 中的整个字符串?

unit-testing - 如何在 AngularJS 中测试广播事件

c# - Visual Studio Code - 单击类/方法名称 -> 显示定义

c# - 如何在 C# 中执行 System.Web.Script.Serialization?

c# - 使用 bool 参数从 C# 调用 Catalog.set_execution_parameter_value

java - 如何在java中检查匹配模式

Silverlight 单元测试框架重新运行测试

unit-testing - 如何模拟 executorService.submit()

c# - .Net (C#) 中 ISNULL() 的等效方法是什么?