DSL与自动化测试 – 用Python实现简单的DSL

自动化测试,一个在测试领域中被广为熟知,也是被谈论最多的概念之一。DSL (Domain Specific Language),一种高度抽象,用于某个特定领域下编程语言。软件测试在大多数情况下都是对某个特定行业的软件系统进行测试,所以这两者应该可以很好的结合起来,事实上也是这样的,QTP里面的keyword view,其实就是DSL的一个实现。DSL一般可以分为两个大的类型,分别是External DSL 和 Internal DSL (引用自Martin Fowler)。External DSL 一般来说是跟其实现语言不一样的 DSL,常见的External DSL 有:SQL和XML配置文件;而Internal DSL 一般来说就是该DSL使用某个现成的编程语言(就是所谓的host language),然后对host language进行一些改造而成。

我们在测试中会遇到很多问题,其中一些问题,几乎是所有公司所有团队都会遇到的,例如测试覆盖率不够,测试的时间不够等等。面对这些问题,自动化测试自然而然地成为解决这些问题的首选方法。但是自动化测试真的就是银弹麽?不见得!以前曾经在ASP.NET QA 的博客中给他们留言,请教过关于自动化测试的事情,我记得其中有一个回复是说,在某个release中过度地使用自动化测试,一切东西都想实现自动化测试,而忽略了产品本身的功能、特性的关注,结果就是超高的自动化测试覆盖率,但是很差的产品质量。大家都去实现自动化测试了,谁来做功能点的覆盖呢?某些领域的专家(SME),他们可能对测试技术是一无所知的,要把这些领域专家和测试实施结合起来,DSL就是一个比较好的桥梁。

我在工作中遇到的问题是,我需要测试一个类似UV(独立用户访问数)统计的系统,统计UV的方法其实就是根据_uid cookie的值来判断这个用户在某段时间内访问过我们的系统多少次,访问了哪些站点,进行了什么样的行为。其中有2个地方比较麻烦,第一就是在测试过程中要不断地拷贝cookie,这样拷来拷去两三次以后很容易就混乱,出错;第二就是需要记录访问哪些站点,这些站点都只是ID,也是需要不断地修改请求,测试时间长了也是很容易出错。所以我就打算在原来的测试工具基础上,实现一个简单的Internal DSL。先看成品:

@tc
def uniq_inventory_case01():
    test= testTool()
    test.user('a').view('asset55100002').anetwork('55100').onsite('site55100503').snetwork('55100').dnetwork('55100').times(1).go()
    test.user('b').view('asset55100002').anetwork('55100').onsite('site55100503').snetwork('55100').dnetwork('55100').times(2).go()
    test.user('b').view('asset55100002').anetwork('55100').onsite('site55100504_noad').snetwork('55100').dnetwork('55100').times(4).go()

实例化一个testTool对象,然后就是指定哪个用户:user(‘a’)或者user(‘b’),看的视频的ID:view(‘asset55100002’),这个视频属于哪个CRO呢?anetwork(‘55100’);放在哪个网站呢?onsite(‘site55100503’);网站是谁的呢?snetwork(‘55100’);谁是分发者呢?dnetwork(‘55100’);看了多少次呢?times(4);最后一个有点儿丑陋的go()。

像这样子一句话里面N个方法连着用,就叫Method Chaining,Method Chaining通常可以让代码变得更加人性化,读起来更加容易。但是使用Method Chaining通常会遇到一个问题,就是很难判断就是到了哪个方法才是终结呢?是不是有些方法的调用是可选的,有些是必选的呢?其中一个解决方法就是我用到的,放一个.go()方法在最后,作为终结方法。要实现Method Chaining,其实只需要顶一个类,对于需要做连接的方法,最后都返回这个类的实例。例如:

def view(self, assetid):
    if assetid:     self.asset_id = assetid
    return self

def anetwork(self, networkid):
    if networkid:   self.a_network_id = networkid
    return self

def snetwork(self, networkid):
    if networkid:   self.s_network_id = networkid
    return self

def dnetwork(self, networkid):
    if networkid:   self.d_network_id = networkid
    return self

def onsite(self, sectionid):
    if sectionid:   self.site_section_id = sectionid
    return self

def times(self, times):
    if times>0:       self.request_times = times
    return self

