我已经考虑这个问题很久了。
基本问题是:如何对存储过程进行单元测试?
我看到我可以相对容易地为经典意义上的函数设置单元测试(我的意思是它们得到零个或多个参数并返回一个值)。但是,如果我考虑一个看似简单的过程在某处插入一行的真实示例,并且在插入之前或之后有几个触发器执行此操作,那么即使定义“单元”的边界也非常困难。我应该只测试INSERT
它本身吗?我认为这相当简单——价值相对较低。我应该测试整个事件链的结果吗?除了这是否是单元测试的问题之外,设计一个合适的测试可能是一项相当艰巨的工作,其中会出现许多额外的问号。
然后是不断变化的数据的问题。在UPDATE
影响不止几行的情况下,每个可能受影响的行都必须以某种方式包含在测试用例中。s等的进一步困难DELETE
等等。
那么如何对存储过程进行单元测试呢?复杂性是否存在完全绝望的临界点?维护需要哪些资源?
编辑另一个小问题,基于 AlexKuznetsov 的回答:或者是否有一个门槛,在它之下它是完全没用的?
我们已经这样做了快五年了,我们认为明确地测试修改绝对是可行的,但是速度很慢。此外,除非我们使用单独的数据库,否则我们不能轻松地从多个连接同时运行此类测试。相反,我们应该隐式地测试修改——我们使用它们来构建至少一些测试数据,并验证我们的选择是否返回了预期的结果。
我写了一篇题为“关闭那些漏洞:从单元测试 T-SQL 中吸取的教训”的文章,以及一些博客文章
关于您的问题“复杂性是否存在完全没有希望的门槛?”,复杂的模块比简单的模块更需要测试。
为了简化维护,我们生成了预期的结果,并将它们存储在单独的文件中——这会产生巨大的差异。
是的,您应该将整个事件链作为一个单元进行测试。因此,在您的示例中,使用插入表并导致触发多个触发器的过程,您应该编写单元测试来评估各种输入的过程。每个单元测试应该通过或失败取决于它是否返回正确的值、正确地更改表的状态、创建正确的电子邮件,甚至发送正确的网络数据包(如果它旨在执行此类操作)。简而言之,该单元的每一个效果都应该得到验证。
你是对的,设计单元测试需要一些工作,但是大部分工作必须手动测试单元,你只是保存测试单元所需的工作,以便将来进行更改时测试可以同样彻底且容易得多。
更改数据确实使测试变得更加困难,但它并没有使测试变得不那么重要,并且实际上增加了单元测试的价值,因为大多数困难只需考虑一次,而不是每次对单元进行更改时。作为设置/拆卸的一部分的已保存数据集、插入/更新/删除以及范围狭窄的操作都可以用来简化此操作。由于问题不是特定于数据库的,因此细节会有所不同。
高端或低端没有复杂性阈值会阻止您进行测试或单元测试。考虑以下问题:
假设您开始一项新工作,并负责对许多地方使用的小功能进行优化。整个应用程序是由一名甚至没有人记得的员工编写和维护的。这些单元有描述正常预期行为的文档,但几乎没有其他内容。您更愿意找到其中的哪一个?
对于 PostgreSQL,请查看pgTAP:
如果您希望完全在 SQL 上完成对存储过程的测试,请查看http://tsqlt.org/
它与 MS SQL 2005 SP2 及更高版本兼容,优点是开发人员无需了解 C# 或其他语言即可实现测试。
还有一些工具可以做模拟表和视图,以帮助您实现可重新运行的测试套件。
实际上,与以预期方式运行存储过程或函数然后检查结果的实际功能测试的价值相比,SQL 中的“单元测试”几乎没有价值。通常,这意味着在接近生产的数据和系统上运行测试。
单元测试可以轻松掩盖逻辑中的问题。对于非常简单的过程和函数,它们的逻辑很少,并且不是跨数据库的,单元测试就可以了。金标准应该是功能测试。例如,我很难为处理复杂编排测试的作业编写单元测试,例如跨多个数据库同步用户安全性。
归根结底,单元测试会创建大量代码 - 必须维护的代码 - 并且不会提供与实际测试一个过程或一系列过程的正确结果的功能测试相同的价值。
编写功能测试也会创建必须维护的代码,但也会提供大多数开发人员和 DBA 都能理解的代码。无意冒犯,但 t-sqlt 使用起来不是那么直观,并且在跨数据库过程中存在缺点 - 导致生成更多代码的缺点(必须维护的代码)。
代码越少越好。更有影响力的代码更好。单元测试在开发世界中很流行,并且已经渗透到 SQL 开发世界中,但最终如果您将用户添加到数据库的 SQL 过程出现问题,那么为它编写一个不会的单元测试是很容易的揭示破损。
严格的功能测试是最好的,用于处理业务关键数据的工作的集成测试是必要的。