使用tSQLt测试SQL Server代码

仅供参考:本文是我在SQA第25天的演讲的扩展版本。

根据与同事的经验,我可以指出:DB代码测试不是一种广泛的实践。 这可能具有潜在的危险。 就像所有其他“常规”代码一样,数据库逻辑也是由人类编写的。 因此,可能存在故障,可能对产品,企业或用户造成负面影响。 无论这些存储过程是帮助后端的过程还是ETL修改仓库中的数据-始终存在风险,测试有助于降低风险。 我想告诉您tSQLt是什么以及它如何帮助我们测试数据库代码。


上下文


有一个使用SQL Server的大型仓库,其中包含不同的临床试验数据。 它来自各种来源(主要是面向文档的数据库)。 许多ETL在很多情况下都会转换仓库中的数据。 可以将这些数据加载到较小的数据库中,以供面向小型特定任务的Web应用程序使用。 一些客户的客户要求实现其需求的API。 此类API通常使用存储过程和不同的查询。


通常,DBMS端有大量代码。

我们为什么需要这个?


从简介中可以看到,数据库代码是应用程序代码的一部分,并且它也可能包含错误。

我想我们很多人都熟悉Boehm的Curve:错误总是在以后的过程中更昂贵地修复。 在较早的开发阶段犯下的错误,而在较晚的开发阶段本地化的错误,可能会造成更大的损失。 这是由于有必要两次执行许多临时步骤(编码,单元测试,集成测试,系统测试等):用于调试并将代码返回到找到它的阶段。 仓库情况也是如此。 如果ETL过程出错,并且数据被多次修改,我们必须:

  1. 完成所有数据转换步骤,回到问题的根源
  2. 解决问题
  3. 再次导出正确的数据(可能需要其他手动编辑)
  4. 确保没有其他由错误引起的损坏数据。

不要忘记我们不出售毛绒玩具。 临床试验等方面的错误不仅可能损害业务,还会损害人体健康。

怎么测试?


因为我们在谈论代码测试,所以我们指的是单元测试和集成测试。 这些事情是非常重复的,意味着持续的回归。 严格来说,这种测试绝不是手动完成的(好吧,除非是某些特殊情况)。

不错的好处:测试可以作为代码文档的辅助材料。 例如,需求可能看起来像这样(可点击):


XLS文件,两列具有要求+其他列中的零碎附加信息+令人困惑的标记。 如有必要,可能很难恢复最初的愿望。 测试可以帮助记录实施的细微差别。 当然,不应将它们视为文档的替代品。

不幸的是,测试复杂度随着代码复杂度的增加而增加,因此,这种影响可以被消除。

测试可以是针对自发合并的附加安全层。 CI自动测试由于其形式主义而有助于解决此问题。

因此,如果我们决定使用自动化,则需要为其选择工具。

使用什么进行测试?


在进行DB代码测试的情况下,我看到两种方法:SQL支持(当工具直接在DBMS中运行时)和非SQL支持。 这是我发现的主要区别:
SQL驱动
非sql-powered
需要安装数据库对象
需要安装外部(相对于数据库)工具
测试独立于DB外使用的技术
测试可以取决于数据库外部使用的技术
该框架始终专用于一个DBMS
框架通常支持多个DBMS
DBMS知识是编写测试的唯一要求。 可以使用手动测试仪或DBA
编写测试需要其他编程语言或技术知识; 通常需要开发人员的帮助
DBMS级执行允许使用高级伪造和断言。
在DBMS外部执行可能会限制工具的功能
对于SQL Server,我们有几种选择:
一般资讯
名称方法建筑学语言/平台测试语言
数据库SQL驱动单位T-SQL + CLRT-SQL
TSQLUnitSQL驱动单位T-SQLT-SQL
utTSQLSQL驱动单位T-SQLT-SQL
TstSQL驱动单位T-SQLT-SQL
数据库非sql-powered健身小姐C#/ javaWiki降价
懒人非sql-poweredRSpec(面向BDD)红宝石红宝石
NUnit非sql-powered单位不适用不适用
日期
名称初次露面最新提交最新版本
数据库2007-07-272019/07/072016年1月1日
TSQLUnit2006-12-16(0.9)
2007-07-21(0.91 rc1)
2018年4月26日(GitHub)04/09/2011(SourceForge)
utTSQL2003-03-122003-03-122003-03-12
Tst2009年2月3日(v1.0)不适用2012年3月30日
数据库2009年12月1日10-09-20182015年8月15日
懒人2011年6月23日2018/12/122018/12/12
NUnit不适用不适用不适用
特色功能
名称不需要CLRXML输出在单独的交易中进行测试假货错误处理程序断言
数据库--++++优秀的
TSQLUnit+--+----失败
utTSQL+--------低于平均水平
Tst+++(可选)--+优秀的
数据库+--+(可选)--+很好 细微的
懒人+--+(可选)----很好 细微的
NUnit++不适用不适用不适用优秀 细微的
其他
名称文献资料社区活动
数据库优秀 细微的优秀的
TSQLUnit低于平均水平低于平均水平
utTSQL优秀的低于平均水平
Tst优秀的低于平均水平
数据库优秀的平均值
懒人优秀的平均值
NUnit优秀的优秀的
等级“优秀-失败”是主观的,很抱歉,很难找到解决方法。

“首次出现”-最早出现的框架出现日期-最早的发布或提交。

如您所见,基于SQL的替代品已被放弃很长时间了,而tSQLt是当前唯一受支持的产品。 此外,tSQLt在功能上胜出。 唯一的是,TST比tSQLt拥有更多的断言集合。 但是,我怀疑这会超过所有弊端。

tSQLt文档有一些细微差别,我将在后面描述。

在非SQL驱动的世界中,事情还不清楚。 尽管不是超级活跃,但替代方案正在开发中。 DbFit是一个基于FitNesse框架的非常有趣的工具。 这意味着使用Wiki标记编写测试。 Slacker也是一个有趣的问题:建议对数据库代码测试使用BDD方法。

我应该说一下非SQL驱动的解决方案中的断言。 乍一看,断言的数量较少,我们可以认为此类工具较差。 但是我们应该记住,它们与tSQLt本质上是不同的,因此,这种表面上的看是不正确的。

最新的行-“ NUnit等” -更像是个提醒。 借助其他库,可以将许多常用的单元测试框架应用于DB代码。 该行中有很多N / A,因为该行实际上包括多个工具。 这就是“断言”列中“细微差别”的来源-不同的工具可以提供不同的集合,并且不能保证所有断言都可以应用于DB。

作为另一个有趣的指标,我们可以考虑Google趋势


细微差别:

  1. 我决定不包括Slacker,因为该名称可能表示不同的含义(并且在图中几乎看不到“ Slacker framework”之类的查询)。
  2. 出于好奇(并且因为其中一个空位仍然空着),我添加了TST趋势。 但是它很难向我们显示真实的情况,因为它的缩写也可能意味着不同的含义。
  3. 我没有包括NUnit及其类似物。 这些工具是“常规”代码测试的框架,因此它们的趋势不能描述我们的情况。

如您所见,tSQLt是列表中搜索最多的工具。 另一个(较少)流行的工具是DbFit。 其他工具的普及程度有限。

总的来说,我们可以看到tSQLt在背景下发光。

什么是tSQLt?


很容易猜到tSQLt是一个基于SQL的单元测试框架。 官方网站是https://tsqlt.org

从2005 SP2开始,承诺tSQLt支持SQL Server。 我没有检查过这样的早期版本,但是我在开发服务器上看不到2012年的问题,在本地计算机上看不到2017年的问题。

开源Apache 2.0许可证, 可在GitHub上获得 。 像往常一样,我们可以在商业项目中免费进行分叉,贡献和免费使用,更重要的是,不必担心CLR中的间谍软件。

机械学



测试用例是存储过程。 它们可以组合成测试类(xUnit术语中的测试套件)。

测试类不过是数据库模式。 tSQLt需要向NewTestClass过程注册它们,该过程将测试类添加到特殊表中。

可以确定设置程序。 这样的过程将在每个单独的测试用例运行之前运行。

