没有合适的资源?快使用搜索试试~ 我知道了~
理论计算机科学电子笔记271(2011)41-62www.elsevier.com/locate/entcs使用QuickCheck测试数据密集型应用程序的数据一致性Laura M. Castro卡斯特罗1,2科鲁尼亚大学计算机科学系ACorrunNacional,Spain托马斯艺术3计算机科学与工程Chalmers University瑞士哥德堡摘要许多软件系统是数据密集型的,并且使用数据管理系统用于数据存储,诸如关系数据库管理系统(RDBMS)。RDBMS用于以结构化的方式存储信息,并定义数据上的几种类型的约束,以保持基本的一致性。RDBMS是成熟的、经过良好测试的软件产品,人们可以信任它来可靠地存储数据,并在定义的约束内保持一致然而,在某些情况下,将一致性实施的责任传递给RDBMS并不方便,或者根本不可能。在这种情况下,另一种选择是在系统的业务逻辑级别上承担责任。因此,从测试数据密集型应用程序的角度来看,最相关的方面之一是确保业务逻辑在数据一致性方面在本文中,我们将展示如何使用QuickCheck(一种针对规范进行随机测试的工具)来测试应用程序的业务逻辑,以提高对数据完整性的信心。 我们建立数据的抽象模型,包含创建有意义的测试用例所需的最少信息,同时保持其状态基本上小于完整数据库中的数据。从抽象模型中,我们自动生成并执行测试用例,检查数据约束是否被保留。关键词:软件验证,软件测试,基于模型的测试,软件工具,快速检查。1571-0661 © 2011 Elsevier B. V.在CC BY-NC-ND许可下开放访问。doi:10.1016/j.entcs.2011.02.01042L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)411介绍许多应用程序使用一个或多个数据库来存储大量数据。它们还提供了许多不同的接口来检查和修改数据。一些应用程序界面可以从web界面访问,其他应用程序界面可以从桌面应用程序使用。不同的用户具有不同的访问权限,因此呈现给数据的接口也不同如果数据库管理系统正确实现,数据库通常会约束数据上的某些关系,这些关系不能被违反。在这些内置约束之上,还有应用程序强加的其他规则。 这些约束不仅涉及数据格式或关系,还涉及重要的计算。例如,在内置约束之上的约束可以是确保邮政编码是给定国家的正确格式,或者用户只能在完成更新某些注册数据之后才能更新某些配置文件信息。一般来说,这些约束由应用程序验证,也就是说,系统验证输入数据的格式是否正确,或者在实际查询数据库之前是否遵循了一个过程。忽略验证信息,将导致在数据库中存储不满足约束的数据需要数据库无法强制执行的约束还有其他原因。例如,当约束依赖于时间,或者具有数据库本身无法保证的性能和效率要求时。通常,许多约束都是业务特定的,因此没有必要也不希望将它们“硬连线”到数据库中,特别是如果数据库要由其他系统共享时。由于这些原因,应用程序的业务逻辑需要处理这些特定于业务的约束,也称为业务规则[5]。在测试数据密集型应用程序时,至关重要的是要提供足够的信心,确保系统不会出现违反业务规则和数据处于不一致状态的情况。这应该独立测试,无论这是一个或几个调用接口函数的结果与意外的输入或未考虑的情况。以前的尝试面临着业务规则定义和建模的挑战,要么提出新的开发方法,要么对现有方法进行修改[5,8]。但很少有研究致力于检查商业规则的实际执行情况尽最大1本研究部分由三个基金赞助:FARMHANDS(MEyC TIN 2005 - 08986和XUGAPGIDIT 07 TIC 005105 PR),欧盟FP 7合作项目ProTest(基金编号215868)和AMBITIIONS(MICIN TIN 2010 -20959)。2电子邮件:lcastro@udc.es3电子邮件:arts@chalmers.seL.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)4143据我们所知,解决这个问题最实用的方法是[16]中提出的方法,该方法提出了对JUnit [14]的扩展,以根据业务规则进行面向数据的测试,从而执行更好的测试。其他的测试方法关注于代码覆盖率和数据库查询正确性[9,12],因此可以被认为与我们的工作正交我们建议采取以下办法:为了确保遵守业务规则,我们将其表述为数据的不变量,数据库我们在随机调用应用程序接口函数后在数据库上检查这些不变量。如果任何其他接口函数调用允许数据库进入违反不变量的状态,则我们在应用程序中检测到错误。为了生成有意义的测试,即,为了以一种智能的方式控制测试的随机性,我们创建了一个数据库的抽象模型。例如,如果我们在数据库中输入某个项目,那么我们需要记住它的键或标识符,以执行与同一项目相关的后续操作。为了保持模型的简单性,重要的是保持模型的抽象性,并在模型状态中存储尽可能少的信息,绝对少于存储在数据库中的数据。如果没有,在操作之后修改模型状态将涉及与修改数据库相同的复杂性。因此,我们将复制模型中的大部分业务逻辑操作,这是我们希望避免的。我们将模型公式化为QuickCheck4规范[4,13]。QuickCheck是一个引导随机测试的工具。QuickCheck规范是用函数语言Erlang [2,7]编写的,带有一些QuickCheck规范的宏定义。该工具根据提供的规范自动生成并执行测试用例。本文通过一个网上商店的小实例,介绍了我们提出的用QuickCheck测试数据密集型系统的方法然而,该方法是在测试实际保险系统时开发的[1]。我们在实际应用程序投入使用几年后开始对其进行测试,我们还没有通过使用我们的方法在生产代码中识别出任何错误,但是我们能够通过在生产代码中注入错误并通过运行源于我们方法的测试来检测它们来展示该方法的可用性此外,在开发的方法,硕士生项目进行了另一个商业数据库密集型系统以同样的方式进行了测试该项目导致检测到一些故障[15],并表明该方法适用于更广泛的领域。本文的结构如下:在第二节中,我们解释了概念业务规则,我们介绍我们的领先的例子,一个在线商店,4我们使用Quviq QuickCheck 1.19版,这是QuickCheck的商用版本44L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41澄清这个概念。我们测试数据库系统的方法的第一步是将业务规则制定为SQL查询。在第3节中,我们描述了作为测试业务规则的测试用例所需的接口调用序列。在第4节中,我们介绍了用于自动生成这些序列的模型该模型被描述为QuickCheck使用的规范形式主义中的有限状态模型。在第5节中,我们描述了我们如何在实际的工业示例中评估所提出的方法;证明这是一种可行且有效的方法来查找可能导致违反业务规则的故障我们在第6节中总结了我们贡献的方法。2业务规则在本节中,我们将介绍一个简单的应用程序,作为本文中的运行示例。该示例演示了在实践中如何在业务逻辑层中更好地定义某些数据约束,而不是在系统的持久层。我们描述了一种测试数据一致性的通用方法,该方法适用于更大的数据库,例如我们将在第5节中看到的案例研究中使用的数据库。本发明用于测试应用程序通过业务逻辑层访问数据库不能违反业务规则,通过使用任何可能的应用程序接口。该方法应适用于关系以及非关系数据库与多个并发客户端访问系统。然而,在我们的研究中,我们没有明确地解决分布式数据库或时间关键数据库。2.1主要例子:网上商店我们从Willmor和Embury [16]中借用一个简单的例子。我们实现了一个处理客户、产品和订单的应用程序,在该应用程序中,我们为客户引入了一个状态,即“黄金”或“非黄金”。这种状态背后的含义是,只有黄金会员才能购买一些特殊产品。图1显示了此示例的权限关系图。当将此图转换为关系数据库模式时,会出现一组约束,这些约束将被实现为数据库约束(实体键作为主键,一对多和多对多关系作为外键等)。然而,像客户何时获得不仅因为这些限制在系统寿命或运行期间可能会有所不同(“特色产品”列表可能每月或每周都不同L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)4145图1.一、简单ER图示例‘gold’因此,这些约束被认为是业务规则,并在应用程序业务逻辑级别进行处理2.2不变量作为SQL查询如[16]所述,可以通过将每个规则转换为可以针对数据库进行评估的SQL查询来测试系统对业务规则的正确实现。例如,下面的SQL语句将检查没有“黄金”状态的客户如果返回空集合以外的任何内容,则违反相应的业务规则请选择customer.idFROMcustomer,order,order_productsWHEREcustomer.id = order.customer_idANDorder.number = order_products.ord_nmbANDcustomer.status>ANDorder_products.product_code在特色产品列表中>46L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)412.3违反商业规则假设当用户至少有五个订单时,将获得黄金会员资格。还假设系统中可用的接口功能是实体(客户、产品、订单)的添加、更新和删除。然后,有很多方法可以打破以前的业务规则。不仅像非金牌用户下一个包含特色产品的新订单这样的单个操作会违反上述约束,下面的序列也会违反:(i) 非黄金客户X下第五个订单(系统更新状态为(ii) 同一个客户X(现在是(iii) 客户X取消了前五个订单中的一个可以实现不同的系统行为,以避免达到此序列将导致数据库不一致的情况(根据业务规则)。例如,应用程序可以防止黄金客户取消订单,如果这将删除他/她的黄金状态,或者取消这样的订单不仅可以删除黄金会员条件,而且还可以自动取消包括特色产品的订单。在测试数据密集型应用程序的数据一致性时,我们并不真正关心实际实现了哪种策略:我们的目标是确保无论是哪种策略,它都能保证始终符合业务规则。3测试序列从前面的例子中,我们还得出结论,数据密集型应用程序测试必须包括对接口函数的调用序列直接违反业务规则通常由开发人员在实现业务逻辑时处理非黄金客户将永远不被允许下包括特殊产品的订单)。相反,不常见或非典型的调用序列很可能会导致数据不一致,并且更容易被忽略。此外,在实际应用中,手动生成相当数量的接口调用组合是不切实际的。因此,我们使用一个工具来帮助生成调用序列,以便测试业务逻辑。3.1有意义的测试序列为了避开任何无意识的假设,并避免在我们的测试序列中遗漏相关的场景或可能性,提供随机输入的自动测试生成工具可能会有所帮助。然而,完全随机化L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)4147测试序列将生成大量无意义的测试,例如,反复尝试将不存在的产品添加到订单中,或取消未知的订单。在纯粹的随机生成中,我们生成有意义的测试的可能性非常小,因此,我们需要使用引导随机生成。这就是我们引入的数据库状态模型我们的示例的业务逻辑提供了许多接口功能,例如,注册新客户(新客户)或禁止销售某个产品(删除产品)。它还包含更复杂的功能,例如注册某个客户下了某个订单(下订单)。测试业务逻辑最安全的假设是接受任何定义和导出供公共使用的函数都可以在任意随机参数的序列。然而,正如我们之前提到的,我们希望得到尽可能多的有意义的测试;因此,我们希望使用关于先前调用了哪些接口调用以及它们具有哪些结果的知识,以便在测试序列中生成下一个接口调用。我们用Erlang语言将测试序列写成数据结构。这些数据结构几乎是测试程序,但我们生成的不是程序,而是我们解释为程序的数据结构。这样做的原因是它允许我们将测试用例视为测试语言中的对象。因此,我们可以以任何我们喜欢的方式简化或修改测试用例;如果我们希望QuickCheck在发现失败的测试用例后找到一个更简单的测试用例,这会有所帮助。下面的示例显示了这样一个测试序列的样子。该序列代表第2.3节中描述的失败场景。在Erlang中,变量是用一个字符来写的,以开头的单词是原子,可以被认为是常量。 元组包含在{ }中 ,列表包含在[]中。ID={call , business_logic , new_customer ,[“Laura”]} , Nr1={call , business_logic ,place_order , [Id , 1277]} , Nr2={call ,business_logic , place_order , [Id , 7027]} , Nr3 ={call , business_logic, place_order, [Id, 3112]},Nr4 = {call , business_logic , place_order , [Id ,4983]},黄金= {call,business_logic,place_order,[Id,9002]},{call,business_logic,cancel_order,[Nr3]}具有第一个参数调用的元组被解释为接口调用,例如,第一个元组将以字符串“Laura”作为参数调用模块business_logic中的一元函数new_customer。请注意,我们需要连续调用中第一个调用的返回值48L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41我们需要记住返回的订单号,以便取消一个(序列的最后一步我们希望这个序列是一个有效的场景,即一系列的调用,实际上可以从用户界面进行有些序列是我们可以预料到的,比如创建一个新客户,直接订购一个特色产品(需要黄金状态)。如果用户尚未达到黄金状态,则用户界面可以禁用订购特色产品的机会。然而,我们不想在这个意义上做任何假设,所以我们也允许生成这样的序列。3.2阳性和阴性检测如果一个接口函数预期会成功返回,我们称它为肯定测试;如果它预期会返回错误,我们称它为否定测试。我们是否期望接口函数成功返回或返回错误取决于数据库的状态,或者换句话说,取决于前面的接口调用。例如,某些测试在给定状态下执行时会违反业务规则,因此我们测试,给定模型的这种状态,结果是接口的错误消息,并且数据库状态之后不会违反业务规则因此,如果应用程序在连续调用期间崩溃,当接口函数之一的结果对于我们的模型来说是意外的,或者当数据库处于违反业务规则的状态时,例如,在我们的在线商店中,如果业务逻辑不允许订单取消与黄金状态更新相冲突,则错误可能是cancel order调用(示例测试序列中的最后一步)的完全有效结果同样,如果业务逻辑选择不同,操作可能成功,只要黄金状态因此而被撤销。4测试模型为了能够自动生成有意义的阳性和阴性测试,我们需要保持最小的测试状态,这将包括在我们的测试序列中生成相关接口调用所需的最少数据量例如,如果我们希望执行涉及同一客户的连续操作,则此类状态需要保存该客户的客户ID。国家将作为一种我已经参与了以前的函数调用,而不需要检查数据库(或者相信它们已经被正确地L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)4149作为先前操作的一部分存储在其中)。换句话说,我们希望对系统行为进行建模,并使用基于模型的测试。另一个需求是,当测试用例失败时,我们希望能够自动获得更简单的测试用例;这有助于故障分析和调试过程。因此,如果测试失败,那么我们希望得到一个序列,该序列在所有可能的方式下都被缩短,并且被调用函数的所有参数都被简化。QuickCheck能够自动执行这些操作 , 只 要 我 们 将 测 试 用 例 呈 现 为 上 面 解 释 的 特 殊 数 据 结 构 。QuickCheck可以修改序列,但是它需要保持测试用例的意义。例如,简单地从序列中删除行可能会导致不适当的或不相关的测试。QuickCheck有许多用于表示状态机模型的特定库,我们使用其中之一(eqc statem)来建模我们的数据约束业务规则测试用例。请注意,我们使用QuickCheck是有优势的,但是测试数据密集型应用程序的基本方法也可以通过使用不同的工具来生成和执行测试用例来实现4.1生成随机命令为了指定被测系统,我们创建了一个状态机。最终,数据密集型系统的核心是其背后的数据库,因此我们可以将其视为有状态系统,其中“状态”是在任何特定时刻存储在数据库中的数据。使用状态机,我们可以指定如何将系统从一个状态带入另一个状态。现在的挑战是对数据库中的数据进行抽象,并将该抽象用作测试模型。否则,我们最终会得到整个数据库的副本作为状态。这不仅可能太大而无法处理,更严重的是,我们必须重新实现(至少部分)业务逻辑来处理它,因为重用测试中的实现不会使我们发现错误。当然,两次实现软件是一个没有吸引力的想法;除了所涉及的工作,人们可能会犯类似的错误。我们的方法是确保在我们的状态下有足够的数据来使系统处于各种不同的状态。我们从识别应用程序中的接口函数开始。例如,在我们的商店示例中,我们有:新客户,添加产品,下订单等。当然,在像我们这里展示的这样的小示例中,界面功能有限且简单,但它解释了我们的观点。50L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41我们希望创建一个接口调用或命令序列,其中每个命令都有一定的可能性出现在序列中。我们使用QuickCheck命令生成器将测试状态作为输入,命令相对于当前状态生成。frequency combinator接受一个元组列表作为输入,元组中的第一个参数是生成命令的相对权重。另一种可能性被选择的频率是指分配给它的频率除以所有选项的总和;频率为零意味着该选项从未被选择。命令(S)->频率([{1,{call,customer_facade,new_customer,[name()]}},{4,{call,product_facade,add_product,[...]}},{2,{call,order_facade,place_order,[...]}},.])。上面的代码指定了给定抽象状态S,指定的命令以频率1,4,2生成。. .在列出的命令总数中。因此,相对于其他备选方案,第二备选方案被选择的时间超过一半(4/7)。命令是具有四个元素的元组(i) 标记调用指定这是函数调用;(ii) 找到函数的模块的名称(iii) 被调用函数的名称;(iv) 一个带有参数的列表。模块和接口函数是静态已知的;它们应该被使用的频率是测试规范的领域知识。想要使用哪个频率取决于想要测试什么。在一般情况下,人们可能希望每个接口函数出现的频率相等。然而,在我们的情况下,我们想强调更多的测试添加产品和下订单的产品。 通过给予更多的权重,以增加产品比放置一个订单,我们实现了放置的订单平均包含至少几个产品。如果我们给他们相同的频率,那么许多测试可能会测试在没有选择产品或选择一种产品的情况下订单。我们对添加许多不同用户的测试也不感兴趣,更多的是对订购许多产品的少数用户的测试。这是通过减少创建新用户的可能性来实现的。通过使用QuickCheck,可以自动生成并运行尽可能多的不同测试一个人应该确保指示L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)4151QuickCheck用于衡量同一个人平均下订单的频率,下空订单的频率,每个订单的产品分布情况等。通过使用QuickCheck通过调整频率,可以获得更好的测试用例分布。如果不能实现这一点,人们总是可以定义两个不同的分布,并使用一个分布运行一组测试用例,使用另一个分布运行另一组测试用例好的一面是,可以很容易地获得关于测试内容的统计数据4.2生成随机数据剩下的任务是生成对以下每一项都有意义的参数:指定的命令。很容易为参数指定生成器在我们简单的例子中,我们只需要一个名字,如果我们提供地址、电子邮件地址、电话号码等,我们可以为这些字段生成随机数据我们从表定义,并可以自动将其传输到数据生成器中进行快速检查。例如,当客户数据库表的SQL定义为:CREATE TABLE客户(idINTEGER CONSTRAINTcust_prk PRIMARY KEY,名称VARCHAR,statusVARCHAR CONSTRAINTcust_nn NOT NULL)然后我们可以使用生成器定义为name()->varchar().其中varchar是一个快速检查生成器,用于创建适当字符的随机列表。我们可以实现一个更具体的生成器,例如创建名字作为名字和姓氏,都以一个字符开头,其余的以字符开头。然而,只有当这确实是业务规则的一部分并且接口功能应该确保这一点时,这才是重要请记住,这允许添加同一用户两次。我们希望这是一个潜在的测试用例,因为业务逻辑可能会阻止或允许这种情况。当我们想要调用函数place order时,生成器的指定变得更加棘手,因为我们需要提供客户ID和产品代码。生成随机客户ID和随机产品代码的问题是,它们很可能不在数据库中在故意52L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41测试方法[16]产品和客户在数据库中不存在时自动生成。我们提出了一种不同的策略,并使用测试状态(S)来跟踪创建的客户和产品,以便在下订单等调用中使用它们。此外,我们仍然会偶尔以小概率选择状态中不存在的客户或产品(或两者)来测试对这些错误场景的正确处理(负面测试)。4.3数据作为模型状态的一部分在我们的示例中,为了管理状态机状态,我们定义了一个具有两个字段的记录数据结构,每个字段包含一个列表:客户标识符列表和产品代码列表所有列表最初都是空的:initial_state()-> #shop{customers =[],products =[]}.产品列表稍后将由我们在执行的测试中添加的产品代码占据。因此,字段产品存储一个索引列表。相应地,客户列表包含客户数据 类 型 的 列 表 , 这 是 具 有 两 个 字 段 的 记 录 , #customer{id ,orders=[]},客户标识符ID和所下订单的列表,其中订单由其订单标识符存储,需要取消订单。我们还可以添加订单信息中订购的所有产品代码,但由于我们的方法是尽可能保持模型简单,从而减少必要的工作量,因此没有理由这样做我们只存储由数据库创建的数据,而不是测试用例中提供的数据的一部分每个接口函数的执行都可能对状态产生影响,因此我们在状态机模型中定义了一个状态转换函数nextstate来反映这一点。这个函数有三个参数,首先是当前抽象状态S,然后是接口函数返回的结果的占位符,以及接口函数的表示。nextstate函数返回更新的测试状态,这是我们期望数据库在执行接口调用后所处状态的抽象。next_state(S,Id,{call,customer_facade,new_customer,[Name]})-> NewCustomer=#customer{id = Id,orders = []},UpdatedCustomers=[NewCustomer| S#shop.customers],S#shop{customers=UpdatedCustomers};在此函数子句中,创建了一个新的客户记录,并使用函数执行时将返回的值初始化新客户(NewCustomer)记录的字段ID。订单字段由以下内容启动:L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)4153我们的优势我们的优势我们的优势空 的 名 单 。 之 后 , 新 客 户 被 添 加 到 已 经 存 在 的 客 户 列 表(S#shop.customer)中,并且状态S通过用这个新客户列表替换现场客户的值来更新这个初始状态和下一个状态函数允许我们生成大量引用预期返回值的不同命令。为了增加一些随机性,我们完成命令生成器如下:命令(S)->频率([1,call,customer_facade,new_customer,[name()],4,call,product_facade,add_product,[pr_code(S)],2,call,order_facade,place_order,[cst_id(S),pr_code(S)]...]).cst_id(S)->oneof([integer()]++[keys(S#shop.customers)]).pr_code(S)-> oneof([integer()]++[S#shop.products]).其中,生成器客户ID和产品代码挑选一个随机整数(因为在在线商店的数据库中实体密钥由整数表示),并将其添加到已知密钥的列表中。此外,QuickCheck生成器之一从给定的列表5中获取随机元素。因此,我们可以生成对函数的调用,例如添加产品或下订单,其中产品和/或客户的标识符可能已经存在或不存在(概率较低)作为参数。4.4验证测试结果因此,我们开发了一个状态机,它可以用来生成任意序列的接口调用,这些调用彼此相关,允许正面测试和负面然而,到目前为止,我们只能生成测试用例,甚至运行它们,但我们仍然需要一种方法来验证调用的结果。有两件事需要验证,第一,调用本身返回一个期望值,第二,数据库状态与业务规则保持一致如果我们假设我们已经成功地执行了一个生成的接口调用序列,我们想知道是否违反了业务规则。事实上,人们可能会在每次5功能键从客户记录中提取密钥。54L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41单独执行命令,但我们建议只在测试序列结束时检查它。我们的假设是,如果数据库处于与业务规则集不一致的状态,那么接口函数的连续调用将不太可能修复它。此外,我们运行数千个测试序列,如果业务逻辑可以被违反,那么我们很可能也会运行一个较短的序列,作为其他测试之一,违反逻辑,并且稍后不会碰巧修复它在每个测试序列中只检查一次业务逻辑冲突的优点是,它使测试更快,因此我们可以在同一时间运行更多的测试。为了使用我们的业务逻辑约束来测试数据库测试后的状态,我们将每个业务规则转换为数据库检查,并将所有这些作为在生成序列后执行的不变函数的一部分。每个业务规则都嵌入到由一个或多个SQL查询组成的数据库例如,我们运行的示例的不变量是:invariant()->{ok,Connection}=db_interface:start_transaction(),Result=business_rule(Connection),db_interface:rollback_transaction(Connection),Result.business_rule(Connection)->[]== db_interface:process_query(Connection,“SELECT id“)“FROMorder_products NATURALJOINcustomer““ WHEREstatus<>“AND代码IN(“++str:join([to_list(P)||-<?GOLDEN],",”)++“)”)。由于我们使用的是事务数据库,所以对数据库的每次访问都是作为一个事务来执行的。 为了确保不变式检查不会影响数据库状态,我们在执行事务后立即撤消事务。严格地说,不需要回滚,因为它是在测试结束时执行的,而且因为我们只检索条目以验证不变式,所以我们从不修改条目。L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41554.5验证调用的结果剩下要做的是检查每个接口函数是否返回预期值。我们希望在调用接口函数之前从数据库内容中获得尽可能多的信息,而从抽象状态中获得尽可能少的信息,因为我们使抽象状态尽可能简单。为了验证返回值,我们建议实现一个验证器函数对于每个接口函数,它基本上描述了数据库上应该填充的条件,以便创建特定的返回值。最简单的方法是将接口函数的调用替换为它们的本地版本,它结合了验证和实际的接口调用。在我们的商店示例中,总是可以创建一个新客户并获得一个新的标识符作为回报。我们不可能知道哪个标识符将被返回,但我们没有必要知道确切的值。因此,我们的新客户函数的本地版本调用相应的接口函数来创建用户,并将结果与预期的值,在这种情况下只是检查它的类型。不匹配该值将导致测试用例失败。new_customer(Name)->Result = customer_facade:new_customer(Name),caseIdwhen is_integer(Id)-> Result;_-> exit(unexpected_value)end.在这种特殊情况下,验证器和接口函数的组合只包含接口函数,因为没有检查数据库上的其他条件。如果实际值与期望值匹配,即是一个整数,那么我们返回该值,否则我们引发异常,测试失败。然而,更高级的接口函数,如下订单,需要更复杂的验证器函数。在那里,业务规则可能会受到接口调用引起的状态更改的影响,因此需要检查调用之前的状态,并用于预测接口调用的预期结果。就接口函数放置顺序而言,相关的业务规则(由相应的验证器体现)如下:规则:直觉上,我们由此推断,如果客户具有56L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41参与);并且如果客户不具有所需状态,则接口调用将仅在订单不包含特色产品的情况下才成功。此外,我们需要检查产品是否确实存在于数据库中,以及客户ID是否有效,以便执行操作。对于这些规则,我们创建SQL查询,并将查询结果转换为验证每个规则的布尔值:product_exists(Connection,PrCode)->[]=/= db:process_query(Connection,“SELECT codeFROMproduct WHEREcode=”++integer_to_list(PrCode))。customer_exists(Connection,Id)->[] =/= db:process_query(Connection,Id)“SELECTid FROMcustomer WHEREid=”++i n t e g e r _to_list(Id))。featured_product(Connection,PrCode)->[PrCode] == db:process_query(Connection,“SELECT codeFROMproduct WHEREcode=”++integer_to_list(PrCode)正++“和 code IN“++验证CodeList>)。gold_customer(Connection,Id)->[Id]==d b :process_query(Connection,“SELECT idFROMcustomerWHERE id=”++integer_to_list(Id)++“ANDstatus='gold'”)。上面的一些查询很可能等同于业务逻辑层中的接口函数。可以选择重用这里的接口函数,但它也有分离关注点和在测试规范中指定约束的价值。特别是,如果不同的人编写测试规范和业务逻辑层,则有额外的可能性来识别失败。现在我们在执行接口函数之前评估所有需要的约束. 然后,我们检查我们得到的结果是否被条件的值所place_order(Id,PrCode)->PE =product_exists(PrCode), CE=customer_exists(Id),FP=featured_product(PrCode),L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)4157------GC= gold_customer(Id),Result = order_facade:place_order(Id,PrCode),caseOrderIDwhen is_integer(Result)和PE和CE和(不是FP或GC)->结果;错误,不是_gold_customerwhen FP and notGC-> Result;error,non_existing_product如果不是PE->结果;错误,不存在客户如果不是CE->结果;_ -> exit(unexpected_value)end.我们实现的情况下,得到的结果,而不是条件的组合区分。一方面,这减少了代码量,因为我们只需要为每个可能的返回值提供一个替代方案。另一方面,我们指定了与实现业务逻辑层时所做的不同的内容。如果代码是不同的,我们犯同样错误的可能性就较小。但比上述两个原因更重要的是,我们不必为处理双重故障的(通常)未指定的实现而烦恼;如果产品和客户都不存在,我们就不知道产生了哪个错误。通过查看结果,无论产生两个错误消息中的哪一个,我们都接受它。请注意,首先,我们在计算结果之前验证接口调用,因为接口调用可能会导致状态更改。其次,要意识到接口函数是(通常应该是)嵌入到它们自己的数据库事务中的,使每个调用都是原子的,从而避免在并发环境中运行系统的任何问题。这也适用于我们的测试环境,但是用于在线商店的数据库和我们考虑的案例研究的数据库都不支持嵌套事务。有人可能会说这种测试是危险的,因为数据库可能会在验证状态和调用接口函数之间发生变化。因此,必须顺序执行这些测试,在验证和调用接口调用序列时,只有一个客户机查询数据库。从我们的观点来看,如果所有的接口调用确实都嵌入到它们自己的事务中,那么这种顺序测试就不是真正的障碍,因为在真实的情况下,我们不会查询数据库来预测结果。 当然,在并发设置中可能存在使用此方法无法发现的问题,可以建议使用其他策略(如负载测试)来测试这些问题。58L.M. Castro,T.Arts/Electronic Notes in Theoretical Computer Science 271(2011)41--4.6QuickCheck属性最后,我们需要为QuickCheck指定一个属性,以使所有这些都能工作。这个属性说明了随机命令序列应该使用4.1.6节中描述的命令生成器生成,并且对于其中的每一个,在执行测试序列之前和之后都要检查数据库不变式(在这种情况下,是2.2节开头所述的业务规则):如果它在结果系统状态中保持不变,则测试通过。prop_business_logic()->?FORALL(Cmds,commands(?MONGOD),begintrue= invariant(),_,_,Res = run_commands(?MOST,Cmds),PostCondition =invariant(),clean_up(S),PostCondition和Res == okend)。在测试开始之前检查数据库不变量是否已填充的原因是为了能够在已经填充了某些数据的数
下载后可阅读完整内容,剩余1页未读,立即下载
cpongm
- 粉丝: 4
- 资源: 2万+
上传资源 快速赚钱
- 我的内容管理 收起
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
会员权益专享
最新资源
- zigbee-cluster-library-specification
- JSBSim Reference Manual
- c++校园超市商品信息管理系统课程设计说明书(含源代码) (2).pdf
- 建筑供配电系统相关课件.pptx
- 企业管理规章制度及管理模式.doc
- vb打开摄像头.doc
- 云计算-可信计算中认证协议改进方案.pdf
- [详细完整版]单片机编程4.ppt
- c语言常用算法.pdf
- c++经典程序代码大全.pdf
- 单片机数字时钟资料.doc
- 11项目管理前沿1.0.pptx
- 基于ssm的“魅力”繁峙宣传网站的设计与实现论文.doc
- 智慧交通综合解决方案.pptx
- 建筑防潮设计-PowerPointPresentati.pptx
- SPC统计过程控制程序.pptx
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功