C# 当参数不';与给定的模式不匹配?

C# 当参数不';与给定的模式不匹配?,c#,unit-testing,mocking,nunit,nsubstitute,C#,Unit Testing,Mocking,Nunit,Nsubstitute,我负责测试我的团队正在维护的用C和C语言开发的遗留软件。最初的团队使用NSubstitute 3.1为代表创建测试加倍,以便对C#部分的API执行单元测试。这里有一个这样的双重测试,忽略了不相关的细节: private static byte[] MockSelectByAidWithoutData(ushort retVal) { var expectedIn= "FFFEFDFCFB".HexToBytes(); var expectedOut= &quo

我负责测试我的团队正在维护的用C和C语言开发的遗留软件。最初的团队使用NSubstitute 3.1为代表创建测试加倍,以便对C#部分的API执行单元测试。这里有一个这样的双重测试,忽略了不相关的细节:

private static byte[] MockSelectByAidWithoutData(ushort retVal)
{
    var expectedIn= "FFFEFDFCFB".HexToBytes();
    var expectedOut= "010203040506070809".HexToBytes();

    var fake = Substitute.For<SomeDelegate>();
    fake(Arg.Is<byte[]>(x => expectedIn.SequenceEqual(x.Take(expectedIn.Length))),
            Arg.Is(0x00),
            Arg.Is(expectedIn.Length),
            Arg.Any<int>(),
            Arg.Any<int>(),
            out int outputLength)
        .Returns(x =>
            {
                expectedOut.CopyTo((Array)x[0], 0);
                x[5] = expectedOut.Length;
                return retVal;
            }
        );
    Mediator.GetInstance().Delegate = fake;
    return expectedOut;
}
private static byte[]mock selectbyaid without data(ushort retVal)
{
var expectedIn=“FFFEFDFCFB”.HexToBytes();
var expectedOut=“010203040506070809”。HexToBytes();
var fake=替换为();
假(参数为(x=>expectedIn.SequenceEqual(x.Take(expectedIn.Length))),
参数为(0x00),
参数为(预计长度),
Arg.Any(),
Arg.Any(),
out int outputLength(输出长度)
.Returns(x=>
{
expectedOut.CopyTo((数组)x[0],0);
x[5]=预期的输出长度;
返回返回;
}
);
Mediator.GetInstance().Delegate=false;
预期回报率;
}
现在,如果使用与
fake()
调用中指定的参数匹配的参数调用伪委托,它将返回
retVal
值,并且每个人都很高兴。但是,如果某个值不匹配,它将返回零。由于零是一个有效但不正确的值,执行将继续,并且我得到一个错误,该错误不是我正在测试的问题的根本原因(即,当问题实际上是错误输入时,输出错误)

我正在寻找一种方法:

  • 为与期望值不匹配的值指定“全面”行为,或
  • 如果参数与预期不匹配,则获取异常
这样,当接收到错误的输入并带有有意义的消息时,测试用例将立即失败,而不会触发进一步的行为,从而污染测试结果

提前感谢,

德克

另外,如果真的有必要的话,我可能可以安全地切换到更新版本的NSubstitute

为与期望值不匹配的值指定“全面”行为

我想我已经找到了一个方法,你可以做到这一点。如果您首先为所有参数存根“catch all”/failure案例,那么您可以存根更具体的调用。NSubstitute将尝试匹配提供的最新规范,返回到以前的存根值

这是一个样本

注意,它使用的是NSubstitute 4.x中引入的from
NSubstitute.Extensions
名称空间。这并不是绝对必要的,因为如果您使用参数匹配器,NSubstitute将自动假定您正在配置调用,但在这样配置重叠调用时,这是一种很好的模式

使用NSubstitute;
使用NSubstitute.Extensions;//Configure()所需的
公共类事物{
公共字符串Id{get;set;}
}
公共接口很简单{
int示例(对象a、字符串b);
}
公共类UnexpectedCallException:异常{}
[事实]
public void SuboneCallButFailOthers()示例{
var sub=替换为();
//一网打尽:
sub.Example(null,null).ReturnsForAnyArgs(x=>throw new UnexpectedCallException());
//我们使用Configure from NSubstitute.Extensions来
//能够在不获得意外CallException的情况下对此进行存根。
//这里没有严格必要,因为我们使用的是参数匹配器,所以NSub
//已经知道我们正在配置呼叫,但这是一个好习惯。
//见:https://nsubstitute.github.io/help/configure/
sub.Configure()
.Example(Arg.Is(x=>x.Id==“abc”),Arg.Any()
.返回(x=>42);
//不匹配呼叫的示例:
Assert.Throws(()=>
sub.Example(新事物{Id=“def”},“hi”)
);
//匹配呼叫的示例:
Assert.Equal(42,sub.Example(newthing{Id=“abc”},“hello”);
}
您可以对此进行扩展,以包括有关不匹配参数的信息,但这将是一项定制工作。如果您查看NSubstitute的一些参数格式代码,这些代码可能可以重复使用,以帮助实现这一点


更新以包含代理示例

我只是用一个代理来运行它,它还传递:

public delegate int SomeDelegate(Thing a, string b);

[Fact]
public void ExampleOfStubOneDelegateCallButFailOthers() {
    var sub = Substitute.For<SomeDelegate>();
    sub(null, null).ReturnsForAnyArgs(x => throw new UnexpectedCallException());
    sub.Configure()
        .Invoke(Arg.Is<Thing>(x => x.Id == "abc"), Arg.Any<string>())
        .Returns(x => 42);
    Assert.Throws<UnexpectedCallException>(() => sub(new Thing { Id = "def" }, "hi"));
    Assert.Equal(42, sub(new Thing { Id = "abc" }, "hello"));
}
public delegate intsomedelegate(事物a,字符串b);
[事实]
public void SubboneDelegateCallButFailOthers()的示例{
var sub=替换为();
sub(null,null).ReturnsForAnyArgs(x=>throw new UnexpectedCallException());
sub.Configure()
.Invoke(Arg.Is(x=>x.Id==“abc”),Arg.Any()
.返回(x=>42);
抛出(()=>sub(newthing{Id=“def”},“hi”);
Assert.Equal(42,sub(newthing{Id=“abc”},“hello”);
}

我认为您需要使用
Received
来断言调用是使用特定参数进行的。如果这将从
返回
存根复制逻辑,您可以使用
ReturnsForAnyArgs
潜在地切换它,并且只匹配
接收的
断言中的参数。这与我使用的解决方法类似,我只接受对fake()的调用中的任何参数然后使用AndDoes在回调中手动执行所有断言。(由于retval=0触发了一个异常,所以接收到的部分永远不会被执行。)但是我并不真正喜欢这种解决方法:我正在手动执行框架应该为我做的事情。似乎我只是使用了错误的工具来完成这项工作。谢谢David,这正是我所说的,但是它似乎对委托不起作用,或者甚至不应该起作用,因为Configure()方法只存在于接口/类级别。@DekDekku我用一个委托示例更新了答案,该示例似乎工作正常。您能描述一下您在学员示例中看到的错误/行为吗?再次感谢。正如我所说,Configure()方法在伪对象上不可用。我会再试一次,因为我是从v3.1和perhap更新的
public delegate int SomeDelegate(Thing a, string b);

[Fact]
public void ExampleOfStubOneDelegateCallButFailOthers() {
    var sub = Substitute.For<SomeDelegate>();
    sub(null, null).ReturnsForAnyArgs(x => throw new UnexpectedCallException());
    sub.Configure()
        .Invoke(Arg.Is<Thing>(x => x.Id == "abc"), Arg.Any<string>())
        .Returns(x => 42);
    Assert.Throws<UnexpectedCallException>(() => sub(new Thing { Id = "def" }, "hi"));
    Assert.Equal(42, sub(new Thing { Id = "abc" }, "hello"));
}