不需要运行测试用例后的拆卸程序。 带有SetUp的每个测试用例都在一个单独的事务中运行,该事务在结果收集后回滚。 这很方便,但是会带来一些负面影响-我稍后再介绍。

该框架允许一次运行一个测试用例,一次只运行整个测试类,甚至一次只运行所有注册的测试类。

功能和示例


不愿重复官方指南,我将在示例中展示tSQLt功能。

免责声明:

  • 例子被简化
  • 原始代码不是完全属于我的-而是集体创造的
  • 为了更充分地展示功能,我对示例2进行了虚构化。

示例#1:CsvSql


应客户的一位客户的要求实施以下步骤。 Nvarchar(MAX)字段中存储有SQL查询。 创建了最小的UI来查看它们。 这些查询生成的结果集在后端使用,以进一步组成CSV文件。 可以通过API调用来请求CSV文件。


结果集很大,并且包含大量列。 这种结果集的假设示例:


该结果集代表临床试验数据。 让我们仔细看一下[ClinicsNum]的计算。 我们有2个表格:[试验]和[诊所]。


有一个FK:[Clinic]。[TrialID]-> [Trial]。[TrialID]。 显然,使用COUNT(*)派生许多诊所就足够了:

SELECT COUNT(*), ...  FROM dbo.Trial  LEFT JOIN dbo.Clinic    ON Trial.ID = Clinic.TrialID  WHERE Trial.Name = @trialName  GROUP BY ... 

我们如何测试这样的查询? 首先,让我们使用存根FakeTable,这将使我们的进一步工作容易得多。

 EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic'; 

FakeTable使事情变得简单-重命名旧表并创建具有相同名称的新表。 相同的名称,相同的列,但没有约束和触发器。

我们需要这样做是因为:

  1. 测试数据库可能包含一些数据,这些数据可能会阻止测试的正确运行。 FakeTable允许我们不要依赖它们。
  2. 通常,出于测试目的,我们仅需要填写几列。 该表可以包含很多表,通常包含约束和触发器。 我们使以后插入数据更加容易-我们将仅插入测试信息所需的内容,从而使测试尽可能简化。
  3. 不会有不必要的触发运行,因此,我们不必担心后效应。

然后我们插入所需的测试数据:

 INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1,   'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1,   1,        'Clinic1'), (2,   1,        'Clinic2'); 

我们从数据库派生查询,创建[Actual]表并用结果填充它
从查询中设置。

 DECLARE @sqlStatement NVARCHAR(MAX) = (SELECTCREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ... 

现在,我们填写[Expected]-我们的期望值:

 CREATE TABLE expected (   ClinicsNum INT ); INSERT INTO expected SELECT 2 

我想提请您注意,尽管[实际]列中有完整列,但[期望]表中只有一列。


这是由于AssertEqualsTable过程的一项有用功能,我们将使用它进行值验证。

 EXEC tSQLt.AssertEqualsTable   'expected',   'actual',   'incorrect number of clinics'; 

它仅比较两个表中显示的那些列。 在我们的例子中,这非常方便,因为被测查询返回很多列,每列都包含相当复杂的逻辑。 我们不想增加测试用例,因此此功能确实有帮助。 当然,此功能是一把双刃剑。 如果通过SELECT TOP 0填充了[Actual],并且在某一时刻出现意外列,则该测试用例将无法捕获该列。 您必须为此写其他支票。

AssertEqualsTable双程序


值得一提的是,tSQLt包含2个过程,如AssertEqualsTable。 它们是AssertEqualsTableSchema和AssertResultSetsHaveSameMetaData。 第一个与AssertEqualsTable相同,但是在表的元数据上。 第二个功能相同,但结果集的元数据相同。

示例2:约束


前面的示例向我们展示了如何删除约束。 但是,如果我们需要检查它们怎么办? 从技术上讲,约束也是逻辑的一部分,可以将它们视为测试覆盖的候选对象。

考虑上一个示例的情况。 2张桌子-[试验]和[诊所]; [TrialID] FK:


让我们尝试编写一个测试用例以进行检查。 首先,像前面的情况一样,我们伪造表:

 EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]' 

目的是相同的-摆脱不必要的限制。 我们想要孤立的支票而无需过多的努力。

