单元测试数据库脚本

接受在我最喜欢的C ++上使用单元测试的便利之后,我试图将自己的经验转移到TSQL,尤其是因为新雇主热衷于实地的一项有用计划并为此分发面包。

我浏览了几个著名的框架,得出的结论是,它们通常是笨重的,并带来了需要进一步研究的其他语法。

向他们展示了一些框架,它们工作得很漂亮,并且吸引了经理的注意,但是有一些我不喜欢的限制。

我想在纯洁清真-东正教TSQL上实现所有功能。

我不时地因磨练脚本的结构而分散了数年的主要开发工作,因此我决定与您共享该脚本(但仍然设法产生了3.5 Mb的脚本)。

我的基本要求很简单-我必须在文件中执行任何单元测试,而无需任何手势和特殊的软件工具-仅限于核心:sqlcmd或MSSMS。

不会对执行测试的数据库进行任何更改-所有内容都会回滚到脚本的开头。

只有一个限制:测试应该在空数据库中工作(可能是初始数据),否则您会厌倦拆卸所有选项。

主要任务是测试逻辑并维护逻辑的完整性。

为此,我将以下标题放在测试的开头:

SET QUOTED_IDENTIFIER ON GO PRINT '-------------------------------- CLR Unit tests for Habr Logic ---------------------------------' IF 0 < ( SELECT count(*) FROM device) begin RAISERROR ('FAILED: database must be empty for this unit test', 16, -1 ) end GO 

我尝试创建单元测试的时间不要超过几个屏幕,尽管在复杂逻辑的情况下这并不容易。

一个典型的单元测试如下所示,它包含三个关键部分:

 BEGIN TRAN TestClr2 declare @test_name sysname = (select TOP 1 name from sys.dm_tran_active_transactions WHERE transaction_type = 1 ORDER BY transaction_begin_time DESC) + ' [fn_calculate_dev_status] record for device has wrong range' BEGIN TRY SET NOCOUNT ON; -- 1. prepare data for unit test insert into device (mli, oxygen, stamp ) values ('111', 5.55, getdate() ) -- 2. execute unit test -- SELECT dbo.fn_calculate_dev_status( 111, 0.1, 1.2) declare @result int = ( SELECT dbo.fn_calculate_dev_status( '111', 0.1, 1.2) ) END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_SEVERITY() AS ErrorSeverity, ERROR_STATE() AS ErrorState , @test_name AS ErrorProcedure, ERROR_LINE() AS ErrorLine, ERROR_MESSAGE() AS ErrorMessage END CATCH -- 3. result verification IF @result <> 0 RAISERROR ('FAILED: %s no data for device should be presented %d ', 16, -1, @test_name, @result ) ELSE print 'PASSED ' + @test_name ROLLBACK TRAN TestClr2 GO 

-1.准备用于单元测试的数据

在这里,我们可以用数据填充必要的表,并准备一些临时变量或表,以免使测试部分中的代码混乱。

-2.执行单元测试

如果我们测试触发逻辑的话,通常是函数调用,过程或表更改。

-3.结果验证

在测试的这一部分中,我们检查数据库对象的状态如何改变,或者测试功能过程的结果。

如果过程函数返回一条记录,则我们将其插入临时表并已对其进行分析。

将汇总和准备的结果与标准进行比较,如果其他所有方法均失败,则抛出异常。

使用Oracle,一切都变得有些复杂-我无法以那种形式和相同的意识形态来编写和运行测试,而只是出于一点经验-我们不再为产品支持Oracle。

每个单元测试均按以下程序发布:

 CREATE OR REPLACE PROCEDURE UnitTest9_TRG_JOBLOGDETAIL AS v_message VARCHAR2(255) := 'UnitTest9_TRG_JOBLOGDETAIL: INSERT joblogdetail]- joblogdetail_result not Failed and joblogdetail_endtime is null '; v_maxdate date := '2014/01/01'; v_cnt NUMBER := 0; BEGIN savepoint my_savepoint; <b>-- 1. prepare data for unit test</b> insert into device ( dev_datecreated, dev_create_user, dev_ipaddress, dev_serialnumber , dev_productid, dev_manufacturer, dev_model, dev_id, dev_status, dev_functions) values (sysdate, 'Joe', '1.127.0.1', 'GSN-6238-N34', 'PRTF-452', 'Pinter Company', 'CM6003', 1, 1, 1 ); insert into joblog (JOBLOG_ID, joblog_starttime, joblog_progress) values (11, sysdate, 1); insert into joblog_template (JOBLOG_TEMPLATE_ID, joblog_id, joblog_templatename, joblog_templatetype) values (111, 11, N'joblog_template_test', 1); <b>-- 2. execute unit test</b> insert into joblogdetail ( JOBLOGDETAIL_ID, joblog_template_id, joblogdetail_function, joblogdetail_functiondetail, joblogdetail_result, joblogdetail_dev_id, joblogdetail_starttime, joblogdetail_endtime) values ( 1111, 111, 1, 1, 40, 1, v_maxdate, v_maxdate); <b>-- 3. result verification</b> SELECT count(dev_id) INTO v_cnt FROM device where dev_last_comm_time = v_maxdate; IF 1 <> v_cnt THEN DBMS_OUTPUT.PUT_LINE( 'FAILED: ' || v_message || ': Should not be update dev_last_comm_time: ' || TO_CHAR(v_maxdate)); ELSE DBMS_OUTPUT.PUT_LINE( 'PASSED: ' || v_message ); END IF; rollback to my_savepoint; END; / 

