【问题标题】:NSubstitute - TestFixture 1 causes AmbiguousArgumentsException in TestFixture 2NSubstitute - TestFixture 1 在 TestFixture 2 中导致 AmbiguousArgumentsException
【发布时间】:2015-01-04 11:47:30
【问题描述】:

我正在使用 NUnit 和 NSubstitute 编写 C# 单元测试。我正在测试一个类,它将尝试从实现以下接口的配置提供程序中检索对象:

public interface IConfigProvider<T> {
    T GetConfig(int id);
    T GetConfig(string id);
}

正在测试的类仅使用 GetConfig 的 int 版本,因此在 SetUpFixture 中我执行以下操作来设置一个始终返回相同虚拟对象的模拟配置提供程序:

IConfigProvider<ConfigType> configProvider = Substitute.For<IConfigProvider<ConfigType>>();
configProvider.GetConfig(Arg.Any<int>()).Returns<ConfigType>(new ConfigType(/* args */);

如果 TestFixture 是唯一正在运行的,那么它运行得非常好。但是,在同一个程序集中的不同 TestFixture 中,我会像这样检查收到的调用:

connection.Received(1).SetCallbacks(Arg.Any<Action<Message>>(), Arg.Any<Action<long>>(), Arg.Any<Action<long, Exception>>());

如果这些 Received 测试在配置提供程序测试之前运行,则配置测试在 SetUpFixture 中失败并出现 AmbiguousArgumentsException:

Here.Be.Namespace.ProfileManagerTests+Setup (TestFixtureSetUp):
SetUp : NSubstitute.Exceptions.AmbiguousArgumentsException : Cannot determine argument specifications to use.
Please use specifications for all arguments of the same type.
at NSubstitute.Core.Arguments.NonParamsArgumentSpecificationFactory.Create(Object argument, IParameterInfo parameterInfo, ISuppliedArgumentSpecifications suppliedArgumentSpecifications)
at System.Linq.Enumerable.<SelectIterator>d__7`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at NSubstitute.Core.Arguments.MixedArgumentSpecificationsFactory.Create(IList`1 argumentSpecs, Object[] arguments, IParameterInfo[] parameterInfos)
at NSubstitute.Core.Arguments.ArgumentSpecificationsFactory.Create(IList`1 argumentSpecs, Object[] arguments, IParameterInfo[] parameterInfos, MatchArgs matchArgs)
at NSubstitute.Core.CallSpecificationFactory.CreateFrom(ICall call, MatchArgs matchArgs)
at NSubstitute.Routing.Handlers.RecordCallSpecificationHandler.Handle(ICall call)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at NSubstitute.Routing.Route.Handle(ICall call)
at NSubstitute.Proxies.CastleDynamicProxy.CastleForwardingInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.IConfigProvider`1Proxy.GetConfig(Int32 id)
at Here.Be.Namespace.ProfileManagerTests.Setup.DoSetup()

真正让我困惑的是,即使在测试运行之间我也能观察到这种效果——如果我使用 NUnit GUI 单独运行 Received 测试,然后单独运行配置测试,配置测试将失败。如果我立即再次运行配置测试,它们就会通过。

我尝试过的事情:

  • 也添加configProvider.GetConfig(Arg.Any&lt;string&gt;()).Returns...,以防超载是问题。
  • 我已经阅读了NSubstitute docs on argument matching,但在那里我找不到解决方案。如果必须为方法的 int 和 string 版本提供参数匹配器,我不知道该怎么做。

碰巧的是,我正在使用的测试只会调用值为 0 或 1 的 GetConfig 方法,因此我可以只为这两个值提供 Returns 规范而不使用匹配,但是我想了解如何更普遍地解决此问题。

【问题讨论】:

  • 您是否在new ConfigType(/* args */) 代码中使用了任何参数匹配器?
  • 不,我有一个枚举实例、一个字符串和一个空 List - 只是占位符参数来创建一个足够好的对象供接收者接受。

标签: c# nunit nsubstitute


【解决方案1】:

模棱两可的参数是当 NSubstitute 将参数与其当前正在使用的调用进行比较时,与它拥有的“参数匹配器”堆栈进行比较(每次调用 Arg.Blah 时,都会将参数匹配器添加到该堆栈中),并且无法解决哪个论点在哪里。

这通常是由像 blah(null, null) 这样的调用引起的,其中单个参数匹配器排队,但也可能是由于在调用配置之外使用 arg 匹配器而导致堆栈不同步,或作为非虚拟方法的参数。

1.8.0 版(在您提出问题后发布)对后一种情况的检测略有改进,因此值得一试。

除此之外,我曾多次遇到此问题并使用了以下(痛苦的)方法。

  • 单独运行测试并确保它通过
  • 找出立即运行的测试(通常可以猜测,但测试日志在这里可以提供帮助),然后只运行这两个测试。确认失败。
  • 查找对Arg.xyz 的任何调用,这些调用可以在任一测试中将参数匹配器排队。确保它被用作呼叫配置的一部分。有时可以通过注释掉行或用其他值替换 arg 匹配器来确定哪个调用有问题。
  • 确保没有调用会混淆 NSubstitute 的非虚拟方法。

有时问题可能是由于之前的固定装置造成的,因此您可能需要锻炼之前的固定装置并在那里进行探索。 :(

【讨论】:

  • "确保没有调用会混淆 NSubstitute 的非虚拟方法。"这就是问题所在,我创建了各种Connection 虚拟方法,但SetCallbacks 不是其中之一。现在是,第二个夹具现在工作正常:)
【解决方案2】:

had similar errors 是在我将 Microsoft 测试运行程序切换到 VSTest.Console 时开始的(在 MSTest.exe 下运行时不会发生这种情况)。

正如David's answer 中所建议的,错误是由使用Arg.* 参数调用非替代方法引起的。 Arg.Any 被传递给实际的代码方法,在没有 ReturnsReceived 相关方法的情况下调用。

为了扫描我的测试库中的此类问题,我使用正则表达式搜索来查找带有Arg. 但不是Arg. 后跟Returns 或前面是Received 的行

(?=^.*Arg.*$)(?=^((?!Arg.*\.Returns).)*$)^((?!\.Received\(.*Arg.).)*$

它不是防弹过滤器(例如,它不排除多行语句),但它有助于减少调用检查的次数。

【讨论】:

  • 谢谢迈克尔,这有帮助。我做的一个小改进是在第一个 Arg 之后添加一个句点,例如 (?=^.*Arg\..*$)(?=^((?!Arg.*\.Returns).)*$)^((?!\.Received\(.*Arg.).)*$。我还指示 Visual Studio 的查找工具“匹配大小写”。
【解决方案3】:

更改我的测试顺序有效。不是一个很好的答案,但有效 - 请尝试!

【讨论】:

    【解决方案4】:

    这是我最终使用的正则表达式(在我的情况下,在 Rider 中,但它是标准的 .Net 正则表达式)。它可以很好地处理多行语句。我确实出现了一些附加了 Returns() 调用的误报,但它不到总使用次数的 1%。

    它不处理 When()/Do()WithAnyArgs 变体,但在我的特定代码库中,这些是迄今为止少数用途。

    ^(?!\s*\.)[\w\s_\.]+\.(?!DidNotReceive\(\)\s*)(?!Received\(\d*\)\s*)[\w_]+\((((Arg.Any&lt;.*?&gt;\(\))|(Arg.Is(&lt;.*?&gt;)?\(.*\)))[,\s]*)+\)(?!\s*\.Return)(?!\s*?\.Throw)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多