Nunit、NCrunch 和 NSubstitute - 总是有 2 个测试因 UnexpectedArgumentMatcherException 而失败,它(准)随机变化
Nunit, NCrunch and NSubstitute - Always 2 tests failing with UnexpectedArgumentMatcherException, which change (quasi) randomly
我有一个使用 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.Is
或 Arg.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);
}
}
有什么(明显的)我遗漏的吗?这一切的准随机性似乎都指向代码没有为每个测试正确重新初始化,但我完全看不出如何。
非常感谢任何帮助。
这些行尝试使用具有非替代 (_auctionService
) 的参数匹配器:
var result = _auctionService.Accept(_ride.Id, Arg.Any<decimal>());
参数匹配器只能用于替换,不能用于通过 new
创建的标准值。
NSubstitute 缓存 Arg.Any<...>
之前测试的调用。先前测试中的错误调用可能会导致出现此神秘错误。
一位很棒的同事向我展示了这个技巧,以找出哪个 测试做出了这个错误的调用。
将以下部分放在 Teardown 方法中将导致无效的 Arg.Any
调用失败。
[TearDown]
public void TearDown()
{
var argSpecs = SubstitutionContext.Current.DequeueAllArgumentSpecifications();
if (argSpecs.Any())
{
throw new UnexpectedArgumentMatcherException();
}
}
我有一个使用 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.Is
或 Arg.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);
}
}
有什么(明显的)我遗漏的吗?这一切的准随机性似乎都指向代码没有为每个测试正确重新初始化,但我完全看不出如何。
非常感谢任何帮助。
这些行尝试使用具有非替代 (_auctionService
) 的参数匹配器:
var result = _auctionService.Accept(_ride.Id, Arg.Any<decimal>());
参数匹配器只能用于替换,不能用于通过 new
创建的标准值。
NSubstitute 缓存 Arg.Any<...>
之前测试的调用。先前测试中的错误调用可能会导致出现此神秘错误。
一位很棒的同事向我展示了这个技巧,以找出哪个 测试做出了这个错误的调用。
将以下部分放在 Teardown 方法中将导致无效的 Arg.Any
调用失败。
[TearDown]
public void TearDown()
{
var argSpecs = SubstitutionContext.Current.DequeueAllArgumentSpecifications();
if (argSpecs.Any())
{
throw new UnexpectedArgumentMatcherException();
}
}