unit testing仅在构建服务器上运行时失败
为了帮助进行unit testing,我们在委托中包装了DateTime
类,以便可以在unit testing中覆盖DateTime.Now
。
public static class SystemTime { #region Static Fields public static Func Now = () => DateTime.Now; #endregion }
以下是在xunitunit testing中使用它的示例:
[Fact] public void it_should_update_the_last_accessed_timestamp_on_an_entry() { // Arrange var service = this.CreateClassUnderTest(); var expectedTimestamp = SystemTime.Now(); SystemTime.Now = () => expectedTimestamp; // Act service.UpdateLastAccessedTimestamp(this._testEntry); // Assert Assert.Equal(expectedTimestamp, this._testEntry.LastAccessedOn); }
测试在本地运行正常,但是在我们的构建服务器上失败,因为Assert
语句中的日期时间不同。
考虑到DateTime
是通过上面提到的委托包装器来模拟的,我正在努力想到它会失败的原因。 我已经validation了UpdateLastAccessedTimestamp
方法的实现没有问题,并且测试在本地运行时通过。
不幸的是我无法在我们的构建服务器上调试它。 任何想法为什么它只会在构建服务器上运行时失败?
请注意, UpdateLastAccessedTimestamp
的实现如下:
public void UpdateLastAccessedTimestamp(Entry entry) { entry.LastAccessedOn = SystemTime.Now(); this._unitOfWork.Entries.Update(entry); this._unitOfWork.Save(); }
Entry
类只是一个简单的POCO类,它有许多字段,包括LastAccessedOn
字段:
public class Entry { public DateTime LastAccessedOn { get; set; } //other fields that have left out to keep the example concise }
您的问题可能是由于使用static SystemTime
多个unit testing。 如果你有例如:
unit testing1
[Fact] public void it_should_update_the_last_accessed_timestamp_on_an_entry() { // Arrange var service = this.CreateClassUnderTest(); var expectedTimestamp = SystemTime.Now(); SystemTime.Now = () => expectedTimestamp; // Act service.UpdateLastAccessedTimestamp(this._testEntry); // Assert Assert.Equal(expectedTimestamp, this._testEntry.LastAccessedOn); } public void UpdateLastAccessedTimestamp(Entry entry) { entry.LastAccessedOn = SystemTime.Now(); this._unitOfWork.Entries.Update(entry); this._unitOfWork.Save(); }
unit testing2
[Fact] public void do_something_different { SystemTime.Now = () => DateTime.Now; }
因此,我们假设unit testing2(它是完整的)在unit testing1中的线之间触发:
SystemTime.Now = () => expectedTimestamp; // Unit test 2 starts execution here // Act service.UpdateLastAccessedTimestamp(this._testEntry);
如果发生这种情况,则UpdateLastAccessedTimestamp
不会(必然)具有您在SystemTime.Now = () => expectedTimestamp;
设置的预期DateTime
值SystemTime.Now = () => expectedTimestamp;
,因为另一项测试已经覆盖了您从unit testing1提供的function。
这就是为什么我认为你最好将DateTime
作为参数传递,或者使用注入日期时间如下:
/// /// Injectable DateTime interface, should be used to ensure date specific logic is more testable /// public interface IDateTime { /// /// Current Data time /// DateTime Now { get; } } /// /// DateTime.Now - use as concrete implementation /// public class SystemDateTime : IDateTime { /// /// DateTime.Now /// public DateTime Now { get { return DateTime.Now; } } } /// /// DateTime - used to unit testing functionality around DateTime.Now (externalizes dependency on DateTime.Now /// public class MockSystemDateTime : IDateTime { private readonly DateTime MockedDateTime; /// /// Take in mocked DateTime for use in testing /// /// public MockSystemDateTime(DateTime mockedDateTime) { this.MockedDateTime = mockedDateTime; } /// /// DateTime passed from constructor /// public DateTime Now { get { return MockedDateTime; } } }
使用此方案,您的服务类可能会改变(类似于):
public class Service { public Service() { } public void UpdateLastAccessedTimestamp(Entry entry) { entry.LastAccessedOn = SystemTime.Now(); this._unitOfWork.Entries.Update(entry); this._unitOfWork.Save(); } }
对此:
public class Service { private readonly IDateTime _iDateTime; public Service(IDateTime iDateTime) { if (iDateTime == null) throw new ArgumentNullException(nameof(iDateTime)); // or you could new up the concrete implementation of SystemDateTime if not provided _iDateTime = iDateTime; } public void UpdateLastAccessedTimestamp(Entry entry) { entry.LastAccessedOn = _iDateTime.Now; this._unitOfWork.Entries.Update(entry); this._unitOfWork.Save(); } }
对于您的Service
的实际实现,您可能是新的(或使用IOC容器):
Service service = new Service(new SystemDateTime());
对于测试,您可以使用模拟框架,也可以使用Mock类:
Service service = new Service(new MockDateTime(new DateTime(2000, 1, 1)));
您的unit testing可能会变成:
[Fact] public void it_should_update_the_last_accessed_timestamp_on_an_entry() { // Arrange MockDateTime mockDateTime = new MockDateTime(new DateTime 2000, 1, 1); var service = this.CreateClassUnderTest(mockDateTime); // Act service.UpdateLastAccessedTimestamp(this._testEntry); // Assert Assert.Equal(mockDateTime.Now, this._testEntry.LastAccessedOn); }
你很幸运它在当地工作。 要使其正常工作,您必须存根服务获取其上次访问数据时间的位置,并检查返回时间。 此时,本地计算机的速度足以在DataTime.Now上同时返回2次,而构建服务器则不然。