现在的位置: 首页 > 数据库 > 正文

关系数据库测试驱动开发

2019年06月18日 数据库 ⁄ 共 7156字 ⁄ 字号 评论关闭

最近要交个作业,翻译了篇Scott W. Ambler的论文,贴出来给大家拍拍砖

附上原文

------------------------------------------------------------------------------------

<!----><!----><!----><!----> <!---->

关系数据库测试驱动开发

在测试先行开发( TFD )中,开发人员通过迭代的方式对系统进行详细设计和实现,并对其进行测试。测试驱动开发(TDD )将测试先行开发和重构(开发人员对代码进行小步修改以改善其设计但不改变代码的语义)结合。程序员使用测试驱动开发来实现一个新功能时,首先要了解当前的设计对于增加这个新功能来说,是否是最方便的。如果确定,使用测试先行开发方式实现该功能;否则,先重构代码然后再使用 TFD 。从行为驱动开发方法到 TDD ,命名习惯和术语都使程序员的首要目标更清晰:描述而不是验证系统。

       数据库测试驱动设计将 TDD 应用到数据库开发中。在数据库测试驱动( TDDD )开发中,开发人员通过关系数据库实现系统关键行为(包括常量数据的业务规则,如“只有红,蓝,紫才是有效的颜色”)和业务功能。对于数据库,我们通过数据库测试来定义数据库的行为,如同通过测试来定义应用程序代码功能一样。关系数据库测试驱动开发不是一个独立的活动,可以把它简单地理解为针对数据库层面的 TDD 。这意味着,在开发应用程序时应保持对数据库 schema 的同步开发,你会时而写应用程序测试来定义应用代码行为,时而写数据库测试指定数据库行为。

       使用 TDDD 非常重要,因为:

首先,所有 TDD 的优点都适用与 TDDD :你可以小步而安全的前进;通过重构使系统在整个生命周期中保持高质量的设计;回归测试让你能尽早发现缺陷; TDDD 促使你能时刻获得一个最新的可执行的系统(而不像传统的设计文档)。而且通过 TDDD,数据库开发成果可以有效的整合进整个系统开发中。 IT 社区长期忍受折磨正是由于开发人员和数据库专家之间文化不匹配。这种文化差异主要源于不同的哲学和工作方式。现代方法论,包括 RUP , XP , DSDM 都是演进式的(增量和迭代)。因此,这值得我们寻找能使数据库专家同开发人员一同使用演进方式工作的技术。 TDDD 正是这样一种技术。

扩展 TDD 到数据库开发

为了扩展 TDD 到数据库开发,我们需要对应的数据库回归测试,重构,持续集成等技术。

数据库回归测试

       对于数据库回归测试,需要经常的通过运行综合测试套件来验证数据库。理想状况下,任何时候改变了数据库 schema 或以一种新的方式访问数据库都需重新验证。如图 1 所示,数据库访问接口和数据库需同时测试。

<!----><!----> <!---->

接口测试。 通过数据库访问接口测试指定系统如何访问数据库。从数据库的角度来看,这种属黑盒测试。一个这种测试可能会指定提交的数据库的硬编码 SQL 语句;也可能指定:

■ 一个 SQL 语句返回期望结果

■ 一个视图产生期望结果

■ SQL 计算语句返回期望结果

■ 某一用户 ID 不能访问特定信息,或者

■ 一用户 ID 能访问特定信息

       访问接口测试非常容易实现,因为很多开发平台都有很好的工具,尤其是 xUnit 框架。商业工具也是可行的选择。我建议:使用与应用程序回归测试相同的工具

内部测试。 内部测试验证数据库所实现的行为和数据有效性。你可以针对 PL/SQL 代码写测试,正如可以对 Java 代码写测试一样,没有什么特别之处。据当前流行观点,数据是一种企业资产,因此,难道不应当验证你想存储的数据吗?从数据库的角度来看,这种属白盒测试。一个这种测试可能会定义:

■ 一个存储过程的某些行为

■ 对传入存储过程的参数有效性验证

■ 存储过程返回值的效性验证

■ 存储过程返回一个期望的错误码

■ 一个栏位只保存指定常量

■ 一个参照完整性规则

