永远不要忘记数据库测试 数据库测试的重要性和测试要点说明

译者导读:本文分为三部分,第一部分是第1节,即说明“对数据库测试的根本误解”;第二部分从第2节至倒数第4节,详述“数据库测试测什么”的问题;第三部分是最后3节,引出“数据库测试怎么测”的问题,提出自动部署、自动化测试、持续集成的思路及工具。另,副标题是俺自行添上去的,以明示本文意图。 程序员

对数据库测试的根本误解

有许多关于测试驱动开发(Test-Driven Development,缩写为TDD)的书籍。那些书一般关注的是将测试应用于工做单元(units of work)。对于工做单元的理解有许多种不一样的方式,一般它表示一个类(class)。正如那些书中所言:编写许多测试,以使那些测试都能经过的方式建立代码。应模拟全部的外部资源,以便你能够只测试这个单元。 数据库

这很酷,但不幸的是全部的测试在此刻中止了。由于一般会有些没被测到的查询(手写的或者是由某些ORM工具生成的)。有些程序员使用集成测试来测试那些查询——链接到一个真实的数据库并执行真实的查询来进行测试。这种作法一般意味着在测试快乐路径(happy path)——我已经有了ORM工具,因此它会搞定每件事,我无须费心。 网络

数据库一般是一家公司最有价值的资产。应用程序能够一遍一遍重写。旧的应用程序扔出去,新的应用程序装进来。可是更换应用程序时没人会丢弃满载数据的数据库。而是将数据库当心翼翼地迁移过去。位于多个系统中的许多不一样的应用程序会在同一时刻使用同一数据库。这就是为何拥有充满约束的良好数据库模型是如此重要、以及为何应谨慎对待数据库的缘由。你真的不想破坏数据一致性(consistency),由于这会使你的公司付出高昂的代价。 session

本文是关于常常被遗忘的数据库测试的。使用真实数据进行集成测试。实际上,它与你所使用的数据库引擎的类型可有可无。你可使用PostgreSQL、MySQL、Oracle,或者甚至使用那些有趣的noSQL数据库,例如MongoDB。如下规则可适用于各类数据库和各种应用程序。也许不是所有,例如noSQL数据库就没法强制实施数据完整性(integrity)。 架构

你的应用程序一般是由许多不一样的部件组成的。其中有一些<将任何你喜欢的语言放在这里>代码,一些配置文件,一些SQL查询,一些外部系统。测试一个应用程序意味着分别测试每一个部件(由于只有这样才更容易找出bug)、以及测试全部部件是如何协做的。数据库就是这些部件的其中之一,并且你应该完全测试它。 app

不测试数据库

这是首要的、最可怕的错误。根本不测试数据库。你编写了一些使用数据库的代码。你甚至使用一些模拟数据库链接为这些类建立了单元测试。 dom

集成测试怎么样?集成测试应在生产环境下对应用程序进行测试。集成测试背后的惟一想法是,确保应用程序部署到生产环境后能够正常工做。若是你不在生产数据库上测试应用程序,那么你实际上不并不知道应用程序可否工做。你的模拟链接让你能够发送还没有检查以及没有检查的任何查询。模拟链接只返回你所需的数据。 数据库设计

不建立集成测试意味着你实际上没有测试你的应用程序。 wordpress

不测试数据库Schema(模式/架构)

我所观察过的大多数团队拥有某种形式的集成测试。一般进行快乐路径测试:有某个ORM工具,咱们持久化对象,ORM工具会完成那些工做,真是太酷了,我无须费心。 工具

我从未见过一支对数据库schema(模式/架构)进行测试的团队。想象一下,因为某些针对产品的查询很慢,所以你必须在该数据库中建立某个索引。当下次在新的客户环境中部署此应用程序时,你但愿拥有该索引并确认该索引真的就在那里。为何不编写一个简单的测试来检查该索引的存在呢?

除了索引,还有许多要测试的内容:

  • 主键(primary keys)
  • 外键(foreign keys)
  • 一些检查——以确保“price”(价格)列不会有负值
  • 某些列的惟一性(uniqueness)——你实际上不想拥有两个具备相同登陆名的用户。

不在生产环境下测试

