【问题标题】:Nunit, NCrunch and NSubstitute - Always 2 tests failing with UnexpectedArgumentMatcherException, which change (quasi) randomlyNunit、NCrunch 和 NSubstitute - 总是有 2 个测试因 UnexpectedArgumentMatcherException 失败,随机变化(准)
【发布时间】:2016-07-09 15:00:52
【问题描述】:

我有一个使用 NUnit 3.4.1、NSubstitute 1.10.0 和 NCrunch 2.23.0.2 的文本夹具

此夹具中的任何时间点都有 2 个失败的测试。每次我更改某些内容时,哪个测试失败似乎都会有所不同。并非所有测试最终都会失败,但大多数都会失败,而且问题始终是 NSubstitute 异常,如下所示:

// _clock is initialized as _clock = Substitute.For<IClock>();
// the dates in the Returns statement change on every test
_clock.Now.Returns(new DateTime(2015, 1, 1));

我将 NCrunch 添加到此组合中,因为 Resharper 2016 测试运行器似乎通过了所有测试。大部分。

我总是得到的例外是:

NSubstitute.Exceptions.UnexpectedArgumentMatcherException : Argument matchers 
    (Arg.Is, Arg.Any) should only be used in place of member arguments. 
Do not use in a Returns() statement or anywhere else outside of a member call.

这很清楚,除了在大多数测试中我不使用任何Arg.IsArg.Any

IClock接口

这是IClock 界面,尽显其荣耀。 Now 是一个 getter-only 属性,但这对 NSubstitute 来说应该不是问题,不是吗?

public interface IClock
{
    DateTime Now { get; }
}

整场比赛,即将到来

很抱歉出现大量代码,但我不想假设这是由于一个测试或另一个测试,所以这里是:

[TestFixture]
public class AuctionTests : TestBase
{
    #region Fields

    AuctionService _auctionService;
    IClock _clock;
    Ride _ride;
    IMailService _mailer;

    #endregion

    [SetUp]
    public void Init()
    {
        _clock = Substitute.For<IClock>();
        _mailer = Substitute.For<IMailService>();

        _ride = new Ride
        {
            StartAuction = new DateTime(2016, 2, 12, 19, 0, 23),
            PriceForCustomer = 20m,
            InitialAuctionPrice = 15m,
            HighestAuctionPrice = 19m
        };

        SetupData(_ride);

        _auctionService = new AuctionService(RavenSession, _clock, _mailer);
    }

    [Test]
    public void Auction_rejects_price_when_price_is_higher_then_HighestAuctionPrice()
    {
        const decimal price = 90m;
        _clock.Now.Returns(new DateTime(2015, 1, 1));

        var result = _auctionService.Accept(_ride.Id, price);

        result.Should().BeFalse();
    }

    [Test]
    public void Auction_rejects_price_when_inactive()
    {
        _clock.Now.Returns(new DateTime(2016, 2, 12, 20, 1, 23));

        var result = _auctionService.Accept(_ride.Id, Arg.Any<decimal>());

        result.Should().BeFalse();
    }

    [Test]
    public void Auction_is_inactive_when_current_time_is_before_auction_startDate()
    {
        _clock.Now.Returns(new DateTime(2016, 2, 12, 18, 0, 23));

        var result = _auctionService.Accept(_ride.Id, Arg.Any<decimal>());

        Assert.IsFalse(result);
    }

    [Test]
    public void Auction_is_active_when_current_time_is_exactly_auction_startDate()
    {
        _clock.Now.Returns(_ride.StartAuction);

        var result = _auctionService.Accept(_ride.Id, _ride.InitialAuctionPrice);

        result.Should().BeTrue();
    }

    [Test]
    public void Action_price_is_valid_at_auction_start_time_if_equal_to_initial_price()
    {
        var price = _ride.InitialAuctionPrice;

        _clock.Now.Returns(_ride.StartAuction);

        var result = _auctionService.Accept(_ride.Id, price);

        result.Should().BeTrue();
    }

    [Test]
    public void Action_price_is_valid_at_auction_end_time_if_equal_to_highest_possible_price()
    {
        var price = _ride.HighestAuctionPrice;

        _clock.Now.Returns(_ride.StartAuction.AddMinutes(60));

        var result = _auctionService.Accept(_ride.Id, price);

        result.Should().BeTrue();
    }