最后,在同一测试文件中,您将必须从创建和执行的测试中进行数据库清除。

 commit; / set serveroutput on; SET FEEDBACK OFF; spool C:\dist\test.spl; exec UnitTest_empty_database; exec UnitTest3297_TRGBFR_UDEVICE(1); exec UnitTest5_TRG_BF_UDEVICE; exec UnitTest_3062a; ... spool off; / DROP PROCEDURE UnitTest_3062; DROP PROCEDURE UnitTest_BIRDIESEC_3344; DROP PROCEDURE UnitTest_empty_database; ... SET FEEDBACK ON; commit; 

仅此而已。

然后,您只需生成文件,将其分为以下类型的类别:触发器,函数,过程,报告,业务逻辑的大型和特殊对象,当然还有每个数据库对象。

几乎所有的数据库开发人员都皱着眉头说-为什么我应该让测试人员这样做呢? 如果数据库根本没有单词的逻辑,那么我同意它们,但是如果数据库中有很多逻辑,那么它们自然就可以省去神经,声誉和金钱。

一个例子。

在Web界面中,我们在树对象(例如,美国->加拿大->安大略->亚洲滑铁卢->日本->东京-> Ebina)之间建立了逻辑连接树,即整个地理办公室。

每个这样的节点都有非常复杂的规则,用户或规则或生成器会分配设备。

结果,即使在步骤中详细描述的指令也会破坏每个人,甚至是那些参与讨论和开发此逻辑的人。

带有不同数据集的指令的五十多个步骤-详细记录了所有内容。

逻辑上的任何更改或补充都是手动验证的时钟,没有任何故障。
死亡重构类似。

在用单元测试介绍了逻辑之后,一切都由Silk进行了检查,并且我确定一切都可以正常工作。

任何运行Java的Java开发人员都可以通过运行适当的测试来轻松地摆弄雷电(思考自己弯曲的手)。

几分钟,每个人都很满意。 在我不在的情况下,任何致命的代码更改都会通过邮件快速向我报告。

自然,作为一个懒惰的人,我决定自动化Continuous Automation的所有内容,并用批处理和python编写粥。

我要求您稍微执行一下,在每天要发展的十几种语言和环境中,您必须抽空来舔所有的东西并将它们放在专业的外观中,这是灾难性的。

我不想在Windows Powershell上做任何事情-我们的跳过程序仍然在嵌入式Windows95上到处运行。

我想用Python进行所有调用,但事实证明,不仅在python库中,而且在.NET中均不支持某些sql(在cte中进行XML解析)构造,因此我通过sqlcmd进行了脚本编写。

代码在这里发布。

要运行一个有效的示例,只需编辑2个文件:smtppart.py和config.ini-SMTP服务器名称,端口和电子邮件,将在其中放置错误消息。

脚本首先尝试从svn获取最新更新(替换为您自己的-git,perforce等)。

然后从具有随机名称的脚本创建一个干净的基础,在其中启动单元测试,然后删除该基础。

创建一个包含80 Mb脚本和3.5 Mb测试的数据库(该方案的主要部分已经在我加入公司之前完成,因此我仅测试了自己的一部分)在大约15分钟的时间内在我的计算机上完成。 在最后提交之前,我只是有时间喝一杯咖啡。

如果有错误,则错误结果将发送到电子邮件。

依赖项安装在文件readme.txt中进行了描述

每次更改代码后,您都必须在config.ini文件中手动设置代码哈希(将在命令行中看到)-即使代码已更改且没有任何损坏,消息仍会出现-因此我可以控制代码中的更改,以便可以检查更改而无需我以前的参与。



可以将autorun.bat文件中所有单元测试的启动放置在Windows Task Scheduler中,以在公司组建之前一天或在离开家后一天运行1-2次-如果晚上出现故障-您可以查看电视前发生的情况并迅速进行修复。

我知道在单元测试中,最困难的是设置所有内容,然后编写测试很容易,虽然这可能很困难,但很必要。 祝您测试顺利,希望我的建议对您有所帮助。

如果某些地方可以改进并梳理代码,我会很乐意提意见,请不要严格判断。

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


All Articles