自动化测试的分层结构

摘要

\

在测试自动化中,测试代码中不仅仅包含测试逻辑,还包含许多其他代码,比如URL拼接、html/xml解析、访问UI控件,等等。若把测试逻辑与这些无关代码混在一起,测试逻辑将会很难理解, 也不容易维护。本文会介绍如何用分层结构来解决测试自动化中遇到的这些问题。在这个分层结构中,测试自动化代码会被分成三层:(1)测试用例层,表达应用程序的测试逻辑。(2)领域层, 用业务领域术语来给待测系统建模,封装HTTP请求、浏览器控制、结果解析逻辑等,给测试用例层提供一个接口。(3)待测系统层,第2层构建在这一层之上。

\

问题

\

QA的工作包括设计测试用例、探索性测试(exploratory testing)及回归测试,等等。这些工作有的依靠QA的聪明才智, 而有的却只是重复劳动(例如回归测试)。随着系统中不断地加入新功能,回归测试这类工作耗费的时间也越来越多。

\

测试自动化可以解决这个问题。测试自动化后,重复性的劳动会由计算机来做,而测试用例都用计算机程序来表述, 因此QA可以从重复劳动中解脱出来,有更多的时间用在创造性的工作上来。

\

在测试自动化中,测试代码并不仅仅包含测试逻辑,也包含许多其他的支撑代码,例如URL拼接、HTML/XML解析、UI控件访问等。 例如要测试一个能接受不同搜索参数,并返回包含特定信息的XML(例如用户数据)的web服务,测试代码需要:

\
  1. 根据待测操作拼接URL\
  2. 使用HTTP库发起HTTP请求\
  3. 读取web服务器返回的信息,并解析数据\
  4. 对比返回的数据与期望数据\

自动化测试的分层结构

\

有的测试自动化代码,会把URL拼接、HTML/XML解析、XPath表达式,和测试逻辑写在一起,通常在同一个类或方法中。

\

这种方法很容易入手,且很直观,因为其反映了测试人员手工测试的过程。但是这种方法存在一定的问题:

\
  1. 测试逻辑难以理解及修改。当测试逻辑与一大堆无关代码混在一起时,很难辨别出测试逻辑。 要添加新测试用例,通常需要重读这些支撑代码才能找到需要修改的代码。测试逻辑也会很难理解。\
  2. 测试变得很脆弱。因为测试逻辑和html解析等支撑代码混在一起,待测系统和自动化测试直接的‘契约’若稍有变化, 自动化测试将无法运行。例如,若UI发生变化,比如把input元素挪到另一个div元素下, 或者改变某个UI元素的ID,所有相关的测试自动化代码都会受到影响。\
  3. 维护开销大。一组完备的测试用例会对系统的某个部分进行多组测试,而每组测试间都会存在重复的代码。例如这些代码可能都要 (1)根据待测操作拼装URL,(2)发出HTTP请求,(3)解析web服务器返回的信息,(4)比较实际结果及期望结果。因为在各个测试用例间存在 重复代码,如果这个过程发生任何改变,则需要修改各个测试用例的代码。\

解决方法

\

软件开发领域曾遇到过同样的问题,并找到了解决方法,即‘层次结构’(Layered Architecture)。引用《领域驱动设计--软件核心复杂性应对之道》('Domain-driven design: tackling complexity in the heart of software')一书:

\
“分层结构的价值在于每一层只关注于程序的特定方面。这使得每个方面的设计都很紧凑,也更容易理解。当然,使用层次结构的最重要原因是把各个重要的方面都分隔开。“
\

虽然测试自动化领域关注的是测试领域,但是所遇到的问题的本质却是一样的,因此可以应用相似的解决方案:

\
测试用例层 这一层包含所有(并只有)测试逻辑。有了下一层即领域层帮忙,测试逻辑可以很清晰、简洁地表达出来。不同用户故事、场景及边界条件 都构建领域层之上,区别只在于测试数据。
领域层 这一层封装了对待测系统的所有操作,例如URL拼接、XML或HTML解析,富客户端或浏览器的控制,等等。通过这一层包装, 待测系统可以以业务领域语言的形式供调用者使用,而非以xpath、sql或者html等技术“语言”形式。这层的目的在于提高抽象层次。 测试的目的是验证业务逻辑是否实现地正确。若测试能用业务领域的语言编写,那么测试目的就一目了然了。
待测系统层 即要测试的系统

自动化测试的分层结构

\

测试用例层包含许多测试用例。这些测试用例都是基于领域层的。领域层用领域语言封装了待测系统。
\领域层直接访问待测系统。

\

例子

\

假设我们要测试一个restful web服务。通过这个web服务,我们可以用电话作为关键字搜索客户信息。

\

要调用这个web服务,需要发起以下格式的HTTP请求:

\
\    http://{endpoint}/subscribers?telephoneNumber={telephoneNumber}\    
\

服务端返回的以竖线分割的数据包含客户的姓名、电话、地址及其他信息:

\
\    13120205504|ST|C|SQ|112|||FIRST|ST|W|Riverfront|BC|010|68930432|\    
\

测试这个服务的用例为:(1)用能精确匹配一个用户的电话作为关键字搜索,(2)用能精确匹配多个用户的电话作为关键字搜索,(3)用 不完整电话作为关键字搜索等。用例的完整程度完全取决于QA的想象能力。

\

对于每个测试用例,执行的数据基本上都一样:(1)拼装包含电话号码关键字的URL,(2)用HTTP库发出HTTP GET请求,(3)解析数据, (4)把真实值与期望值做比较。为了避免上面提到的问题,我们在这里采用分层结构:

\

测试用例层

\

这一层的具体实现方式与采用的测试框架有关。在这个例子中,我们采用C#及NBehave

\
\    [Story]\    public class SearchCustomerbyTelephoneNumberStory: TestBase\    {\        [Scenario]\        public void SearchWithAPhoneNumberWhichHasAnExactMatch()\        {\            story.WithScenario(\"Search with a phone number which has a exact match\")\                .Given(AN_ACCOUNT_WITH_PHONE_NUMBER, \"01068930432\