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;设置的预期DateTimeSystemTime.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次,而构建服务器则不然。