最后一个终结方法go(),就做真正的处理

def go(self):
    if self.asset_id and self.site_section_id and self.times and self.a_network_id and self.s_network_id:
        self.prepareRequest()
        for i in range(self.request_times):
            self.sendRequest()

        self.cleanup()
    else:
        info = 'Required information missing, abort running.'
        logging.debug(info)
        print info

如果是实现一个External DSL 的话,的确难度不小;但是Internal DSL其实并不是很高深,也不是很难实现,在它的帮助下,可以把工作完成的更好,对自己以后维护测试用例也带来了不少方便。

自动化测试中的sleep

最近在修改公司现有的一个自动化测试框架,里面用了很多time.sleep()方法,看着不是很爽,为什么我觉得sleep方法在自动化测试中不应该过多的使用呢,我甚至觉得应该尽可能避免sleep方法的使用,sleep可以作为增加自动化测试稳定性的手段,但是不能依赖sleep来让自动化系统稳定。

举个例子,如果一个UI的自动化测试,需要等待某个页面load完成以后才进行操作,那么需要对那个页面是否已经Load完成进行判断,而不应该sleep(x),x是一个magic number,有时候1、2秒就足以,有时候它却不知道有多大,因为已经超时了!那如果我们在check页面状态之前做一个短时间的sleep,那么在某些场合下可以增加这个自动化测试的稳定性,但是最终整个自动化测试的脚本是不会依赖于这个sleep的语句来达到稳定的。

在做自动化测试的时候,最常见的两种判断就是1. 某程序已经成功启动,某页面已经加载完毕。 2. 某程序已经正常关闭,某服务已经顺利停止。

回到实际的工作,我要判断被测的程序是否已经正常启动,可以用系统提供的一些工具,或者调用一些接口,例如SNMP命令,或者是调用一下Lua脚本等,如果他们都返回我们期望的数据,那么可以认为程序已经成功启动了。反之,如果前面的这些命令出错了,那么我也可以认为程序已经是关闭了的。

要实现自动化测试,就必须要让测试代码每时每刻都掌握着被测系统的状态,sleep方法会让自动化测试脚本的行为变得诡异

自己动手创建Web测试验证规则

”Web测试”是由一系列 HTTP 请求组成,通过发出 HTTP 请求在协议层工作的测试类型。在VSTS中自带了若干个预先定义好的验证规则,例如在返回的页面上寻找某些字符,返回的文档中是否包含某些Tag,等等。前一段时间在测试一个安全过滤器,这个过滤器的基本功能就是过滤用户输入中的一些有可能构成安全隐患的内容,例如<script>标签的内容,JS内容等等。按照预先给出的测试用例执行完测试以后,OK,没有问题,不过在上线一个小时后发现一个问题,就是经过安全过滤器输出的内容会比原来输入的内容多了两个字符–“rn”,这个问题一下就修复好了,Trim一下。然后我就要给原来的测试加上对于这个问题的回归测试实现。很不巧,Web测试没有一个对返回内容长度进行验证的验证规则,不过微软提供了创建自定义验证规则的接口,我们可以创建一个验证ContentLength的规则。

1. 创建一个新的类库项目,这个项目是一个独立的可重用的类库,所创建的Web测试插件可以在不同的包含有Web测试的项目中使用。其实可以跟上一篇文章说的WebTest Plugin公用一个类库项目

2. 在该类库项目中添加对“Microsoft.VisualStudio.QualityTools.WebTestFramework”的引用

3. 在该类库项目中创建一个从ValidationRule派生出的类

4. 重写基类的Validate方法

5. 打开包含有Web Test的项目,并且在该项目中引用刚才添加的包含有自定义验证规则的类库项目

6. 打开需要调用自定义验证规则的Web测试,点击”Add Validation Rule”,选择刚才编写好的验证规则。

现在看看在自定义验证规则中添加参数,让用户输入相应的参数,从而使得验证规则更加灵活。
Continue reading “自己动手创建Web测试验证规则”

在VSTS中创建Web Test的插件

做过单元测试的朋友都知道,几乎每一个单元测试的框架都提供了类似于TestInitialize、TestCleanup这样的操作,可以在测试的开始和完成测试以后让我们处理一些问题,例如初始化一些数据,或者销毁一些数据等操作。在VSTS中有一种测试类型叫”Web测试”,它由一系列 HTTP 请求组成,通过发出 HTTP 请求在协议层工作。Web测试没有单独的TestInitialize、TestCleanup操作,但是VSTS所提供的Web Test插件能够帮助我们完成这样的工作。以下是MSDN对Web测试的定义:

