【问题标题】:Mocking Regex.IsMatch()模拟 Regex.IsMatch()
【发布时间】:2014-06-26 00:20:57
【问题描述】:

所以,我有自己的 Luhn 算法实现,我正在使用正则表达式来验证用户的输入。

我继续进行单元测试,但我发现自己遇到了这个问题:

Mock<Regex> regexMock = new Mock<Regex>();
regexMock.Setup(r => r.IsMatch(It.IsAny<string>())).Returns(true);

注意:我正在使用 Moq 框架进行模拟

但不知何故,最后一行代码抛出异常

非虚拟(在 VB 中可覆盖)成员的设置无效:r => r.IsMatch(It.IsAny())

我想知道我有什么替代方案来解决我的模拟问题,或者可以采取一些解决方法。

提前致谢!

编辑:

好的,所以我的测试看起来像这样:

Mock<Regex> regexMock = new Mock<Regex>();
regexMock.Setup(r => r.IsMatch(It.IsAny<string>())).Returns(true);

Mock<IRegexBuilder> builderMock = new Mock<IRegexBuilder>();
builderMock.Setup(m => m.Build()).Returns(regexMock.Object);

LuhnAlgorithm luhn = new LuhnAlgorithm(builderMock.Object);

string input = "7992739871";
ushort expected = 3;

object output = luhn.GenerateChecksum(input);

Assert.IsInstanceOfType(output, typeof(ushort));
Assert.AreEqual(expected, (ushort)output);

我有这个IRegexBuilder,这是我为帮助创建正则表达式而制作的另一个类。重要的是,最终的 Regex 对象是通过调用 IRegexBuilder.Build() 方法生成的。

现在,我可以模拟该方法并返回一个固定的正则表达式,例如:

builderMock.Setup(m => m.Build()).Returns(new Regex("\d+"));

但我不想在测试中定义我的验证。

伙计们,我希望我的验证(不管它是如何进行的)不会影响我的测试,我想模拟输入匹配以返回真或假,而与验证的方式无关。如果我在测试方法中创建自己的正则表达式,那么以后每当我更改验证逻辑时,我都必须更改测试。

【问题讨论】:

  • 为什么你要模拟这个?你想测试什么?如果你能展示你的代码的形状和你正在尝试编写的测试,那将会很有帮助。
  • 我同意 Jon 的观点......我想不出模拟正则表达式的场景。
  • 又一次滥用嘲讽。你在测试.NET 框架本身吗?您可以轻松地将两种不同的数据传递到您实际可测试的方法中,并且需要一项测试通过,一项测试失败。像对待盐一样对待嘲笑 - 没有或太多,你吃的饭很糟糕
  • 好的,我用更多细节编辑了我的问题

标签: c# visual-studio oop unit-testing mocking


【解决方案1】:

为什么要模拟一个正则表达式?

虽然正则表达式是 Luhn 实现的内部依赖项,但它不是应该注入的依赖项,因此不应被模拟。

如果执行 Luhn 检查依赖于您的验证代码,以及验证它是否执行 Luhn 检查的内容,您可以有一个接口/抽象类,其中实现可以在内部执行正则表达式。

有可能

interface ICardValidator
{
    bool IsCardValid(string cardNumber);
}

class LuhnCardValidator : ICardValidator
{
    private static readonly Regex _cardRegex = new Regex(...);

    bool IsCardValid(string cardNumber)
    {
        return Regex.IsMatch(cardNumber);
    }
}

您可以针对 LuhnCardValidator 编写单元测试来验证您的 Luhn 检查是否有效。

[Test]
[TestCase("4242424242424242")
public void ShouldBeValid(string cardNumber)
{
    Assert.IsTrue(new LuhnCardValidator().IsCardValid(cardNumber));
}

您还可以针对依赖于ICardValidator 的代码编写测试,例如当验证失败时,它会向用户显示相应的错误消息。

[Test]
public void ShouldPresentCardFailedMessage()
{
    var mockCardValidator = new Mock<ICardValidator>();
    mockCardValidator.Setup(x => x.IsCardValid(It.IsAny<string>()).Returns(false);

    var validationSummary = new ValidationSummary(mockCardValidator.Object);

    validationSummary.ValidateThePage(...);

    var errors = validationSummary.GetErrors();

    Assert.IsTrue(errors.Any(x => x.Message == "Credit card number is not valid"));
}

【讨论】:

  • 我喜欢 Validation Encapsulation 替代方案,但没有创建新接口和类的最简单方法吗?
  • 我决定创建一个接口,因为它将充当您的依赖项并让您进行依赖项注入。做一个简单的 luhn 检查可能有点过头了,但我发现这是设置单元测试上下文的最简单方法。
【解决方案2】:

如果正则表达式表示一个复杂的算法,您可以将匹配封装在一个自定义类中,该类具有接口和依赖注入(或模拟)。

如果是基本验证,我根本不会嘲笑它。你会嘲笑String.Contains()吗?

【讨论】:

  • 匹配的封装可能是一个选项。我不想要的是在测试中创建正则表达式。查看编辑后的答案
  • 我会选择@Matthew的回答;他举了一个很好的例子。不要害怕创建新的接口和类——这就是面向对象编程的全部内容! :)
【解决方案3】:

当您使用 Moq 创建模拟时,它会创建实现您正在模拟的接口的类,或从您正在模拟的类继承的类。这个生成的类应该提供你正在模拟的成员的自己的实现。接口很简单,因为没有实现。类成员应该是抽象的或虚拟的。 Regex.IsMatch 不是抽象的,也不是虚拟的。所以 Moq 不能创建自己的这个成员的实现来进行设置。

您应该使用起订量可以处理的东西。通常是一些具有虚拟方法或实现某些接口的包装类。此类应将工作委托给Regex 类:

public interface IWrapper // use appropriate name of interface
{
    bool IsValid(string value)
}

这个界面可以很容易地被模拟。您现在可以为使用此接口的客户端编写测试。在现实生活中,您需要将工作委托给Regex

public class Wrapper : IWrapper
{
   public bool IsValid(string value)
   {
       // use Regex here
   }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-04
    • 2020-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-12
    • 2014-04-22
    相关资源
    最近更新 更多