■ 一个栏位栏的默认值

数据库内部测试的工具较难实现,但这种状况正在改变。例如, Quest’s Qute 可验证 Oracle PLSQL 代码; Microsoft’s Visual Studio Team System for Database Professionals 包含了 SQL Server 的测试工具。

       测试实例。 下面的例子说明了如何做数据库回归测试。假设我们正在构建一个银行系统,包含两个功能: 1. 从数据库中获取帐户信息, 2. 保存帐户信息至数据库。同时也假设这是我们第一次遇到帐户的概念,而且我们正在实现这两个功能。对帐户查询,我们需运行几个数据库测试(一次一个),包括: shouldExistOneOrMoreAccounts-ForCustomer,

shouldExistOneOrMoreCustomersForAccount,

shouldHaveUniqueAccountID,

shouldHavePositiveAccountBalance

对保存帐户,我们增加两个新测试:

                     shouldBeAssignedUniqueAccountIDOnCreation

shouldBeAssignedZeroBalanceOnCreation

我们这里使用代码命名规范 ”should”, 一般 TDD 命名规范也同样适用(以测试案例以 ”Test” 开始)。

       使用 TDDD 方法使,我们最好一次写一个测试,然后演进数据库 schema 来满足测试定义的功能。例如,测试shouldHavePositiveAccountBalance 。假设这是我们第一次碰到帐户的概念,我们会增加 Account.Balance 这个栏位。根据我们的数据库设计规范,实现这个以数据为基础的业务规则还要增加一个栏位约束或更新触发器检查 Account.Balance 的值。无论使用哪种实现策略,关键是要验证数据库正确地支持业务规则。

       识别数据库测试没有什么特别之处,在该问题的其它相关文章和 TDD 相关书籍中都有很多好的建议。 TDDD 的主要区别是,我们必须认识到同时验证数据库所实现的功能和所包含的数据是非常重要的。数据库系统如同系统其他组件,必须被验证。

 

数据库重构

       重构是一个重新组织代码的严格的方法:通过小步修改改善代码的设计,而不改变其行为语义——即不删除也不增加行为。重构使你以一种安全、演进的方式时刻地改进你的代码。类似地,通过数据库重构,可以对数据库做小小的修改以改善其设计,同时保持其行为和信息语义。数据库包括结构化部分(表和视图),功能性部分(存储过程和触发器)和数据性部分(数据)。数据库重构包含众多方式,如删除一个不再使用的视图,移动一个栏位到更

合适的表,重命名栏位,分割多用途栏位,相似方法参数化。

       理论上来说,数据库重构比代码重构更困难,因为必须同时保证数据库的信息语义和行为语义不变。保留信息语义意味着对一个栏位数据值的改变不应对信息使用者有实质的影响。例如,有一个用字符格式保存的电话号码,如果将“ (416) 555-1212” 改为“ 4165551212” ,则没有实质性地改变信息语义,该栏位信息的使用者依然可以使用新格式的信息。但是,若改为“ (416) 555-5555” ,则很明显地改变了信息语义。类似地,要保持行为语义,则必须保持黑盒功能不变性。所有与数据库 schema 变更部分打交道的源代码都必须改造,从而实现与原来同样的功能。

       重构流程。 图 2 使用 UML 活动图描述了数据库重构的整体流程。

<!----><!----> <!---->

首先,建议开发人员在自己独立的开发环境中实现数据库重构,然后再提升到共享环境,如团队的集成整合环境中。若是有两个人结对完成这个工作就更好了,且至少有一人掌握敏捷数据库开发的技巧,更重要的熟悉目标数据库。

       重构第一步确认是否需要重构 schema ,若需要,然后选择合适的重构策略。例如,某人建议做“重命名栏位”的重构,但没有意识到当前的栏位名符合公司的命名规范;或者可能正真的问题不在于栏位名,而是该栏位在不恰当的表中,因此显得命名不当;或者在某些情况下重构的成本是不合算的。

       第二步是使目标 schema 过期。这一步是可选的;一些生产数据库供数十个甚至上百个系统访问,这些系统使用不同的语言,运行在不同平台,按不同的时间表发布。这种情况下,必须支持一个转换期 (transition window) ——在一些组织内可能会持续数年,在该期间现存的 schema 和重构版本并行运行。简单情形下,可直接同时更新和发布数据库 schema 和应用系统,而无需支持转换期。关键是能支持从简单到高度复杂系统,各种情形下实现数据库重构。

       下一步,写代码 ( 或使用数据库重构工具产生代码 ) 修改数据库。对于修改表结构的重构,需通过 DDL 增加新 schema ,以及 DML 来操作数据。为了保持数据同步,还需增加一些脚本——典型情况下是一个触发器,某些情形可更新视图也适合。对于数据质量重构,只需通过 DML 来解决数据质量问题。类似地,对于数据库方法重构,仅需修改存储过程代码。同时,还必须对工作产品进行测试;使用测试先行方法设计;每次系统整合时执行回归测试套件。当发现 Bug 时,及时修改;交替地执行编码和测试工作。

       每当完成一次重构,必须将工作产品置于配置管理中,并对所在团队和整个组织宣布此次重构。因为数据库是共享资源,数据库重构常常是对组织的一个挑战——须用一个高效的方式在个团队间沟通数据库变更。

       结构重构实例。

<!----><!----> <!---->

我们通过完成一个简单的结构重构——将一个栏位从一个表移动到另一表——来演示如何重构。假设,有超过 100 个应用系统访问一个关键任务数据库,我们不可能中断该数据库。图 3a 显示一个银行数据库 schema 的小部分。 Customer 表有一个Balance 栏位,该栏位本应属于 Account 表。这种设计现在变得难以支持业务逻辑了。

       图 3b 显示了转换期数据库 schema ,该转换期对于这种复杂情形是必需的。我们使用两年的转换期来确保所有开发团队能重构和测试使用新 schema 的代码。如图 3b 所示,我们不仅要增加新的 schema(Account 表 Balance 栏位 ) ,还要写触发器SynchronizeCustomerBalance 和 SynchronizeAccountBalance ——同步两个栏位的数据。在转换期中,我们假设:每一个应用系统只需与一种 schema 打交道,而不是两个版本的 schema 。换句话说,数据库必需负责保持它的一致性。

       如图 3b 所示,我们标识了数据同步脚本和原先 scheam 以及它们预期删除的日期。类似于 SUN 公司的 JDK 版本演进策略:增加新功能,废除 (deprecate) 旧功能以不再使用它。不同之处在于,这里删除日期表明开发团队重构应用代码的期限。而且,当到达该日期时,不应该自动删除旧的 schema 和脚本代码;而是首先应确定开发团队已成功更新和部署了应用系统。图3b 显示,重构完成后的数据库 schema 。

 

 