    [Test]
    public void Action_price_is_invalid_if_not_within_time_parameters()
    {
        var price = 12m;

        _clock.Now.Returns(_ride.StartAuction.AddMinutes(30));

        var result = _auctionService.Accept(_ride.Id, price);

        result.Should().BeFalse();
    }

    [Test]
    [Ignore("Uitzoeken hoeveel seconde vertraging wenselijk is")]
    public void Accept_takes_delay_in_requests_into_account()
    {
        var price = 17m;

        _clock.Now.Returns(_ride.StartAuction.AddMinutes(30).AddSeconds(30));

        var result = _auctionService.Accept(_ride.Id, price);

        result.Should().BeTrue();
    }

    [Test]
    public void Ride_is_saved_with_accepted_price()
    {
        var price = 17m;

        var date = _ride.StartAuction.AddMinutes(30);
        _clock.Now.Returns(date);

        var result = _auctionService.Accept(_ride.Id, price);

        Assert.IsTrue(result);

        var dbRide = RavenSession.Load<Ride>(_ride.Id);

        price.IsSameOrEqualTo(dbRide.AcceptedAuctionPrice);
    }

    [Test]
    public void On_Start_Auction_InitialAuctionPrice_should_be_fifteen_percent_of_PriceForCustomer()
    {
        _ride.PriceForCustomer = 100;

        _auctionService.StartAuction(_ride.Id);

        Assert.AreEqual(85m, _ride.InitialAuctionPrice);
    }

    [Test]
    public void On_Start_Auction_send_email_to_priorityPartners()
    {
        var priorityPartner = new Partner { Priority = true, Email = "some@email.com" };

        SetupData(priorityPartner, new Partner { Priority = false });

        _auctionService.StartAuction(_ride.Id);

        _mailer.Received(1).SendAuctionEmail(Arg.Any<string>(), _ride);
    }
}

我有什么(明显的)遗漏吗?这一切的准随机性似乎表明代码没有为每次测试正确重新初始化,但我完全看不出如何。

非常感谢任何帮助。

【问题讨论】:

  • 我也有同样的问题。您找到了解决方案吗?
  • 嗯,是的......下面接受的答案。

标签: c# nsubstitute nunit-3.0 ncrunch


【解决方案1】:

NSubstitute 缓存来自先前测试的 Arg.Any&lt;...&gt; 调用。先前测试中的错误调用可能会导致出现此神秘错误。

一位了不起的同事向我展示了这个技巧,以找出哪个测试做出了这个错误的调用。

将以下部分放在 Teardown 方法中会导致无效的Arg.Any 调用失败。

    [TearDown]
    public void TearDown()
    {

        var argSpecs = SubstitutionContext.Current.DequeueAllArgumentSpecifications();
        if (argSpecs.Any())
        {
            throw new UnexpectedArgumentMatcherException();
        }
    }

【讨论】:

    【解决方案2】:

    这些行尝试使用带有非替代 (_auctionService) 的参数匹配器:

    var result = _auctionService.Accept(_ride.Id, Arg.Any<decimal>());
    

    参数匹配器只能用于替换,不能用于通过new 创建的标准值。

    【讨论】:

    • 嗯.. 我刚从 Moq 搬到 NSubstitute,而 Moq 可以做到这一点。有趣的是,这不是失败的测试之一,所以一直让我失望。在发布问题之前,我什至尝试过不在那里使用Arg.Any,但显然奇怪的行为仍然存在,直到我将0m 传递给我错误地使用Arg.Any 的两个测试。你知道不可预知的行为是从哪里来的吗?为什么不同的测试每次都失败?为什么 Resharper 测试运行器不会失败?
    • @SergiPapaseit,是的,这是 NSub 语法的一个已知权衡。有一点explanation here,标题为“NSubstitute 权衡”和“NSubstitute 状态问题示例”。如果您愿意,很高兴能详细介绍。
    • 那篇博文提供了一个绝妙的解释;非常感谢。
    猜你喜欢
    • 2012-05-24
    • 1970-01-01
    • 1970-01-01
    • 2012-06-10
    • 1970-01-01
    • 2020-03-13
    • 2016-01-31
    • 2021-06-03
    • 1970-01-01
    相关资源
    最近更新 更多