【问题标题】:TSQLT Test Run TimeoutTSQLT 测试运行超时
【发布时间】:2017-08-17 20:59:18
【问题描述】:

我最近开始通过 Redgate 的 SQL 测试使用 TSQLT 来创建和运行单元测试。我遇到了一个问题。执行时间超过几分钟的单元测试将超时,从而停止执行所有其他单元测试。

如何延长 tSQLt 的超时时间?

我的“单元测试”可能实际上不是单元测试,但我不熟悉另一种更适合的测试方法。

我正在开展一个项目,以提高我们数据仓库的夜间刷新速度。目前,这个过程需要五个小时。通过重新安排任务以尽可能并行运行,我将时间缩短到两个小时。我的问题是,除非我能找到一种方法来证明新流程与旧流程具有完全相同的最终结果,否则 QA 将在接下来的一年中检查每个表中每一行的每一列中的每个值。要么这样,要么该项目将因为“太难”而被废弃。

所以我想出的测试是这样的: 我有一个数据库,在使用我创建的新方法在我们的测试环境中处理结果表后,我运行一个脚本来复制结果表。然后,回到测试环境,我运行旧进程来更新表。然后,我对每个表运行一个单元测试,以证明使用新方法处理的归档表的内容与使用旧方法重新处理的表的内容完全相同。

很遗憾,由于其中一些表的大小(数百万行),一些单元测试会超时。以下是我收到的错误:

测试程序:[HR360_unitTest1].[HR360_DW_Job6].[test fact_group_clients 相同的内容] 在 emr\preprod System.Data.SqlClient.SqlException (0x80131904):超时已过期。这 在操作完成之前经过的超时时间或 服务器没有响应。 ---> System.ComponentModel.Win32Exception (0x80004005): 等待操作超时 System.Data.SqlClient.SqlConnection.OnError(SqlException 异常, Boolean breakConnection, Action`1 wrapCloseInAction) at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException 异常,布尔型 breakConnection,Action`1 wrapCloseInAction) 在 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler、SqlDataReader 数据流、 BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean & dataReady) 在 System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior,字符串 resetOptionsString) 在 System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior、RunBehavior runBehavior、布尔 returnStream、布尔 异步,Int32 超时,任务和任务,布尔 asyncWrite,SqlDataReader ds,布尔值 describeParameterEncryptionRequest)在 System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior、RunBehavior、runBehavior、布尔返回流、字符串 方法,TaskCompletionSource1 completion, Int32 timeout, Task& task, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource1 完成,字符串方法名,布尔型 sendToPipe,Int32 超时, 布尔 asyncWrite) 在 System.Data.SqlClient.SqlCommand.ExecuteNonQuery() 在 RedGate.SQLTest.tSQLt.FrameworkWrapper.#kz(SqlCommand #LGj) 在 RedGate.SQLTest.tSQLt.FrameworkWrapper.#7qHc(String #2xAd, SqlParameter[] #LvPb) 在 RedGate.SQLTest.tSQLt.FrameworkWrapper.#qd4b(String #LGxc) 在 RedGate.SQLTest.tSQLt.TestRunner.Execute(SqlConnection 连接) ClientConnectionId:519569ed-03ce-4510-b226-9ff18e0f1d8d 错误 编号:-2,状态:0,等级:11

如果无法增加 tSQLt 的超时时间,那么我将不得不寻找另一种方法来自动测试这些表的内容是否相同(以可随意重复的方式)或放弃该项目.

【问题讨论】:

  • 您是否尝试过更改连接字符串中的超时属性? msdn.microsoft.com/en-us/library/…
  • 不幸的是,对连接字符串的调用是由 tSQLt 框架进行的。我发布了这个问题,因为我无法找到一种方法来告诉 tSQLt 使用不同的超时参数进行这些调用。您是说有一种方法可以在 tSQLt 周围进行结束运行并设置将覆盖 tSQLt 在运行时使用的任何值的参数?
  • 只是想一想,您是否尝试过从命令行运行 tSQLt(使用 sqlcmd 或 sql-ps)?或者,如果这只是一次性的,您甚至可以在 SSMS 中运行它并将结果窗口导出为“证据”。我认为我从未在 SSMS 中看到过 tSQLt 超时,并且我们经常在大约 18 分钟内运行超过 6000 次测试而没有问题。虽然,在 VSTS 中运行这些测试时,我们必须逐类运行以避免 CI 过程超时。超时可能在 SQL 测试中,而不是 tSQLt 框架本身。
  • 谢谢@datacentricity!事实证明,你的怀疑是对的。经过进一步测试,我的问题似乎与 Redgate SQL 测试有关,而不是 tSQLt。我按照您的建议使用 tSQLt.Run 运行了一项测试。我应该小心我的愿望,因为它在 19 小时后仍在运行。为了验证它不只是卡住了,我创建了一个只执行 WAITFOR DELAY '00:10' 的假存储过程,并为此过程创建了一个单元测试。通过 SQL 测试运行的单元测试超时。使用 tSQLt.Run 的单元测试成功。

标签: sql-server unit-testing tsqlt


【解决方案1】:

tSQLt 可能不会是我进行此类测试的首选,它专为单元测试而设计并且完全适用于单元测试 - 小部分代码的小型、通常快速测试。您可以查看 DbFit 之类的内容,但这也将难以处理大量数据。

