5

我有一个多次使用 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. 将日期/时间作为参数传入(如果为 NULL,则将值设置为 GETUTCDATE())
  2. 用我自己的函数替换对 GETUTCDATE() 的调用,以返回我可以使用 tSQLt 伪造的日期/时间
  3. ?

这两个似乎都是测试所需的选项,看起来有点臭;关于如何从主应用程序调用这个存储过程,永远不需要参数化它。

由于它与 SQL 中具有自己依赖关系的内置函数的依赖关系有关,有没有办法在 tSQLt 测试中伪造这些调用?有没有更好的方法来伪造对 GETUTCDATE() 的调用以返回我在使用 tSQLt 的测试中指定的日期,或者这些是我唯一的选择吗?

4

4 回答 4

4

您可以使用任何一种方法,并且像 Andrew 一样,我过去都使用过这两种方法。

但是,我通常更喜欢选项 1(传递参数)。它具有使执行更具可重复性的额外好处。换句话说,我可以传入任意日期,这通常对支持很有用,并且不止一次使添加新功能更容易。我发现这在 SQL 之外的语言中也很有用。它的作用是使单个复杂代码的结果不依赖于它的执行时间,而是依赖于接收到的参数和系统中的数据。换句话说,除了测试将当前时间作为参数传递之外,还有其他好处,并且可以减轻您的犹豫。(需要注意的是,我也相信做出支持测试的设计决策并没有错——它是产品的重要组成部分)。

选项 2 也有效,您只需创建自己的函数来包装系统函数。但是,您应该知道,这可能会对性能产生影响,您应该对其进行彻底的测试。当查询中涉及函数时,SQL Server 并不总是对您友善。由于假定的性能问题,我不喜欢永远不要在查询中使用标量函数的建议,因为有时没有性能损失,有时性能差异不是问题。但是,如果您决定包装该函数,您应该意识到这种可能性。

于 2014-04-11T02:53:03.807 回答
2

我会在测试中生成我的测试数据,这样测试总是有当年的可靠数据(即使用相对日期)。这样,数据将始终返回预期结果。

当然,对 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;

我更喜欢这种方法,因为尽管编写测试代码需要做更多的工作,并且需要考虑对未来时间段的测试预期,但它允许您在不干扰被测代码的情况下编写代码,并允许您知道该测试在未来几年仍将起作用。

于 2014-04-10T20:35:10.247 回答
1

我已经使用了上面的选项 1 和 2。在您的特定示例中,我更喜欢选项 1,其中您有一个默认参数,除非您正在处理一个新建项目,您可以在其中定义函数以预先返回 datetime 和 datetimeutc,然后将它们的用法定义为未来的标准。我个人认为默认参数选项只是更容易和更清晰一点。

于 2014-04-10T20:11:21.153 回答
0

我在我的系统上尝试了这两种方法,发现实现我自己的包装函数是更好的解决方案。如果您只处理存储过程,则将日期作为参数传递是很有吸引力的,但是正如我发现的那样,对于在日期前后执行计算的视图可能会出现问题。我建议制作一个 GETUTCDATE 辅助函数并使用它而不是直接调用系统函数。您最终将获得更多控制权,这使您能够创建更多/更好的测试。

于 2017-01-19T03:52:04.673 回答