2016-03-24 4 views
3

Чтобы помочь модульному тестированию, мы завершили класс DateTime делегатом, чтобы DateTime.Now можно было переопределить в модульном тесте.Ошибка модуля при запуске только на сервере сборки

public static class SystemTime 
{ 
    #region Static Fields 

    public static Func<DateTime> Now =() => DateTime.Now; 

    #endregion 
} 

Вот пример его использования в юнит-тест XUnit:

[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); 
} 

тест работает нормально локально, однако он терпит неудачу на нашем сервере сборки, как DateTimes отличаются в Assert заявлении ,

Я изо всех сил пытаюсь придумать причину, по которой это может потерпеть неудачу при условии, что DateTime высмеивается через вышеупомянутую оболочку делегата. Я проверил, нет никаких проблем в реализации метода 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 
} 
+0

ли 'UpdateLastAccessedTimestamp' делать какие-либо манипуляции с даты, или, возможно, тянуть время от внешнего источника? Можете ли вы увидеть причину сбоя ('values') между« ожидаемыми »и« фактическими »? Что такое this._testEntry? ваша часть 'Act', похоже, не проходит в вашем ожидаемомTimestamp – Kritner

+0

@ Kritner благодарит за ваш ответ. Я обновил свой вопрос в ответ на ваши отзывы. Обратите внимание, что вызовы объекта _unitOfWork не выполняют никаких манипуляций с полем LastAccessedOn. – aw1975

+1

Просто любопытно ... если вместо 'SystemTime.Now =() => expectedTimestamp;' вы сделали 'SystemTime.Now =() => новый DateTime (2000, 1, 1);' - что бы ваши результаты вышли в виде? Является ли ваш ожидаемый 2000, 1, 1? Является ли ваш фактический 2000, 1, 1? Или это возможно 2016, 2, 24? Это, по крайней мере, надеюсь, поможет сузить проблему. – Kritner

ответ

3

Ваша проблема может быть вызвана несколькими модульными тестами, которые работают с static SystemTime. Если бы вы, например, иметь что-то вроде:

тест Unit 1

[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(); 
} 

тест Блок 2

[Fact] 
public void do_something_different 
{ 
    SystemTime.Now =() => DateTime.Now; 
} 

Итак, давайте предположим, что модульное тестирование 2 (это цельность) выстреливает в между линии в единичном испытании 1 из:

SystemTime.Now =() => expectedTimestamp; 

// Unit test 2 starts execution here 

// Act 
service.UpdateLastAccessedTimestamp(this._testEntry); 

Если этот сценарий произойдет, то ваш UpdateLastAccessedTimestamp не будет (обязательно) иметь ожидаемое значение DateTime, которое вы установили в SystemTime.Now =() => expectedTimestamp;, поскольку еще один тест с тех пор перезаписал функцию, которую вы предоставили из модульного теста 1.

Вот почему я думаю, что вы, вероятно, лучше, или пропуская DateTime в качестве параметра, или с помощью инъекционного DateTime, как так:

/// <summary> 
/// Injectable DateTime interface, should be used to ensure date specific logic is more testable 
/// </summary> 
public interface IDateTime 
{ 
    /// <summary> 
    /// Current Data time 
    /// </summary> 
    DateTime Now { get; } 
} 

/// <summary> 
/// DateTime.Now - use as concrete implementation 
/// </summary> 
public class SystemDateTime : IDateTime 
{ 
    /// <summary> 
    /// DateTime.Now 
    /// </summary> 
    public DateTime Now { get { return DateTime.Now; } } 
} 

/// <summary> 
/// DateTime - used to unit testing functionality around DateTime.Now (externalizes dependency on DateTime.Now 
/// </summary> 
public class MockSystemDateTime : IDateTime 
{ 
    private readonly DateTime MockedDateTime; 

    /// <summary> 
    /// Take in mocked DateTime for use in testing 
    /// </summary> 
    /// <param name="mockedDateTime"></param> 
    public MockSystemDateTime(DateTime mockedDateTime) 
    { 
     this.MockedDateTime = mockedDateTime; 
    } 

    /// <summary> 
    /// DateTime passed from constructor 
    /// </summary> 
    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 вы могли новые вверх, как (или используйте контейнер МОК):

Service service = new Service(new SystemDateTime()); 

Для тестирования вы можете либо использовать насмешливые рамки, или ваш Пробный класс как таковые:

Service service = new Service(new MockDateTime(new DateTime(2000, 1, 1))); 

И ваш блок тест может стать:

[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); 
} 
+0

Большое спасибо за подробное объяснение. – aw1975

+0

Нет проблем - были ли вы в состоянии подтвердить, что это были множественные модульные тесты, переопределяющие предоставленные реализации 'SystemTime'? Это было только лучшее предположение: P – Kritner

0

Вы просто повезло, что он работает локально. Чтобы получить эту работу, вы должны заглушить место, где ваш сервис получает последний доступ к данным и проверяет время возвращения туда. В этот момент ваша локальная машина достаточно быстрая, чтобы возвращать 2 раза в то же время в DataTime. Теперь и ваш сервер сборки нет.

+0

Спасибо за ваш ответ. Доступ к DateTime осуществляется через делегат в службе. Я обновил свой вопрос, чтобы включить реализацию метода UpdateLastAccessedTimestamp. – aw1975