数据库持续集成

       在数据库持续集成中,团队成员持续地将各自对数据库的修改集成,包括结构性,功能性,数据性的修改,同时也包括相关的系统自动构建脚本及回归测试。理想状况下,至少每天集成一次。在任何情况下,无论何时某个开发人员提交了数据库变更,其他成员都应尽快从配置管理系统中更新以获得最新的变更,并整合到自己的数据库实例中。最好是能自动更新变更的代码并在你机器上重新构建系统,如 BuildForge,CruiseContrl 等工具。

       对于数据持续集成,有几条好的最佳实践:

       1. 构建过程自动化。数据库构建过程应该自动发现是否需应用某些变更到 schema ,并运行测试套件。数据库构建过程是整个应用系统构建过程的一部分。当前,数据库构建工具是个相对新的概念,但开源开发者在 ROR 开发已使用ActiveRecordMigration 和 dbdeploy.

       2. 对所有工作产品进行版本控制。数据模型,数据脚本,测试数据,类似的地工件都是重要的工作产品,因此必须进行管理。我的哲学很简单:值得创建的东西,就值得进行版本控制。

       3. 每个开发者拥有自己的数据库副本。开发人员必须首先在自己的沙盒中编写代码和修改数据库 schema 。代码和数据库都经过完整测试后,再提交到项目集成环境中。这减少了开发人员破坏构建的可能,因为他们先在自己的独立环境中工作的。

       4. 自动化数据库创建。很多场合需要创建一个数据库的副本。你可能需要从头构建系统以进行完整测试;加入团队的新人需建立开发环境;也可能要在一台机器建立 Demo 系统。创建一个新的数据库是经常要做的一件事,所以应力求将该项任务自动化。类似地,也应当能很容易地删除数据库。

       5. 独立地进行每次重构。这样每次只对数据库 schema 做小的变更,使得可以从任何版本的数据库 schema 开始,前进到任何版本。

       6. 采用统一的版本标识策略。常见标识策略包括使用递增数字( 1 , 2 , 3 , 4 , … ),时间戳或版本号。这三种都是很好的策略,但我偏向使用数字,因为很简单。无论哪种策略,关键是能以正确的顺序应用数据库变更,尤其需要自动创建和更新schema 时。

       7. 需要时将数据库变更打包。为实现一个功能,开发人员可能需要做几处数据库变更。当从项目集成环境部署系统到生产前的测试环境时,需要将 schema 的变更打包。比如,假设当前数据库版本号是 1701 ,而需要部署三个新的数据库重构(版本号是 1702, 1703, 1704 )。若依次部署这三个重构,则数据库 schema 的版本号将为 1704 。

       8 . 确保数据库知道自己当前的版本号。数据库更新脚本应让开发人员清楚当前数据库版本号。例如,可以写一个UpdateSchema 的脚本,该脚本以一个数据库版本号为参数(如 1704 或 2007-06-14 -23 : 45 : 00 ),自动运行正确的变更脚本,将数据库从当前脚本更新到指定版本。

       系统持续集成包含系统自动构建和回归测试,数据库持续集成是整个集成过程的一部分。换句话说,仅仅集成应用程序代码是不够的,还需集成数据库代码。

 

 

采用 TDDD

       TDDD 作为整个开发流程中一个完整的部分 , 不应该是孤立的活动,数据库专家使用 TDD 方法同步工作。虽然从技术角度, TDDD 是相对容易的,但要在 IT 社区中大规范使用,还需克服很多问题。

文化分歧。 采用 TDDD 最大的障碍是开发社区和数据库社区间的文化分歧。开发社区正向着演进式开发方式发展,而数据库社区似乎仍坚持瀑布式模型。许多开发人员数年前就接受了 TDD ,而对很多数据库专家这还是相对较新的概念。可喜的是在数据仓库 / 商业智能社区,“思想领袖”正在推动演进式工作流程(但是实践中却常常忽视这些好建议)。不幸的是,这些社区内很多人认为:因为他们用演进方式工作,所以他们不需要测试!在 90 年代早期,我们在面向对象开发社区内也曾听得过类似言论。

工具匮乏。 正如我之前所提到的,我们面临的第二个挑战是缺乏工具。但在市场上也逐渐出现了一些有趣的数据库重构、测试、持续集成的工具。因为数据库是共享资源,所以也需要工具能使相关团队之间沟通数据库 schema 的变更。

同时,我们还需要完善数据库建模工具——例如,能显示一个被废除数据库 schema 的删除日期 ( 如图 3b 中 ) 。好的数据库建模工具应充分发挥可视化设计的价值。如果建模工具能定义数据库测试就更好了。最后,我们还需改进数据库本身。比如,像 Sun JDK 处理被废除的 Java 代码一样,当访问一个被废除的数据库元素时,难道数据库不应该给一个警告吗?

       不了解。 据我的经验,敏捷软件开发者能很快接受 TDDD 。这大概是因为他们已经采用了 TDD ,所以将 TDD 扩展到数据库开发中是很自然的。传统开发人员及数据专家常常对 TDDD 很感兴趣,但这与他们所习惯的工作方式差距太多,所以对他们来说很痛苦。我期望能通过培训和咨询等方式克服这个问题,同时也需要一点耐心。

 

       毋庸置疑,我们能将 TDD 扩展到数据库开发领域,实现一个与现代软件开发方法相适应的演进式方法论。采用 TDDD 所需的技术条件已经具备,更重要的是,我们也看到很多支持工具也在诞生中。期望在两到三年内,我们能看到众多工具的产生来支持演进式——即便还不算“敏捷”——数据库开发。

 

<!----><!---->  <!---->

抱歉!评论已关闭.