如果您真的必须证明每个表上每一行的每一列都是相同的,我会考虑在两个数据库中的每个表上创建一个 MD5 哈希列,然后只需在每个表中查询哈希不匹配的行。在构建哈希值时,您需要排除任何无法保证在两个表中相同的列,例如IDENTITY 值、加载日期/时间等。例如:

--! Create our existing table structures create table dbo.ExistingCustomer ( CustomerId int not null identity(1,1) primary key , LastName varchar(50) not null , FirstName varchar(50) not null , MiddleName varchar(50) null , TownOfBirth varchar(50) null , DateOfBirth datetime not null , NumberOfDependents int not null , EtlCreatedOn datetime not null ) go

——!添加一个连接所有感兴趣的列的计算列 ——!成单个字符串,在过程中处理 NULL,然后创建 ——!整个字符串的 32 个字符 MD5 散列 更改表 dbo.ExistingCustomer 将 DeltaHash 添加为 转换(nvarchar(32),哈希字节('MD4' ,转换(nvarchar(最大值) ——!即使您可能合理地期望不会填写名字和姓氏, ——!总是代码防御性地对一系列空字符串进行散列将给出 ——!不太自信的结果 ,合并(nullif(姓氏,''),'姓氏') +合并(nullif(名字,''),'名字') ——!此模式对可为空的列也有效 +合并(nullif(中间名,''),'中间名') + 合并(转换(char(24),TownOfBirth,121),'TownOfBirth') + coalesce(cast(NumberOfDependents as varchar(32)), 'NumberOfDependents')) 整理 Latin1_General_CI_AS), 2) 持续 去

在构建此哈希时,您需要非常小心排序规则(在两个数据库之间甚至在列级别)和空格。想象一下,您在一行中有四个整数列,所有这些列都是 NULL bar 一列。如果只用空字符串替换 null,则无论第二列或第三列是否包含有效整数,MD5 哈希值都将相同。

您需要将此列添加到旧表和新加载数据的副本中,然后您可以使用如下查询:

--! Expect Zero select count(*) as [FailCount] from OldLoadDb.dbo.ExistingCustomer as ec inner join NewLoadDb.dbo.NewCustomer as nc --! This join should probably be on some business key but you get the idea on nc.CustomerId = ec.CustomerId where ec.DeltaHash <> ec.DeltaHash go

像上面这样的东西在 DbFit 中运行得非常好,因为所有繁重的工作都在服务器端完成。

您还应该为存在于一个表中而不存在于另一个表中的行添加测试。

当然,哈希查询不会告诉您有什么区别,但至少可以让您识别不同的行。

【讨论】:

  • 我对 DbFit 不熟悉。怎么样让它更适合这种测试?它比 tSQLt 更健壮吗?你的解决方案可能是我最终不得不去做的。使用 tSQLt 是个好主意,因为我不必跟踪每个表的主键并在每个表测试中包含引用它们的代码。但是,如果这是我必须做的,那么这就是我必须做的。如果似乎没有人对我的问题有更好的解决方案,我会继续并将其标记为答案。
  • DbFit 包含一个称为“存储查询”的测试装置,它非常适合比较不同服务器上两个数据库中表的内容。我知道你可以使用链接服务器查询来做同样的事情——尽管不能使用 tSQLt。 DbFit 的缺点是它将所有行(包括通过/失败信息)逐个单元格地呈现为 wiki 页面上的 html。因此,虽然对于大数据量不是很好,但它确实可以很好地直观地表示所有通过和失败。我在使用 DbFit 作为较小(测试/样本)数据集的 ETL 集成测试框架方面取得了巨大成功
  • 关于 MD5 哈希方法的最后一个想法 - 如果您的表结构良好 - 即您要排除的 ETL 审计列在所有表上重命名为相同,并且存在某种编程上可理解的模式要使用的主键/唯一键,从元数据(INFORMATION_SCHEMA 视图等)自动生成上述 ALTER TABLE 和比较查询应该太难了。我猜这取决于你需要多少张桌子。可悲的是,解释如何做到这一点本身就是一篇完整的博客文章:-)
  • 为此,我会远离像 MD5 这样的短散列。 SQL Server 能够计算更适合此类问题的“加密安全”哈希。但是,您可以通过使用 SQL Server 的内置 EXCEPT 语句轻松摆脱没有散列的情况 - 至少在您的表具有主键的情况下。如果没有主键,您将不得不使用 tSQLt 正在使用的表比较算法之类的东西。 tSQLt 基本上是在所有列上使用GROUP BY 计算每个表中所有相等的行,然后将计数与所有其他列进行比较。
  • 请记住,您必须同时执行 SELECT * FROM A.dbo.table EXCEPT SELECT * FROM B.dbo.tableSELECT * FROM B.dbo.table EXCEPT SELECT * FROM A.dbo.table 才能捕获丢失的行和额外的行。
【解决方案2】:

超时问题出现在 Redgate SQL 测试中。因此,长时间运行的单元测试的简单解决方案是通过调用 tSQLt.Run 直接在 tSQLt 框架中运行它们。在我的测试中,tSQLt 似乎没有超时问题。在撰写本文时,我有一个单元测试已经连续运行了 19 个小时而没有超时。

这个单元测试需要很长时间才能运行,在我的特殊情况下会产生它自己的问题。我将使用散列解决方案或 EXCEPT 解决方案来解决此问题,如@datacentricity 的响应中所讨论的线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-02
    • 2012-04-11
    • 2017-10-30
    • 1970-01-01
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多