Sql server 使用tSQLt,在使用GETUTCDATE()时,如何隔离对系统时钟的依赖关系?

Sql server 使用tSQLt,在使用GETUTCDATE()时,如何隔离对系统时钟的依赖关系?,sql-server,tsql,tsqlt,Sql Server,Tsql,Tsqlt,我有一个多次使用GETUTCDATE()函数的存储过程。这是一个非常具体的业务范围,所以在这里展示可能没有多大意义。话虽如此,了解这场狂欢只会在今年举行或许会有所帮助。这里有一个人为的例子,它没有显示我正在做的事情的复杂性,但应该有助于说明我在说什么: CREATE PROCEDURE dbo.GenerateRequestListForCurrentYear AS BEGIN SELECT RequestId, StartDate, EndDate FROM Requests WHER

我有一个多次使用GETUTCDATE()函数的存储过程。这是一个非常具体的业务范围,所以在这里展示可能没有多大意义。话虽如此,了解这场狂欢只会在今年举行或许会有所帮助。这里有一个人为的例子,它没有显示我正在做的事情的复杂性,但应该有助于说明我在说什么:

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;
我看到了几个选择:

  • 将日期/时间作为参数传入(如果为NULL,则将值设置为GETUTCDATE())
  • 用我自己的函数替换对GETUTCDATE()的调用,以返回我可以使用tSQLt伪造的日期/时间
  • ?
  • 这两个选项似乎都是只有在有点臭的测试中才需要的;就如何从主应用程序调用存储过程而言,永远不需要参数化该存储过程


    由于它涉及到SQL中具有内置函数的依赖项,并且这些函数有自己的依赖项,那么有没有办法在tSQLt测试中伪造这些调用?是否有更好的方法伪造对GETUTCDATE()的调用以返回我在使用tSQLt的测试中指定的日期,或者这些是我唯一的选项?

    我使用了上面的选项1和2。在您的特定示例中,我更喜欢选项1,其中您有一个默认参数,除非您正在处理一个greenfields项目,在该项目中,您可以定义函数以预先返回datetime和datetimeutc,然后将其用法定义为今后的标准。我个人认为默认参数选项更简单、更清晰。

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

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

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

    您可以使用任何一种方法,就像Andrew一样,我过去都用过

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


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

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

    我会选择2,但如果您想要一些非常难闻的东西,请在通话前更改系统时间。事实上,如果你只想测试几次,它不会那么臭,也不需要改变你的程序。谢谢你的选择,@DaveGreen。我没有从这个角度考虑它,因为我通常认为我的期望是静态的。@DaveGreen这是一个可能的解决方案,但即使GETDATE()和GETUTCDATE()是非确定性的,即它们在查询范围内返回相同的值,在存储过程中,我会将GETDATEUTC()分配给一个变量。我猜在您的示例中,如果只考虑日期,那么随着年份的变化,测试失败的可能性很小。@Andrew-我接受您关于测试组装/动作部分中值变化的观点,如果查看较小的间隔(例如时间)然后,我会设置数据,然后根据指定的ID提取。返回值更改的时间段很短(当查看较长的日期时,在本例中为一年),并且在该时间调用它的风险可以忽略不计,但我同意您的观点,即它可能会在一些小时间间隔内导致错误故障,而且更难排除故障。谢谢你指出这一点。(+1)可能导致测试失败,仅在特殊日期失败,例如更改夏令时等。