接下来,我们返回要使用ApplyConstraint测试的约束:

 EXEC tSQLt.ApplyConstraint   '[dbo].[Clinic]',   'Trial_FK'; 

现在我们有一个检查配置。 检查本身是尝试插入数据将导致异常。 对于通过测试的案例,我们需要捕获此异常。 异常处理程序ExpectException可以提供帮助。

 EXEC tSQLt.ExpectException   @ExpectedMessage = 'The INSERT statement conflicted...',   @ExpectedSeverity = 16,   @ExpectedState = 0; 

我们可以尝试在处理程序设置之后插入不可插入的内容。

 INSERT INTO [dbo].[Clinic] ([TrialID])   VALUES (1) 

捕获到异常。 测试通过。

ApplyConstraint孪生程序


tSQLt作者提出的测试触发器的方法类似于测试约束。 我们可以使用ApplyTrigger过程将触发器返回到表。 之后,一切如上例所示-启动触发器,检查结果。

ExpectNoException-ExpectException的反义词


对于不能发生异常的情况,有一个ExpectNoException过程。 它与ExpectException的工作方式相同,除了在发生异常的情况下测试失败之外。

例子3:信号量


有一些存储过程和Windows服务。 它们执行的开始可能是由不同的外部事件引起的。 但是,它们的执行顺序是固定的。 因此,需要在数据库端实现访问控制-即信号量。 在我们的例子中,信号量是一组一起工作的存储过程。

让我们看一下信号量内的过程。 我们有2个表格-[Process]和[ProcStatus]:


[Process]表包含允许执行的进程的列表。 显然,[ProcStatus]包含上表中的进程状态列表。

那么,我们的程序做什么? 首先,它执行以下检查:

  1. 我们已经传递了一个过程名称作为过程的输入参数之一。 在[处理]表的[名称]字段中搜索该名称。
  2. 如果找到了进程的名称,它将检查[Process]表的[IsRunable]标志。
  3. 如果该标志为ON,则认为该过程可以运行。 最后的检查在[ProcStatus]表中进行。 我们需要确保该进程当前未运行,这意味着[ProcStatus]表中没有有关状态为“ InProg”的进程的记录。

如果一切正常,并且所有检查都通过,则将有关过程的新记录添加到状态为“ InProg”的[ProcStatus]表中。 该新记录的ID与ProcStatusId输出参数一起返回。

如果出现问题,我们期望以下几点:

  1. 发送给系统管理员的电子邮件。
  2. 返回ProcStatusId = -1。
  3. 没有添加新的[ProcStatus]记录。

让我们创建一个测试用例,以检查[Process]表中是否缺少流程。

我们再次使用FakeTable。 这里不是很关键,但是可以很方便,因为:

  1. 保证不会有任何数据可能破坏测试用例的执行。
  2. 新[ProcStatus]记录不存在的进一步检查将被简化。

 EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus'; 

有一个[SendEmail]过程,其名称说明一切。 我们需要抓住它的电话。 tSQLt建议为此使用SpyProcedure模拟。

 EXEC tSQLt.SpyProcedure 'dbo.SendEmail' 

SpyProcedure执行以下操作:

  1. 创建一个名称类似于[dbo]的表。[ProcedureName_SpyProcedureLog]
  2. 就像FakeTable一样,将原始过程替换为自动生成的过程,该过程具有相同的名称,但内部具有日志记录逻辑。 如果需要,还可以将自己的逻辑添加到生成的过程中。

不难猜测日志已记录到[dbo]。[SendEmail_SpyProcedureLog]表中。 该表包含[_ID_]列,用于呼叫的序列号。 随后的列以传递给过程的参数命名并用于收集它们,因此,也可以验证参数的值。


在进行信号量调用之前,我们需要做的最后一件事是创建一个用于存储[ProcStatusId]值的变量(确切地说是-1,因为不会添加记录)。

 DECLARE @ProcStatusId BIGINT; 

我们称信号量为:

 EXEC dbo.[Semaphore_JobStarter]   'SomeProcess',   @ProcStatusId OUTPUT; -- here we get -1 

