如果您像我一样,则可能已经花费了很多时间在查询分析器中开发代码。在您对代码感到满意之后,可以立即对开发上的测试上的测试运行一个或两个专设 测试。如果看起来没有什么问题,您便可以将代码投入生产。如果这是一段关键代码,或者该代码较为复杂,则您可能会执行更多的检查,以避免后验剖析。甚至在这种情况下,您也可能屏息以待。
这就是我在大部分职业生涯中所采用的编码方式。哦,有时我会存储测试查询以供将来使用,这通常是因为总裁/CEO/CIO/部门经理习惯于大约每周就改变一下他或她的要求。但是,除此以外,我不会再做什么。我通常在查询分析器或它的 Oracle/Access/FoxPro 等效工具外部用专设 查询进行测试。更高强度的测试需要使用查询分析器调试器。在绝望的情形下,需要使用 PRINT 语句。
目前存在 一种更好的方式。
超越专设 测试
当我的 SIL 部门采用极限编程 (XP) 时,我们还采用了该方法论的单元测试部分,而它们两者都使我成为更出色的开发人员。但是,即使您不在 XP 环境中工作,您仍然可以从 XP 风格的单元测试中获益。
单元测试不同于接受测试。单元测试用于测试较小的代码块(例如,存储过程),而接受测试更多地涉及到用户是否可以接受 UI。以下是我发现的单元测试的五个优点:
• | 它们能够找出应该承担责任的当事人。您是否收到过电子邮件,告诉您应该修复错误,而这实际上是其他某个人所作更改的副作用?好,如果您具有一些零散的测试查询,请将它们包装到可以定期运行(或许是在晚上)的存储过程中。请确保在单元测试失败时能够生成电子邮件。 |
• | ;生成库不需要花费很长时间。每个存储过程和每个存储函数都应当具有为它编写的测试,而触发器也应该如此。如果这听起来有些苛刻,那么请想一想,能够在问题到达生产之前捕捉到它,从而拯救您自己,将会是一种多么好的感觉。如果您具有大量旧式代码,那么为每个单元编写测试可能需要多年的工作,并且您也不能仅仅为了编写测试而停止新的开发工作。但是,您可以为每段新代码编写测试,也可以为您修改的每个过程编写测试。用不了多长时间,您就会为关键的旧式代码和新代码编写众多的测试。 |
• | 轻松创建准确的代码文档。每个过程或函数都应当用不同的参数组合调用。这不仅能够确保代码按预期方式工作,而且还提供了有关您的工作的最新而准确的文档。另外一个编码员只需查看您的测试,就可以了解对您的过程进行调用的示例。谁知道呢?某一天,这另一个编码员可能就是您自己。 |
• | 它们迫使您预先进行一点儿思考和计划。您应当在编写实际的过程或函数之前编写自己的单元测试。“什么?”您说,“我抗议!我们如何为尚未进行编码的东西编写测试?” |
• | 有一个很老的笑话,它讲的是:有一个经理说:“我将弄清楚他们需要什么。其余的人开始编码。”那么,编码员在知道他们需要编写什么之前是无法开始工作的,不是吗?当您首先编写测试时,您将被迫考虑在开始编写该过程之前,您希望该过程完成什么工作。 |
• | 它们确实可以节省您的时间。开发人员经常抱怨,编写测试需要花费比编写实际过程更多的时间。有时的确如此。但是请考虑以下情况:我最近接受了一项任务,即,修改我曾经遇到过的最难的存储过程之一。它是旧式代码,但是我仍然首先编写了测试。它花费了我几天的时间才完成,部分原因在于对该过程所施加的要求。实践证明,出于我刚才列出的所有原因,该测试非常重要,并且当我必须重新编写该过程以改善性能时,它变得弥足珍贵。 |
• | 单元测试显示重新编写的过程中存在大量错误,而我能够很快地找到每个错误的根源,所花费的时间只占不使用单元测试时的几分之一。然后,当我认为已经完成该任务时,模糊测试失败了。主循环中的变量之一存在缺陷。如果代码以这种状态发布到生产环境中,那么这将是一个难以捕获的错误。最终,我以比采用其他方式更快的速度完成了这项任务。 |
如何编写 T-SQL 单元测试
在我告诉您有关 T-SQL 测试框架的内容之前,首先需要提醒您注意两个非常基本的原则:
• | 第一,您需要一个具有良好测试数据的。我用“良好数据”表示来自现实世界的真实数据。无论您是一个多么优秀的员,都无法充分地为应用仿造数据。即使要替换的旧式由纸张组成,也要找一位数据录入员来在某些表中输入数据。完成获得真实数据所需的工作。[尽管如此,仍然存在测试数据生成器。请参见本期中我的提示“生成测试数据”— 编者] |
• | 第二,不应当针对生产进行开发。您应当具有一个开发或测试,以便满足您自己的需要。过去,当我在 Oracle 进行开发时,我曾经花费了一周的时间将开发放在一个陈旧的上。SQL Server 开发人员没有这样的借口。 |
在为开发配备良好的数据以后,您需要某种框架以便运行测试。您可以编写自己的框架,但是为什么要这么做呢?已经有一个可用的框架了。
TSQLUnit 简介
TSQLUnit 是 T-SQL 的一个开放源码单元测试框架,它由 Henrik Ekelund 编写,并且可以从 unit" target="_blank">http://sourceforge.net/projects/tunit 获得。以下是一个有关我如何使用它的示例。
我的 TSQLUnit 测试采用了类似的三部分模式:1) 单元测试设置,2) 执行目标过程,和 3) 检查结果。
在单元测试设置过程中,我经常进行检查,以确保没有人趁我不注意时破坏了我的数据:
DECLARE @nId INT, @nNewId INT —- @nNewId is for laterSELECT @nId = [ID] FROM MyTableWHERE MyField = whateverIF @nId IS NULL -- or @@ROWCOUNT = 0EXEC tsu_failure The data has changed.whatever couldnt be found
IF 块用于检查预期的记录。如果找不到该记录,则测试会失败,并且会生成错误信息。测试框架移动至下一个单元测试。您不需要在失败消息串中使用该单元测试的名称,因为当测试失败时,TSQLUnit 将为您命名它。
现在,我调用将要编写的存储过程:
EXEC CreateMyTableNewRec @nId, @nNewId OUTPUT
正如您看到的那样,我已经确定了需要来自这一新过程的输出参数。在检查结果的过程中,我确保输出参数确实填充了某些内容:
IF @nNewId IS NULLEXEC tsu_failureA new record was not created for table MyTable.
我可以进一步检查该值,以查看新记录是否是按照我希望的方式创建的。
每个 TSQLUnit 测试本身都是一个存储过程。清单 1 显示了在将上述所有代码段放在一起时所具有的样子:
清单 1. T-SQL 的完整单元测试。
CREATE PROCEDURE ut_MyTable_NewRecAS--== Setup ==--DECLARE @nID INT, @nNewId INTSELECT @nId = ID FROM MyTableWHERE MyField = whateverIF @nId IS NULL -- or @@ROWCOUNT = 0EXEC tsu_failure The data has changed.Whatever couldnt be found--== Execute ==--EXEC CreateMyTableNewRec @nId, @nNewId OUTPUT--== Check ==--IF @nNewId IS NULLEXEC tsu_failure A new record was not createdfor table MyTable.GO
请注意存储过程的三部分名称 — ut_MyTable_NewRec。前缀“ut_”提醒 TSQLUnit 这是一个它应当运行的单元测试。如果您已经将前缀 ut_ 用于其他目的,则 TSQLUnit 允许您将它设置为其他内容。“MyTable”是相关单元测试组(称为测试组)的名称。例如,您可以添加另一个名为 ut_MyTable_DeleteRec 的单元测试。MyTable 组将同时对在 MyTable 中添加和删除记录进行测试。该组可以独立于其他测试组运行。该名称的第三部分 —“NewRec”或“DeleteRec”唯一地标识该单元测试。
请注意,您不需要在每个单元测试中使用 BEGIN TRAN 和 ROLLBACK;TSQLUnit 会为您处理该问题。
运行单元测试
为了运行清单 1 中的单元测试,您需要设置框架。从查询分析器中,对开发运行 tunit.。您只需要对该执行一次该操作。接下来,创建过程 ut_MyTable_NewRec(如果您尚未创建的话)。现在,您已经准备就绪了。只须执行以下单元测试:
-- This will run all tests for suite MyTable,EXEC tsu_RunTests MyTable
固定件
假设我希望使许多记录可供一个组中的所有 单元测试使用。我不希望为每个测试编写相同的设置代码。TSQLUnit 通过设置固定件 解决了这个问题。固定件中的代码将在每个单元测试之前运行。
例如,上述 MyTable 组的设置固定件将命名为 ut_MyTable_setup。该名称的第三部分“setup”提醒 TSQLUnit 将该过程视为该组的设置固定件。它将如下所示:
CREATE PROCEDURE ut_MyTable_setupASINSERT INTO MyTable ([Description])VALUES (something)--( more records inserted hereGO
SQL Server 社区非常感谢 Henrik Ekelund 和他的雇主使 TSQLUnit 成为开放源码。
链接到
unit" target="_blank">http://sourceforge.net/projects/tunit
链接到
unit.sourceforge.net/tunit_cookbook.htm" target="_blank">http://tunit.sourceforge.net/tunit_cookbook.htm (文档)
有关 SQL Server Professional 和 Pinnacle Publishing 的详细信息,请访问它们位于 http://www.pinpub.com/ 的 Web 站点。
注:这不是 Microsoft Corporation 的 Web 站点。Microsoft 对该 Web 站点的内容不承担任何责任。
本文是从 SQL Server Professional 2004 年 9 月刊转载的。版权所有 2004,Pinnacle Publishing, Inc.(除非另行说明)。保留所有权利。SQL Server Professional 是 Pinnacle Publishing, Inc. 独立发行的产品。未经 Pinnacle Publishing, Inc. 事先同意,不得以任何形式使用或复制本文的任何部分(评论文章中的简短引用除外)。要联系 Pinnacle Publishing, Inc.,请致电 1-800-788-1900。