测试 SQL 查询的最佳方法 [关闭]

Posted

技术标签:

【中文标题】测试 SQL 查询的最佳方法 [关闭]【英文标题】:Best way to test SQL queries [closed] 【发布时间】:2010-10-19 18:21:36 【问题描述】:

我遇到了一个问题,我们不断有复杂的 SQL 查询出错。从本质上讲,这会导致向错误的客户发送邮件以及其他类似的“问题”。

每个人在创建这样的 SQL 查询方面都有什么经验?我们每隔一周创建新的数据群组。

以下是我的一些想法和对它们的限制:

创建测试数据虽然这可以证明我们拥有所有正确的数据,但它不会强制排除生产中的异常。这些数据在今天被认为是错误的,但在 10 年前可能是正确的;它没有记录在案,因此我们只有在提取数据后才知道。

创建维恩图和数据图 这似乎是测试查询设计的可靠方法,但它不能保证实现正确。它让开发人员提前计划并在编写时思考正在发生的事情。

感谢您对我的问题提出的任何意见。

【问题讨论】:

【参考方案1】:

您不会编写具有 200 行长的函数的应用程序。您可以将这些长函数分解为更小的函数,每个函数都有一个明确定义的职责。

为什么要这样写你的 SQL?

分解查询,就像分解函数一样。这使得它们更短、更简单、更容易理解、更容易测试、更容易重构。它允许您在它们之间添加“垫片”,并在它们周围添加“包装器”,就像在过程代码中一样。

你是怎么做到的?通过将查询所做的每一件重要的事情变成一个视图。然后,您从这些更简单的视图中组合更复杂的查询,就像您从更原始的函数中组合更复杂的函数一样。

最棒的是,对于大多数视图组合,您将从 RDBMS 中获得完全相同的性能。 (对于有些人你不会;那又怎样?过早的优化是万恶之源。首先正确编码,然后在需要时进行优化。)

Here's an example of using several view to decompose a complicated query.

在示例中,由于每个视图只添加了一个变换,因此每个视图都可以独立测试以发现错误,并且测试简单。

这是示例中的基表:

create table month_value( 
    eid int not null, month int, year int,  value int );

这个表是有缺陷的,因为它使用两列,月和年,来表示一个数据,一个绝对月份。这是我们对新的计算列的规范:

我们将把它作为一个线性变换来做,它的排序与(年,月)相同,并且对于任何(年,月)元组,都有一个唯一的值,并且所有的值都是连续的:

create view cm_absolute_month as 
select *, year * 12 + month as absolute_month from month_value;

现在我们必须测试的是我们的规范中固有的,即对于任何元组(年、月),只有一个(absolute_month),并且(absolute_month)是连续的。让我们写一些测试。

我们的测试将是一个 SQL select 查询,具有以下结构:测试名称和 case 语句连接在一起。测试名称只是一个任意字符串。 case语句只是case when测试语句then 'passed' else 'failed' end

测试语句将只是 SQL 选择(子查询),必须为真才能通过测试。

这是我们的第一个测试:

--a select statement that catenates the test name and the case statement
select concat( 
-- the test name
'For every (year, month) there is one and only one (absolute_month): ', 
-- the case statement
   case when 
-- one or more subqueries
-- in this case, an expected value and an actual value 
-- that must be equal for the test to pass
  ( select count(distinct year, month) from month_value) 
  --expected value,
  = ( select count(distinct absolute_month) from cm_absolute_month)  
  -- actual value
  -- the then and else branches of the case statement
  then 'passed' else 'failed' end
  -- close the concat function and terminate the query 
  ); 
  -- test result.

运行该查询会产生以下结果:For every (year, month) there is one and only one (absolute_month): passed

只要month_value中有足够的测试数据,这个测试就有效。

我们也可以为足够的测试数据添加测试:

select concat( 'Sufficient and sufficiently varied month_value test data: ',
   case when 
      ( select count(distinct year, month) from month_value) > 10
  and ( select count(distinct year) from month_value) > 3
  and ... more tests 
  then 'passed' else 'failed' end );

现在让我们测试它是连续的:

select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b 
on (     (a.month + 1 = b.month and a.year = b.year) 
      or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )  
where a.absolute_month + 1 <> b.absolute_month ) = 0 
then 'passed' else 'failed' end );

现在让我们将我们的测试(只是查询)放入一个文件中,然后针对数据库运行该脚本。事实上,如果我们将视图定义存储在一个脚本(或多个脚本,我建议每个相关视图一个文件)中以针对数据库运行,我们可以将每个视图的测试添加到 same 脚本中,这样(重新)创建我们的视图的行为也会运行视图的测试。这样,当我们重新创建视图时,我们都会得到回归测试,并且当视图创建运行在生产环境中时,视图也将在生产环境中进行测试。

【讨论】:

这是我第一次在 sql 中看到干净的代码和单元测试,我很高兴 :) 这很好,但是为什么要为列使用一个字母的名称,而视图名称几乎看不懂呢?为什么 SQL 的自文档化或可读性不如 Python? 对我在 SQL/DB 世界中从未见过的有用的东西做出了很棒的解释。我也喜欢你在这里测试数据库的方式。 作为一个警告,我已经看到加入 sql 视图的 sql 视图在 PostgreSQL 上的性能很差。不过,我已经在 M$ SQL 中有效地使用了这种技术。 @BenLiyanage 你知道这个事实吗?您能否提供任何示例或指示。我真的很想探讨这个话题!【参考方案2】:

您可能想检查DbUnit,因此您可以尝试使用一组固定数据为您的程序编写单元测试。这样,您应该能够编写具有或多或少可预测结果的查询。

您可能想要做的另一件事是分析您的 SQL Server 执行堆栈并确定所有查询是否确实是正确的,例如,如果您只使用一个返回正确和错误结果的查询,那么显然正在使用的查询有问题,但是如果您的应用程序在代码的不同点发送不同的查询怎么办?

那么任何修复您的查询的尝试都是徒劳的......无论如何,流氓查询可能仍然是引发错误结果的那些。

【讨论】:

【参考方案3】:

回复:tpdi

case when ( select count(*) from cm_abs_month a join cm_abs_month b  
on (( a.m + 1 = b.m and a.y = b.y) or (a.m = 12 and b.m = 1 and a.y + 1 = b.y) )   
where a.am + 1 <> b.am ) = 0  

请注意,这只检查连续月份的 am 值是否连续,而不是连续数据是否存在(这可能是您最初想要的)。如果您的源数据都不是连续的(例如,您只有偶数月份),这将始终通过,即使您的 am 计算完全关闭。

我还遗漏了什么,还是那个 ON 子句的后半部分会影响错误的月份值? (即检查 12/2011 是否在 1/2010 之后)

更糟糕的是,如果我没记错的话,SQL Server 至少允许您查看不到 10 级的视图,然后优化器将其虚拟双手抛向空中并开始对每个请求进行全表扫描,所以不要过度这种方法。

记住要测试你的测试用例!

否则,创建一个非常广泛的数据集以包含大多数或所有可能的输入形式,使用 SqlUnit 或 DbUnit 或任何其他 *Unit 自动检查针对该数据的预期结果,并查看、维护和更新它必要时通常似乎是要走的路。

【讨论】:

【参考方案4】:

创建一个测试系统数据库,您可以根据需要随时重新加载。加载您的数据或创建您的数据并将其保存。产生一个简单的方法来重新加载它。将您的开发系统附加到该数据库并在投入生产之前验证您的代码。每次你设法让问题投入生产时,都要踢自己。创建一套测试来验证已知问题并随着时间的推移扩展您的测试套件。

【讨论】:

以上是关于测试 SQL 查询的最佳方法 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

使用 GoogleTest 测试私有方法的最佳方法是啥? [关闭]

测试 REST API 的最佳方法? [关闭]

对 Rails Web 应用程序进行压力测试的最佳方法? [关闭]

对 ASP.NET 2.0 网页进行单元测试的最佳方法是啥? [关闭]

哪种是对 Spring Boot Rest API 进行端到端测试的最佳方法? [关闭]

SQL测试题(注:最佳答案必须能在MySQL下运行)