测试持久化 domain model

【字号: 日期:2023-02-15浏览:41作者:雯心
内容: 测试与牙线--测试持久化 domain model作者:cleverpig译编者序: 有时编写测试犹如漫漫人生旅途一般,我们常常面对某些抉择,对于如何选择下一步我们要做的,往往决定于我们的视野、价值观。本人翻译、编写此文旨在于为大家提供测试过程中一些参考。因为此文截取自POJOs in Action这本书,所以部分承启的内容不免有些唐突,望读者见谅。注释:本文摘自POJOs in Action第四章。注释:文中提及的“数据仓库(database repository):指具体的数据库实现,比如MySql。正文: 每隔半年,我的牙医Anne-Marie就教育我牙线是如何的重要。每次,我心不在焉地许诺将更加重视,但我从没有坚守诺言。有些开发者对待测试如同我对待牙线的态度:测试对于软件开发是good idea,但是他们不是很被迫的做测试,就是完全不进行测试。 然而,测试作为软件开发进程中的关键组成部分,就如牙线防止牙病一般可以预防软件的“腐败。持久化层和大多数应用组件相似都无法抵御“腐败,所以测试是必需的。你需要编写测试:核对domain model是否正确地映射到database、查询如期地被使用。这里在测试持久化domain model时存在两大挑战。 首先,第一个挑战来自于domain model、ORM文档和数据库schema之间的不一致。例如,一个常见的错误是在映射中忘记定义新增的字段,然而这将引起一些诡异的bug。另一个常见问题是数据库约束阻止应用建立、更新或者删除持久化对象。编写对持久化domain model的测试实质上为了捕捉这些(和其它)问题。 第二个挑战是如何高效的测试持久化domain model从而最小化测试时间。当测试一个大块头domain model的O/R映射时,这个测试suite将花费长时间来执行。不仅大量的测试耗时,而且仅一个访问数据库的测试也比简单的对象测试多花费时间。尽管一些数据库测试是不可避免的,但重要的是能否找到避免这些测试的方法。 本章节,你们将学习不同ORM的bug和如何编写检测它们的测试。在这里我说明了哪些O/R映射测试必须依靠数据库进行测试,而哪些为了最小化执行时间能脱离数据库而单独测试。你们将在第5、6章看到这些方式的测试范例。4.5.1 O/R测试方式 隐藏于O/R映射中的bug包括以下: 1.映射缺少字段; 2.引用了不存在的表或列; 3.数据库约束阻止持久化对象被插入、更新或删除; 4.无效的查询或返回错误结果; 5.不恰当的数据仓库(repository)实现。 一些bug是由domain model、ORM文档和数据库schema不一致造成的。比如,通过增加新字段或者重命名旧字段的方式修改domain model,而忘记了增加或者更新此字段的O/R映射。某些ORM框架会在缺少字段O/R映射定义时产生错误信息,但另一些(包括Hibernate)却沉默地允许将这个字段作为非持久化类型,这会引发潜在、棘手的bug。同样,在定义字段映射时很容易忘记更新数据库schema。 有一部分bug容易被发现,比如被ORM框架在启动时检测到:Hibernate在应用打开一个SessionFactory时会报告缺少的字段、属性或者对象构造方法。另外一类bug需要指定代码路线才能被执行到。一个对collection字段的错误映射直到应用长时访问collection时才被检测到。同样的,在查询中的bug经常到它被执行时才被检测到。为了发现这种类型的bug,我们必须十分完整的测试应用。 测试持久化层的一个方法是编写依赖数据库运行的测试。例如,我们可以写测试来建立、更新持久化对象、调用数据仓库方法(repository methods)。然而,随之而来的一个问题是测试即使再使用象HSQLDB这样的in-memory数据库也会执行一段时间。另一个问题是测试检测到bug(比如缺少字段映射)时将失败。而且编写这些测试需要花上许多精力。 一个更有效、更快速的方法是使用一些和持久化层分离的测试,即脱离数据库的测试。一些测试依赖数据库,而另一些可以脱离数据库。 这些依赖数据库的测试包括: 1.建立、更新、删除持久化对象的测试; 2.被用于数据仓库的查询测试; 3.验证O/R映射和数据库schema是否匹配的测试。 下面是可脱离数据库的测试: 1.用于数据仓库的假对象(Mock object)测试; 2.通过使用XML映射文档方式验证O/R映射正确性的测试。 下面,我们将看到这些不同的测试。4.5.2 依赖数据库的测试 依赖数据库的测试是持久化domain model测试中的一个基本组成,尽管它们需要花掉相对长一些的时间执行。目前有两种数据库级别(database-level)测试:第一种是验证持久化对象能被建立、更新、删除。另一种是验证被数据仓库使用的查询。 下面让我们看看每种测试方法。测试持久化对象 测试持久化domain model的目的在于核对持久化对象是否能保存到数据库中。一个简单的方法是编写测试:建立对象图表,将其保存到数据库。测试并不尝试核对数据表包含正确的值,取而代之的是ORM框架抛出的异常。这种测试是找到基本ORM bug(包括检测是否丢失类映射和数据库列映射)的相对简单方法。尽管这种测试是一种“起步的好办法,但它不能检测其它的ORM bug,比如发生在更新、插入、删除时的约束违背。 我们可以通过编写更加精细的测试来更新、删除持久化对象,从而观察那些“起步测试无法找到的bug类型。例如,一个针对PendingOrder的测试由以下几步构成: 1.建立PendingOrder持久化对象,保存之; 2.读取PendingOrder持久化对象,更新交货信息,保存之; 3.读取PendingOrder持久化对象,更新餐馆,保存之; 4.读取PendingOrder持久化对象,更新订货量,保存之; 5.读取PendingOrder持久化对象,更新订货量,保存之(再次测试删除line items); 6.读取PendingOrder持久化对象,更新支付信息,保存之; 7.删除PendingOrder持久化对象。 上面的测试核对了数据库能够保存对象的所有状态,并在建立或者删除对象之间(PendingOrder与line items)的关联时检测问题。每一步测试由数据库事务构成,此事务使用新的持久化框架建立的数据库连接。通过每次使用新的事务和连接,我们确保了对象真实地被持久化到数据库中并再次被读取。它也确定了对迟缓的约束(因为直到commit时数据库才会检测)是否满足。 这种方式的缺点是它会改变数据库,每次测试前需要初始化数据库到一个已知的状态。 我们还可以通过核对数据表内容来增强这些测试去验证对象字段是否非正确映射:在插入对象到数据库后,测试核对数据库是否包括期待的行、列值。这个测试通过使用JDBC获取数据的方式校对数据库内容。另一种方法是使用DbUnit比较数据表和包含期望数据的XML文件。此方法很精细,而对开发、维护这些测试来讲却是完全乏味的。另外,这些测试不能检测到缺少新增字段或者属性的映射。 总地来看,更好的方法是测试classes和字段/属性映射是否匹配,正如我下面要讲的——直接测试ORM文档。 插入、更新、删除持久化对象的测试完全有用,但是它们对于编写者来讲是不小的挑战:一个原因是一些对象的各种状态需要测试,对于复杂性角度而言另一个原因是测试必需的大量设置(setup)。测试不得不建立被测试对象引用的其它的持久化对象。比如,为了持久化PendingOrder和它的line items,测试不得以地初始化餐厅(Restaurant)和菜单项(MenuItems)。另外,对象的公共接口往往不允许它的字段被直接赋值,所以测试必需使用正确的参数调用一系列业务方法(business methods),也许这些业务方法会调用更多的setup代码。作为后果,这可能对编写良好持久化测试构成了挑战。 这种方法另一个弊端是由于访问数据库所以执行测试很缓慢。每个持久化类可能有多个测试,每个测试由多步组成。每一步多次调用ORM框架,用以执行多个SQL语句。 总而言之,这些测试作为单元测试套件的一部分经常花费太长时间,应把它放到功能测试中。 即使这些持久化对象测试很难编写,并需要很多时间执行,但他们仍作为domain model测试套件中重要的一部分。如果需要,你可以开始编写仅保存对象的测试,然后逐渐添加更新、删除对象的测试。测试查询 我们需要写数据库级别的查询测试。一个基本的方法是编写仅执行查询、忽略结果的查询。这个快速、简单的方式能使我们捕捉到一些基本错误,这是常常作为测试简单查询而作的。 对于复杂的查询,重要的是检测查询逻辑:比如使用“
相关文章: