Используя tSQLt, как я могу изолировать зависимость от системных часов при использовании GETUTCDATE()?

У меня есть хранимая процедура, которая использует функцию GETUTCDATE() несколько раз. Это очень специфическая линия бизнеса, поэтому, вероятно, не имеет смысла показывать это здесь. Сказав это, было бы полезно знать, что sproc будет когда-либо называться только на текущий год. Вот надуманный пример, который не показывает сложности того, что я делаю, но должен помочь проиллюстрировать то, о чем я говорю:

CREATE PROCEDURE dbo.GenerateRequestListForCurrentYear AS BEGIN
  SELECT RequestId, StartDate, EndDate FROM Requests
  WHERE YEAR(EndDate) = YEAR(GETUTCDATE());
END;

Мой тест выглядит так:

CREATE PROCEDURE testClass.[test Requests are generated for the current year] AS BEGIN
  -- arrange
  EXEC tSQLt.FakeTable 'dbo.Requests';
  INSERT INTO dbo.Requests (RequestId, StartDate, EndDate) VALUES
    (1, '2/1/14', '2/10/14'), (2, '2/1/13', '2/10/13');

  SELECT TOP (0) * INTO #Expected FROM dbo.Requests;
  SELECT TOP (0) * INTO #Actual FROM dbo.Requests;
  INSERT INTO #Expected VALUES
    (1, '2/1/14', '2/10/14');

  -- act
  INSERT INTO #Actual
  EXEC dbo.GenerateRequestListForCurrentYear;

  -- assert
  EXEC tSQLt.AssertEqualsTable #Expected, #Actual;
END;

Я вижу пару вариантов:

  1. Передайте дату / время в качестве параметра (и установите значение GETUTCDATE(), если NULL)
  2. Замените вызов GETUTCDATE() моей собственной функцией, чтобы вернуть дату / время, которое я могу подделать, используя tSQLt
  3. ?

Оба из них кажутся вариантами, которые необходимы только для теста, который кажется немного вонючим; никогда не было бы необходимости параметризовать этот sproc относительно того, как он вызывается из основного приложения.

Что касается зависимостей со встроенными в SQL функциями, которые имеют свои зависимости, есть ли способ подделать эти вызовы в тесте tSQLt? Есть ли лучший способ подделать вызов GETUTCDATE() для возврата даты, которую я указываю в своих тестах с использованием tSQLt, или это единственные мои варианты?

4 ответа

Решение

Вы можете использовать любой подход, и, как Эндрю, я использовал оба в прошлом.

Я обычно предпочитаю вариант 1 (передавая параметр) всякий раз, когда могу. Это имеет дополнительное преимущество, заключающееся в том, что выполнение становится более повторяемым. Другими словами, я могу указать произвольную дату, что часто полезно для поддержки и не раз облегчает добавление новой функции. Я нахожу это полезным и в языках вне SQL. Это приводит к тому, что результат выполнения отдельного сложного фрагмента кода зависит не от того, когда он был выполнен, а от полученных параметров и данных в системе. Другими словами, помимо проверки передачи текущего времени в качестве параметра есть преимущество, которое может облегчить ваши колебания. (Следует отметить, что я также считаю, что нет ничего плохого в принятии проектного решения для поддержки тестирования - это важная часть продукта).

Вариант 2 также работает, и вы просто создадите свою собственную функцию, которая обернет системную функцию. Однако вы должны знать, что это может повлиять на производительность, и вам следует тщательно ее протестировать. SQL Server не всегда добр к вам, когда функции участвуют в запросах. Мне не нравится совет никогда не использовать скалярные функции в запросах из-за предполагаемых проблем с производительностью, потому что иногда нет снижения производительности, а иногда разница в производительности не является проблемой. Тем не менее, вы должны знать о потенциале, если вы решите обернуть функцию.

Я буду генерировать свои тестовые данные в тесте, чтобы тест всегда имел достоверные данные за текущий год (т.е. использовал относительные даты). Таким образом, данные всегда будут возвращать ожидаемый результат.

Конечно, вызов GETUTCDATE() является зависимостью, но на самом деле вам будет трудно (или невозможно) изолировать от большинства системных функций; Я думаю, что более важно проверить функциональность даты, которая будет использоваться.

Чтобы использовать ваш пример:

CREATE PROCEDURE testClass.[test Requests are generated for the current year] AS BEGIN
  -- arrange
  EXEC tSQLt.FakeTable 'dbo.Requests';
  INSERT INTO dbo.Requests (RequestId, StartDate, EndDate) --Calculate working example in current year
    SELECT 1,'2/1/'+convert(nvarchar(4),year(GETUTCDATE())),'2/10/'+convert(nvarchar(4),year(GETUTCDATE()))
  INSERT INTO dbo.Requests (RequestId, StartDate, EndDate) --Calculate previous year example
    SELECT 2,'2/1/'+convert(nvarchar(4),year(dateadd(year,-1,GETUTCDATE()))),'2/10/'+convert(nvarchar(4),year(dateadd(year,-1,GETUTCDATE())))

  SELECT TOP (0) * INTO #Expected FROM dbo.Requests;
  SELECT TOP (0) * INTO #Actual FROM dbo.Requests;
  INSERT INTO #Expected 
    SELECT 1,'2/1/'+convert(nvarchar(4),year(GETUTCDATE())),'2/10/'+convert(nvarchar(4),year(GETUTCDATE()));
  -- act
  INSERT INTO #Actual
  EXEC dbo.GenerateRequestListForCurrentYear;

  -- assert
  EXEC tSQLt.AssertEqualsTable #Expected, #Actual;
END;

Я предпочитаю такой подход, так как хотя кодирование теста немного сложнее и требует размышлений об ожидании теста в будущих периодах времени, он позволяет вам кодировать без вмешательства в тестируемый код и позволяет вам знаю, что тест будет функционировать в будущем.

Я использовал оба варианта 1 и 2 выше. В вашем конкретном примере я бы предпочел вариант 1, где у вас есть параметр по умолчанию, если вы не работаете над проектом greenfields, где вы можете определить функции, которые возвращают datetime и datetimeutc заранее, а затем определить их использование в качестве стандарта в будущем. Лично я считаю, что опция параметров по умолчанию немного проще и понятнее.

Я попробовал оба подхода с моей системой и обнаружил, что реализация моей собственной функции-оболочки была лучшим решением. Передача даты в качестве параметра привлекательна, если вы имеете дело только с хранимыми процедурами, но, как я выяснил, сложным способом, это может быть проблематично для представлений, которые выполняют вычисления вокруг даты. Я бы предложил создать вспомогательную функцию GETUTCDATE и использовать ее вместо прямого вызова системной функции. В конце дня вы получите больше контроля, и это позволит вам создавать больше / лучше тестов.

Другие вопросы по тегам