现在,我们拥有了检查所需的所有数据。 让我们从检查开始
消息已发送。

 IF NOT EXISTS (   SELECT *   FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.'; 

在这种情况下,我们不检查传递的参数,而仅测试发送的事实。 我想提请您注意“失败”过程。 它允许我们“正式”失败一个测试用例。 如果您需要构建复杂的结构,Fail可以提供帮助。

现在,我们使用AssertEmptyTable过程检查[ProcStatus]表中是否没有记录。

 EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus'; 

这是我们一开始使用FakeTable的地方。 有了它,我们可能期望一个空表并使用一行代码进行测试。 在不进行表伪造的情况下进行检查的正确方法是比较过程执行前后的行数,这将需要更多的操作。

我们可以轻松地使用AssertEquals检查ProcStatusId = -1相等性。

 EXEC tSQLt.AssertEquals   -1,       @ProcStatusId,       'Wrong ProcStatusId.'; 

AssertEquals非常简单。 它只是比较2个值,没有什么特别的。

AssertEquals双程序


我们有以下过程用于值比较:

  • 断言等于
  • 断言不等于
  • AssertEqualsString
  • Assertike

我认为这些名称是不言自明的。 我要强调的唯一过程是AssertEqualsString。 这是专用于字符串值验证的过程。 考虑到通用的AssertEquals,为什么还要再执行一个程序? 问题是,AssertEquals / AssertNotEquals / AssertLike与SQL_VARIANT类型一起工作。 NVARCHAR(MAX)不包含在SQL_VARIANT中,因此tSQLt开发人员必须执行一个附加过程。

伪造功能


一推,我们可以将FakeFunction调用为类似于SpyProcedure的过程。 这种伪造允许用更简单的功能替换任何功能。 由于SQL Server功能像牙膏管一样工作(结果通过唯一的“孔”返回),因此从技术上讲,不可能实现日志记录功能。 内部逻辑替换是唯一可用的方法。

陷阱


我想告诉您一些在使用tSQLt期间可能遇到的陷阱。 在这种情况下,“陷阱”表示某些问题是由SQL Server限制引起的和/或框架开发人员无法解决的问题。

交易回滚和注定


我们团队面临的第一个也是主要问题是交易回滚和注定失败。 SQL Server无法单独回滚嵌套事务。 它总是将所有事务回滚到最外层。 考虑到tSQLt将每个测试包装到一个单独的事务中,这可能会成为问题,因为存储过程内的回滚会中断测试运行,并产生非描述性执行错误。

作为解决方法,我们使用保存点。 这个想法很简单。 首先,我们检查我们是否在事务内。 如果是,我们假设这是一个tSQLt事务并放置一个保存点,因此如有必要,我们将回滚到该点。 如果没有,我们开始新的交易。 实际上,我们不允许嵌套。


事务注定将使问题变得复杂-如果引发异常,则可能发生。 失败的事务无法提交,也无法回滚到保存点,因此我们必须再次将其回滚到最外层的事务。

考虑到以上几点,我们必须使用以下结构:

 DECLARE @isNestedTransaction BIT =   CASE WHEN @@trancount > 0       THEN 'true'       ELSE 'false' END; BEGIN TRY   IF @isNestedTransaction = 'false'       BEGIN TRANSACTION   ELSE       SAVE TRANSACTION SavepointName;       -- something useful   IF @isNestedTransaction = 'false'   COMMIT TRANSACTION; END TRY BEGIN CATCH   DECLARE @isCommitable BIT =       CASE WHEN XACT_STATE() = 1           THEN 'true'           ELSE 'false'   END;   IF @isCommitable = 'true' AND @isNestedTransaction = 'true'       ROLLBACK TRANSACTION SavepointName;   ELSE       ROLLBACK;   THROW; END CATCH; 

让我们逐段检查代码。 首先,我们需要确定我们是否在事务内。

 DECLARE @isNestedTransaction BIT =   CASE WHEN @@trancount > 0       THEN 'true'       ELSE 'false' END; 

派生@isNestedTransaction标志之后,我们可以根据情况启动TRY块并设置保存点或启动事务。

 BEGIN TRY   IF @isNestedTransaction = 'false'       BEGIN TRANSACTION   ELSE       SAVE TRANSACTION SavepointName;       -- something useful 

完成有用的操作后,如果它是“真实的”过程运行,我们将提交结果。

        -- something useful   IF @isNestedTransaction = 'false'   COMMIT TRANSACTION; END TRY 

当然,如果是测试用例运行,则无需提交任何内容。 tSQLt将最终自动回滚所做的更改。

如果出了什么问题,我们进入了CATCH块,我们需要确定事务是否可提交。

 BEGIN CATCH   DECLARE @isCommitable BIT =       CASE WHEN XACT_STATE() = 1           THEN 'true'           ELSE 'false'   END; 

仅在以下情况下,我们才能回滚到保存点:

  1. 交易是可提交的
  2. 这是一个测试运行,因此存在保存点。

在所有其他情况下,我们必须回滚整个交易。

    IF @isCommitable = 'true' AND @isNestedTransaction = 'true'       ROLLBACK TRANSACTION SavepointName;   ELSE       ROLLBACK;   THROW; END CATCH; 

是的,不幸的是,如果在测试运行期间达到不可提交的事务状态,我们仍然会收到执行错误。

仿制品和外键问题


让我们回顾一下熟悉的[Trial]和[Clinic]表


我们记得[TrialID] FK。 它会导致什么问题? 在上面的示例中,我们在两个表上都应用了FakeTable。 如果仅在其中之一上使用它,则将达到以下设置:


因此,即使我们在[Trial]的伪版本中准备了数据,尝试将记录插入[Clinic]的尝试也会失败。

 [dbo].[Test_FK_Problem] failed: (Error) The INSERT statement conflicted with the FOREIGN KEY constraint "Trial_Fk". The conflict occurred in database "HabrDemo", table "dbo.tSQLt_tempobject_ba8f36353f7a44f6a9176a7d1db02493", column 'TrialID'.[16,0]{Test_FK_Problem,14} 

结论:全部或全部不造假。 显然,万一没有,您应该准备一个包含所有必需测试数据的数据库。

系统程序上的SpyProcedure


不幸的是,我们无法监视系统过程:

 [HabrDemo].[test_test] failed: (Error) Cannot use SpyProcedure on sys.sp_help because the procedure does not exist[16,10] {tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure,7} 

在信号量示例中,我们跟踪了由开发人员创建的[SendEmail]过程的调用。 在这种情况下,仅测试并不需要。 必须创建一个单独的过程,因为在发送之前需要准备一些数据。 但是,您应该在心理上做好编写中间层过程的准备,以满足测试目的。

优点


快速安装


tSQLt安装包括2个步骤,大约需要2分钟。 如果CLR当前未激活,则需要激活它并执行一个SQL脚本。 就是这样:现在您可以添加第一个测试类并编写测试用例。

快速学习


tSQLt很容易学习。 我花了一个多工作日。 我问了同事,其他人似乎也需要1个工作日。 我怀疑这可能需要更多时间。

快速集成CI


在我们的项目上花费了大约2个小时来设置CI集成。 时间当然可以变化,但这通常不是问题,可以很快完成。

各种各样的工具


它是主观的,但我认为tSQLt功能丰富,可以满足大部分需求。 如果这还不够,那么对于罕见和复杂的情况,您始终可以使用Fail过程。

方便的文档


官方指南既方便又一致。 即使是第一个单元测试工具,您也可以在短时间内轻松了解tSQLt的用法。

清除输出


测试输出可以采用说明性的文本格式:

 [tSQLtDemo].[test_error_messages] failed: (Failure) Expected an error to be raised. [tSQLtDemo].[test_tables_comparison] failed: (Failure) useful and descriptive error message Unexpected/missing resultset rows! |_m_|Column1|Column2| +---+-------+-------+ |< |2 |Value2 | |= |1 |Value1 | |= |3 |Value3 | |> |2 |Value3 | +----------------------+ |Test Execution Summary| +----------------------+ |No|Test Case Name |Dur(ms)|Result | +--+------------------------------------+-------+-------+ |1 |[tSQLtDemo].[test_constraint] | 83|Success| |2 |[tSQLtDemo].[test_trial_view] | 83|Success| |3 |[tSQLtDemo].[test_error_messages] | 127|Failure| |4 |[tSQLtDemo].[test_tables_comparison]| 147|Failure| ----------------------------------------------------------------------------- Msg 50000, Level 16, State 10, Line 1 Test Case Summary: 4 test case(s) executed, 2 succeeded, 2 failed, 0 errored. ----------------------------------------------------------------------------- 

也可以从数据库(可点击)派生...


...甚至是XML。

 <?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="1" name="tSQLtDemo" tests="3" errors="0" failures="1" timestamp="2019-06-22T16:46:06" time="0.433" hostname="BLAHBLAHBLAH\SQL2017" package="tSQLt"> <properties /> <testcase classname="tSQLtDemo" name="test_constraint" time="0.097" /> <testcase classname="tSQLtDemo" name="test_error_messages" time="0.153"> <failure message="Expected an error to be raised." type="tSQLt.Fail" /> </testcase> <testcase classname="tSQLtDemo" name="test_trial_view" time="0.156" /> <system-out /> <system-err /> </testsuite> </testsuites> 

最后一种格式允许CI集成而没有任何问题。 具体来说,我们将tSQLt与Atlassian Bamboo一起使用。

支持redgate


作为专家之一,我可以说说最大的DBA工具提供商之一的支持-RedGate。 他们的SQL Server Management Studio插件名为SQL Test从一开始就可以与tSQLt一起使用。 此外,根据他在Google小组中的说法,RedGate帮助tSQLt的主要开发人员提供了开发环境。

缺点


没有临时餐桌伪造


tSQLt不允许伪造临时表。 想法,如有必要,可以使用非官方的插件。 不幸的是,此插件仅适用于SQL Server 2016+。

使用外部数据库


tSQLt旨在与安装框架的同一数据库中的代码一起使用。 因此,不可能将其与外部DB一起使用。 至少,假货不起作用。

 CREATE PROCEDURE [tSQLtDemo].[test_outer_db] AS BEGIN   SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password]   EXEC tSQLt.FakeTable '[AdventureWorks2017].[Person].[Password]'   SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] END 