当你开发某个应用程序时,你能够从种类繁多的数据库中进行选择。一般你会从中选择那个最好的、那个被团队所熟知的、或者是由管理层所选定的(有时使用一些奇怪的理由)。有时同一应用程序的多个部署会在同一时间使用不一样的数据库引擎。有时应用程序会为了能使用不一样的数据库引擎进行准备,所以购买此应用程序的客户就能够选择他想要的数据库。

数据库引擎的选择真的与进行产品测试无关。

因为程序员的懒惰,所以他们但愿他们的测试能够运行得飞快。他们不想为测试结果等过久。这也就是为何许多团队使用某些更快的内存数据库(例如HSQLDB)的缘由。因为那些内存数据库仅存储在RAM(Random Access Memory,随机存取存储器)内存中,并且在操做时不接触任何硬盘驱动器,所以它们的运行速度要快得多。

还有一条经常被人遗忘的规则:

测试应该使用与生产环境相同的数据库引擎。

许多程序员会使用某个其余引擎。常见的解释很简单:“咱们的数据库太慢了,咱们应该使用某个内存数据库引擎。”。这并非个好主意。这样你测试的是该其余引擎,而非你的生产环境中的那个,因此实际上你并没对你的应用程序进行测试。

我曾经遇到过这个问题。咱们必须在成功链接数据库后经过设置session变量来优化查询。那个应用程序在生产环境中只使用Oracle数据库。当设置此变量之后,测试失败了。并且是全部的测试都失败了。原来是我不能在HSQLDB内存数据库中设置此变量,由于它根本不存在。所以,我必须编写一段糟糕的代码:在链接后,检查数据库引擎,并由此决定是否设置此变量。

即便你没有任何与混合引擎有关的问题,请记住,当你测试其余非生产环境的数据库引擎时,你偏偏根本没对你的应用程序进行测试。

不许备好数据库就进行测试

测试有一个通用的、明肯定义的流程。 它很是简单:

  • 准备环境
  • 运行一个测试
  • 检查测试结果
  • 返回至第一点
若尝试背道而驰,则你会被它所伤。

你察觉在测试以后是没有整理(tidying)的么?

这点很是重要:必须在测试前准备环境,而非测试以后。你没法确保测试将可以清理一切。应用程序可能由于某个错误、网络链接失败而退出,或者应用程序可能崩溃(例如,因为内存不足异常)。该测试如何终止并不重要,真正重要的是该测试运行在为每一个测试所准备的相同环境中。

我曾犯过这个错误:有大量的集成测试,它们会在每次测试后清理全部更改。许多程序员正使用调试器来运行这些测试,而且当发现bug后会在中间中止测试。任何在该测试以后运行的测试会获得不可预知的且随机的结果。由于它正运行在已被前一测试所改变的环境中,并且没有为其清理整个环境。

准备了数据库却不对其检查就进行测试

在前面的部分中我提到许多有关准备数据库的内容。我还想补充一点。准备数据库是不够的。当你经过清理某些表、加载配件等等准备好数据库时……还剩下一件事情要作。

在准备完毕后,要检查数据库状态。

你真正须要确保的是你已将一切准备稳当。当出现因为bug所致使的某些数据留下来且未能清理时,这些工做就能够节省你的时间。

这应该是测试前数据库准备的一部分。

不测试建立脚本

每一个应用程序都须要某种形式的安装过程。而对于你部署数据库而言,永远是第一次。

程序员每每会经过手工执行某些临时的数据定义语言(DDL,data definition language)查询来改变数据库。他们稍后并无把那些语句写下来,或是忘记了所作的改动。他们没有更新安装脚本。大多数团队不使用有版本控制的脚本(例如,Ruby on Rails中的Migrations、或者是Java世界中的Liquibase)。

对测试而言最好的方式是,在运行测试套件前从新建立数据库。你没必要在每一个测试开始前都那么作。只在运行全部测试前运行一次就好了。

是的,宁肯事先谨慎有余,不要过后追悔莫及。

不测试外键

外键是提供数据库一致性的基本途径之一。在良好的关系数据库schema中,你应该拥有各类键。若是你没有的话,那么这可能代表你有一个真正的大问题。然而,这取决于数据模型,可是一般缺少外键是种很是糟糕设计的味道。