使用 Web 测试插件,可以隔离代码并在 Web 测试中的主要声明性语句外部重用代码。自定义的 Web 测试插件为在运行 Web 测试时调用某些代码提供了途径。在每个测试迭代中,Web 测试插件都要运行一次。此外,如果重写测试插件中的 PreRequest 或 PostRequest 方法,这些请求插件将分别在每个请求之前或之后运行。

可见Web测试插件给使用者带来了很大的灵活性,下面就看看如何创建一个Web测试插件。

1. 创建一个新的类库项目,这个项目是一个独立的可重用的类库,所创建的Web测试插件可以在不同的包含有Web测试的项目中使用。

2. 在该类库项目中添加对“Microsoft.VisualStudio.QualityTools.WebTestFramework”的引用,该Dll是在.NET选项卡上

3. 在该类库项目中从WebTestPlugin派生出一个自定义的Web测试插件类

4. 重写相关的基类方法,例如 PreRequest、 PreWebTest、 PostWebTest等

5. 打开包含有Web Test的项目,并且在该项目中引用刚才添加的包含有自定义Web测试插件的类库项目

6. 打开需要调用Web测试插件的Web测试,点击”Add Web Test Plug-in”,选择刚才编写好的Web插件,完成。
Continue reading “在VSTS中创建Web Test的插件”

实现数据驱动的CodedUI Test

昨天介绍了如何创建一个简单的CodedUI Test。我们也知道,依靠录制回放产生的自动化测试是非常不可靠的,那些在微软的大牛们肯定也早就知道了。虽然用VSTS录制一个自动化测试脚本的过程不是那么友好,也不是很方便,不过它产生的测试代码修改起来还是比较容易的。下面我们就看看如何把一个简单的CodedUI Test改造为数据驱动脚本。

对于每一段录制的操作,VSTS都可以把它抽象成一个方法,它会把这些操作以静态方法的形式存放在一个叫RecordedMethods的类里面。可以对这些方法做任意的修改,我就把给需要输入的方法增加一个输入的参数。

public class RecordedMethods
{
	public static void FirstClick(TestContext testContext, string first)
	{
		//第一个输入
	}

	public static void Operation(TestContext testContext, string operater)
	{
		//操作符
	}

	public static void SecordClick(TestContext testContext, string second)
	{
		//第二个输入
	}

	public static void GetResult(TestContext testContext)
	{
		//按那个等于号
	}
}

Continue reading “实现数据驱动的CodedUI Test”

VSTS2010的一个新功能–CodedUI Test简介

VSTS2010 Beta 1 终于出来了,安装的体积不大,只有1.22GB。之前看了很多VSTS2010的新特性,尤其是测试方面的,非常期待,昨天从MSDN下下来以后,失望了一下,因为很多新功能都是基于TFS2010的,我没有装,所以很多新功能都体验不了。例如让人很激动的Lab Manager,这个需要TFS,还有支持虚拟化技术的CPU。

如果你对VSTS的测试员版本感兴趣,而又没有TFS2010,只装了VSTS2010 BETA 1,那么就只能体验一下VSTS2010的一个新功能–CodedUI Test。微软在VSTS2010以前的版本都不太重视手工测试和功能测试的支持,估计是因为Visual Studio本来是一个集成开发环境的原因吧,不过到了2010,情况完全不一样了,微软想把VS改造成为一个贯穿整个ALM(Application lifecycle management)的主要工具,所以在VSTS2010中加强了对测试计划,测试用例,相关报告等的支持,CodedUI Test就是面向功能测试工程师,给他们提供自动化测试支持的这么一个新功能。

下面我一步步演示一下怎么用CodedUI Test来对WINDOWS自带的计数器实现简单的自动化功能测试。

1. 新建一个测试项目,这个步骤与前几个版本的VS一样,就不重复累赘了。

2. 在该测试项目中新建一个CodedUI Test,如图所示

add-coded-ui-test

3. 当CodedUI Test被创建以后,VS会提示用户,是否立刻创建相关的自动化测试代码,如图所示:

after-create-options
Continue reading “VSTS2010的一个新功能–CodedUI Test简介”

自动化单元测试要点

用单元测试的框架MSTEST,做单元测试,集成测试快1年了,总结一下工作中学到都东西。

单元测试,集成测试有什么用?

1. 改进产品质量

软件测试,很多时候围绕着两个问题:

Verification和Validation,常说的双V。前面的Verification就是Is the software built correctly?。后面的Validation就是Have we built the right software?

单元测试,更多的是Verification。所以有时候经过我单元测试和集成测试以后的功能模块,在交给其他同事做功能测试的时候,依然会有一些BUG,这时候开发可能会埋怨我测试得不够完全,诸如此类。但是其实很多时候,我的关注点是单个方法的功能、行为,没有站到更高的位置来测试这个模块。对于这样的问题,开发和测试应该互相体谅,我本人也会提高自身的水平。希望在单元测试和集成测试中也加入更多关于Validation的思考。
Continue reading “自动化单元测试要点”

单元测试的坏味道

Martin Fowler有一本很出名的书《重构》,里面有个很出名的概念,Code Smell。前阵子我也刚发现一本很好的书,《XUnit Test Patterns》。这本书主要讲的是如何重构测试代码,这里的测试代码指的就是自动化测试的代码,再进一步细化就是单元测试为主的自动化测试代码的重构。由于此书已经让清华大学翻译烂了……所以建议大家下载英文版。

所谓的测试的坏味道,有三种:

  1. 项目(Project)
  2. 行为(Behavior)
  3. 代码(Code)

按照《重构》书中提出的坏味道的概念来看,如果说是有坏味道(Smell),那么大多数都是指代码级别的。什么的坏味道?就是程序有可能会有大问题的一种征兆(symptom)。个人理解就是不能被直接观察到的现象,可以称之为坏味道。

对于自动化测试代码来说,如果有了项目级的坏味道,通常变现就是生产环境出现了BUG。如果出现这样的情况,那么自动化测试代码就不是有味道那么简单了,而是有问题了,自动化测试的安全网没有能够抓住这个BUG。
Continue reading “单元测试的坏味道”

自动化测试中需要避免“诡异”的测试脚本

什么是“诡异”的测试脚本呢?就是这个脚本在多次运行的情况下,测试结果并非每次都是一致,例如运行10次,有9次是通过的,有1次是失败的,那么这个测试究竟算是通过呢,还是失败呢?答案是:It depends

在某些情况下,那一次失败,的确抓住了程序的某个BUG。原因可能是:

  • 程序在运行一段时间后会出错
  • 程序会出错,但错误不是每次都发生
  • 在某些特殊输入下把错误暴露出来

但是在某些情况下,那1次失败的测试其实只是告诉测试的人,你写了一个“诡异”的测试脚本,实现了一个诡异的测试(Flakey Test)。不幸的是,导致一个自动化测试的行为变得诡异的原因实在太多太多。下面是一些常见的:

  • 竞争条件
  • 测试数据选择不当
  • 测试的前置条件不受控

当发现一个诡异的自动化测试脚本,必须要下决心去改掉它:

  • 使用一些常见有效的用例设计模式,例如“准备,执行,断言(Arrange, Act, Assert)”
  • 使用MOCK技术

总之,当出现一个诡异的测试(Flakey Test)的时候,总是很郁闷的,但是为了以后不要遇到更大的郁闷,我们要尽早发现并且改掉那些诡异的测试。

开源自动化测试框架WatiN 2.0 CTP2已经发布

WatiN是一个开源自动化测试框架,在一些中小型的项目中可以取代昂贵的商业工具,例如QTP。今天WatiN发布了第二个社区预览版(CTP)。这个版本的详细release note还没有出来,但是我已经发现了一个非常好的改进,就是抽象出了IBrowser接口,IE和Firefox这两个类都实现了IBrowser接口,真的可以实现写一次代码,在若干个不同的浏览器中运行自动化测试了。

写一个简单的方法,这个方法要做的就是浏览MySpace聚友网的主页,然后检查一下有没有“关于我们”这段文字(但是即使不存在也没事,这个例子只是意思意思)。
Continue reading “开源自动化测试框架WatiN 2.0 CTP2已经发布”