看起来断言可以工作,但是当然不能保证其可使用性。

 CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN   SELECT TOP 1 *   INTO #Actual   FROM [AdventureWorks2017].[Person].[Password]   SELECT *   INTO #Expected   FROM (          SELECT 'bE3XiWw=' AS [PasswordSalt]   ) expectedresult;   EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual', 'The salt is not salty'; END 


文档错误


尽管我在上面提到指南是方便且一致的,但是文档还是存在一些问题。 它包含过时的部分。

示例1. “快速入门指南”建议从SourceForge下载该框架。
他们从SourceForge搬到了2015年

示例2. ApplyConstraint指南在异常捕获示例中利用了Fail过程的庞大设计。 可以使用ExpectException将其替换为简单明了的代码。

 CREATE PROCEDURE ConstraintTests.[test ReferencingTable_ReferencedTable_FK prevents insert of orphaned rows] AS BEGIN EXEC tSQLt.FakeTable 'dbo.ReferencedTable'; EXEC tSQLt.FakeTable 'dbo.ReferencingTable'; EXEC tSQLt.ApplyConstraint 'dbo.ReferencingTable','ReferencingTable_ReferencedTable_FK'; DECLARE @ErrorMessage NVARCHAR(MAX); SET @ErrorMessage = ''; /* [NB] Why don't we use ExceptException below? */ BEGIN TRY INSERT INTO dbo.ReferencingTable ( id, ReferencedTableId ) VALUES ( 1, 11 ) ; END TRY BEGIN CATCH SET @ErrorMessage = ERROR_MESSAGE(); END CATCH IF @ErrorMessage NOT LIKE '%ReferencingTable_ReferencedTable_FK%' BEGIN EXEC tSQLt.Fail 'Expected error message containing ''ReferencingTable_ReferencedTable_FK'' but got: ''',@ErrorMessage,'''!'; END END GO 

这是可以预期的,因为...

部分遗弃


从2016年初到2019年6月,开发工作一直处于停顿状态。是的,不幸的是,此工具已被部分弃用。 根据GitHub的说法,开发工作于2019年开始缓慢进行 。 尽管正式的Google网上论坛有一个话题,主要的tSQLt开发人员Sebastian被问及该项目的未来。 最后一个问题是在2019年3月2日提出的,没有答案。

SQL Server 2017问题


如果您使用的是SQL Server 2017,则tSQLt安装可能需要一些其他操作。Microsoft在此版本中实施了自2012年以来的首次安全更改。 已添加“ CLR严格安全性”服务器级别标志。 该标志不允许创建未签名的程序集(甚至SAFE)。 详细的描述应该另作一篇文章(幸运的是,我们已经有一篇不错的文章;也请依次阅读以下文章。为此,请做好心理准备。

当然,我可以将此问题归因于“陷阱”,但是tSQLt开发人员可以解决此问题。 GitHub问题已经上升 。 不过,自2017年10月以来尚未解决。

其他DBMS的替代(±)


tSQLt不是一种。 尽管由于CLR和T-SQL的细微差别,您不能在其他DBMS中使用它,但是您仍然可以找到类似的东西。 值得一提的是,这些替代方案与tSQLt不太接近,所以我的意思是说SQL支持的方法。

例如,PostgreSQL用户可以尝试pgTAP 。 这是一个使用本地PL / pgSQL进行测试和TAP输出格式的发达且积极开发的工具。 类似的工具MyTap可以帮助您在MySQL下进行测试。 该框架的功能不如pgTAP,但仍然有用。 而且它也在积极开发中。 如果您是快乐的Oracle用户,则有机会使用功能非常强大的工具utPLSQL 。 它发展非常积极,并提供了大量功能。

结论


我想传达2个想法:

第一:DB代码测试的有用性。 使用SQL Server,Oracle,MySQL或其他工具并不重要。 如果您的数据库包含未经测试的逻辑,那么您将承担风险。 与所有其他代码中的所有其他错误一样,DB代码错误可能会损坏产品和提供产品的公司。

第二:工具的选择。 对于那些使用SQL Server的人来说,tSQLt即使不是100%的赢家,也确实值得关注。 尽管发展缓慢且存在一些问题,但它仍然是一个实用的框架,可以使您的工作轻松得多。

有助于我的来源(非详尽列表)
DbFit-自动化的开源数据库测试: http//www.methodsandtools.com/tools/dbfit.php

DbFit文档: https : //dbfit.imtqy.com/dbfit/docs/

Slacker Wiki: https : //github.com/vassilvk/slacker/wiki

TST文档: https//archive.codeplex.com/projects/TST/4e04e281-9f35-4891-809a-15f09d304f4e

NUnit断言: https : //github.com/nunit/docs/wiki/Assertions

utTSQL代码: https ://sourceforge.net/p/uttsql/code/HEAD/tree/

Junit类断言: https : //junit.org/junit4/javadoc/latest/org/junit/Assert.html

pgTap: https ://pgtap.org/

utPLSQL: http : //utplsql.org/

MyTap: https//github.com/hepabolu/mytap

tSQLt Google网上论坛: https ://groups.google.com/forum/#!forum / tsqlt

tSQLt官方网站: https ://tsqlt.org/

tSQLt GitHub: https//github.com/tSQLt-org/tSQLt

Google趋势: https//bit.ly/2x7BQL6

使用tSQLt测试时如何回滚事务: https ://stackoverflow.com/questions/8973138/how-to-rollback-a-transaction-when-testing-using-tsqlt

相对于自动单元测试,手动单元测试的优缺点是什么?: https//stackoverflow.com/questions/2948337/what-are-the-pros-and-cons-of-manual-unit-testing-against自动化单元#2948354

善良,坏人和使用者: https ://sqlquantumleap.com/2017/08/07/sqlclr-vs-sql-server-2017-part-1-clr-strict-security/

Rex Black,Erik Van Veenendal,Dorothy Graham,软件测试基础,第三版,2012年Cengage Learning EMEA

Source: https://habr.com/ru/post/zh-CN465567/


All Articles