单元测试在JavaScript和SQL编程中的应用

在过去的20年中,编程方式发生了巨大的变化。无论是否进行单元测试,很可能已经认识到了单元测试的好处。在某些项目中,遵循测试驱动开发(TDD)的方法,在其他项目中,在编写代码后编写单元测试,而在其他项目中,根本不进行单元测试。思考项目的性质,因此也是要求,一个很好的起点是考虑Joel Spolsky的“软件开发的五个世界”。

虽然单元测试在软件行业中普遍变得更加流行,但有两种非常重要的编程类型通常不太可能被单元测试,如果随机选择一个软件项目的话。这些是JavaScript编程和SQL编程。然而,JavaScript和SQL通常(但并非总是)可以从单元测试中受益。

JavaScript现在得到了越来越多的单元测试框架的支持(使用Jasmine),这些框架旨在解决客户端单元测试带来的挑战。这一趋势在很大程度上是由于对单页应用程序和响应式用户界面的需求增加,以及Node.js作为服务器端语言的受欢迎程度的增长。

对于SQL程序员来说,可供选择的框架确实更少。一个原因是,虽然客户端逻辑变得更加复杂,但SQL逻辑变得不那么复杂。ORM框架如nHibernate和Entity Framework在许多应用程序中消除了复杂存储过程和函数的需求。

当然也有例外。有时,出于某种原因,无法避免需要非平凡的SQL编程。在这些情况下,SQL单元测试可以帮助。最近一直在使用的一个SQL单元测试框架是tSQLt,这是一个简单的开源框架。

使用tSQLt进行SQL单元测试包括通常构成单元测试的四个步骤:设置、执行、验证和清理。将这个过程应用到SQL的一个潜在困难是,在步骤1中创建或操作的任何表自然会被持久化,因此在单元测试完成后会留下。显而易见的解决方案是在“清理”步骤中清理数据,但这将很快导致单元测试过于复杂。tSQLt通过隐式地将每个单元测试包装在一个事务中来解决这个问题,该事务在完成后回滚。这基本上消除了担心清理步骤的需要,因为它是自动处理的。

SQL单元测试的另一个常见困难是,“设置”阶段可以很快成为一个重要的任务。外键约束通常意味着为了将一些测试数据插入一个表,必须插入到它依赖的许多其他表中。一个可能的解决方案是使用一个完全填充的数据库进行单元测试。然而,这是不可取的,因为它导致测试在一个不一致的环境中运行,以及随之而来的可预测性成本。一个更好的方法是始终在空数据库上运行单元测试,并在每个测试后恢复到该空状态,静态表包含查找数据的例外。tSQLt通过“假”表帮助实现这个目标。对于需要插入测试数据的每个表,可以运行FakeTable过程,这有效地在测试期间移除了该表的任何约束。为了进一步隔离正在测试的单元,也可以伪造任何被调用的函数,通过定义简单的模拟版本,它们返回想要的任何内容。

验证是通过使用tSQLt定义的许多Assert函数之一来执行的。这些包括AssertEmptyTable、AssertEquals等。

让考虑一个可能想要编写SQL单元测试的简单场景。正在测试的数据库是电子商务应用程序的一部分。用户将商品添加到购物篮,然后结账以完成购买。将测试的单元是一个名为CompletePurchase的存储过程。数据库包括User、Basket和CompletedPurchase表,以及一个IsValidBasket函数。

CREATE TABLE [dbo].[User]( [UserID] [int] NOT NULL, [Username] [varchar](50) NOT NULL, CONSTRAINT [PK_Usera] PRIMARY KEY CLUSTERED ( [UserID] ASC )) CREATE TABLE [dbo].[Basket]( [BasketID] [int] NOT NULL, [UserID] [int] NULL, CONSTRAINT [PK_Basket] PRIMARY KEY CLUSTERED ( [BasketID] ASC )) GO ALTER TABLE [dbo].[Basket] WITH CHECK ADD CONSTRAINT [FK_Basket_User] FOREIGN KEY ([UserID]) REFERENCES [dbo].[User] ([UserID]) GO CREATE TABLE [dbo].[CompletedPurchase]( [CompletedPurchaseID] [int] IDENTITY(1,1) NOT NULL, [BasketID] [int] NOT NULL, [PurchaseTimestamp] [datetime] NOT NULL) GO CREATE FUNCTION [dbo].[IsValidBasket]( @BasketID int ) RETURNS bit AS BEGIN DECLARE @IsValid bit -- do stuff here to check values in various tables and set @IsValid RETURN @IsValid END GO CREATE PROCEDURE [dbo].[CompletePurchase] @BasketID int AS BEGIN IF [dbo].[IsValidBasket](@BasketID) = 1 INSERT INTO [dbo].[CompletedPurchase] (BasketID, PurchaseTimestamp) VALUES (@BasketID, GetDate()) END GO

现在可以开始编写单元测试。假设已经为单元测试套件创建了一个空数据库。需要向Basket表插入一行测试数据。这个表依赖于User表,所以将使用FakeTable命令来指示tSQLt忽略其约束。

EXEC tSQLt.FakeTable 'dbo.Basket' INSERT INTO [dbo].[Basket] (BasketID, UserID) VALUES (100, 123)

将假设IsValidBasket函数涉及许多其他表,因此将创建一个简单的模拟版本,它只返回true。

CREATE FUNCTION [dbo].[IsValidBasket_FakeReturnTrue](@BasketID int) RETURNS bit AS BEGIN RETURN 1 END GO

现在可以使用FakeFunction命令来告诉tSQLt使用这个函数而不是IsValidBasket。

EXEC tSQLt.FakeFunction 'dbo.IsValidBasket', 'dbo.IsValidBasket_FakeReturnTrue'

设置好数据后,现在可以执行存储过程,并验证结果。总的来说,单元测试看起来像这样:

CREATE PROCEDURE [dbo].[test CompletePurchase] AS BEGIN EXEC tSQLt.FakeTable 'dbo.Basket' INSERT INTO [dbo].[Basket] (BasketID, UserID) VALUES (100, 123) EXEC tSQLt.FakeFunction 'dbo.IsValidBasket', 'dbo.IsValidBasket_FakeReturnTrue' EXEC tSQLt.AssertEmptyTable 'CompletedPurchase'; -- check the table is initially empty EXEC [dbo].[CompletePurchase] 100 -- run the stored procedure DECLARE @RowCount int SELECT @RowCount = COUNT(*) FROM CompletedPurchase EXEC tSQLt.assertEquals 1, @RowCount -- check the table contains exactly 1 row SELECT @RowCount = COUNT(*) FROM CompletedPurchase WHERE BasketID = 100 AND PurchaseTimestamp > DATEADD(s,-5,PurchaseTimestamp) -- check that the row contains the correct data EXEC tSQLt.assertEquals 1, @RowCount END GO EXEC tSQLt.Run '[dbo].[test CompletePurchase]' +----------------------+ |Test Execution Summary| +----------------------+ |No|Test Case Name |Result | +--+-----------------------------+-------+ |1 |[dbo].[test CompletePurchase]|Success| ----------------------------------------------------------------------------- Test Case Summary: 1 test case(s) executed, 1 succeeded, 0 failed, 0 errored. -----------------------------------------------------------------------------
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485