测试外键很简单。只需在事先没有在引用表中添加适当的行的状况下,为某个表添加一些行。你应该获得一个错误。而后,你应该从引用表中删除行,你可能获得错误,或没有错误(这取决于该键的定义)。不管如何,你都应该检查一下预期行为。

不测试默认值

在良好的数据库设计中,你应该定义一些合理的默认值。一般这些默认值多是空(null)。即使这些空也应该进行测试。你不能假设,只有你的应用程序将改变此数据库中的数据。\ 两个问题:

  • 若是某人想建立一个快速修复并使用临时SQL查询更新某些数据,该怎么办?
  • 若是有一天某人启动另外一应用程序,它能够改变这些数据,并且新的应用程序将不使用你的ORM映射或你的DAO(数据访问对象)类,又该怎么办?
  • 若是你拥有愚蠢的默认值、或是错误的默认值,那么你可能会破会数据,并且那可能比一个简单的应用程序bug更糟糕。

不测试约束

在数据库中还有更多约束,不只只有主键和外键约束。你可能拥有一些惟一的(unique)或不为空的列。你可能约束某列只有不多的值集。你可能想确保价格永远不会低于0。

良好的数据库schema应拥有许多约束。你也应该测试它们。若是你但愿你的价格列只能拥有正值,当你尝试向其中插入-1美圆时会发生些什么?为何不测试一下呢?

你不能假设只有你的通过良好测试的应用程序将使用那些数据,并且这些检查是为你防护这些bug的最后一道防线。为何不测试它是否正常工做?

多个测试能够更改同一数据库

一般,数据库测试会更改数据库。你可能同时运行多个测试,可是你必须确保那些测试彼此之间没有影响。你必须确保,若是某个测试将一些内容写入数据库,而另外一测试将不会读到。

一般,很容易搞得一塌糊涂,所以我小小的忠告是:避免在同一时间运行多个测试。这也意味着,你不该该在多台机器运行相同的测试套件。

当你有许多想运行测试的开发者时,他们每一个人应该拥有可用于编写测试的单独的数据库。若是你拥有某种形式的只读数据库,不要紧,多台机器能够在同一时间使用这个数据库。可是若是你容许全部程序员使用同一数据库进行测试的情形出现,那么你可能真的会获得不可预知的测试结果。

当程序员在某个测试中发现一个错误时会怎么作?那么,优秀的程序员会尽可能修正错误。若是该测试失败仅仅是由于另外一程序员在同一个数据库上运行他的测试所致使的话,那么修正此类错误只是在浪费程序员的时间。

没有大红按钮

优秀的程序员是懒惰的。若是你命令优秀的程序员每次都重复一样的任务,他们会愈来愈沮丧。优秀的程序员会自动化可重复的事情。

在每一个项目中,你必须在测试环境中部署某些东西。作这些会花去多少时间?你真的想为了从新部署应用程序和加载数据库一直浪费你的程序员时间么?

这就是为何每一个项目都应该有个大红按钮的缘由。某位程序员能够按下此按钮,而后冲杯咖啡,回去工做,而且几分钟后得知的大红色按钮完成的工做,一切准备就绪。

大红按钮真的会为你节省不少时间。你会说自动化全部工做实在太缓慢。然而,事实并不是如此。偏偏相反,就像说测试驱动开发(TDD,Test-Driven Development)很慢同样。在最初的时候比较慢,但随着项目变得更加复杂,因为存在测试或按钮,会为你节省更多的时间。各类各样的大红按钮——你能够用它们部署应用程序、运行测试,以及相似的后方支援。

有时会使用Jenkins(又名Hudson)来作这些。是的,对于此类大红按钮而言这是一款伟大的软件。惟一的事情是,每位程序员应该有其本身的一组工做以便在其本身的环境中部署全部的内容,在其本身的环境中他(或她,固然)能够自由发挥,而不会影响他人,一样也不会受到他人的影响。

工具

有许多数据库测试工具。为了测试整个schema,你能够编写简单的集成测试。对于PostgreSQL有pgTAP,使用TAP插件它能够与Jenkins集成到一块儿,所以Jenkins能够拥有一项用于测试数据库(包括生产数据库)是否正常的工做。

相关文章
相关标签/搜索