【翻译】如何写可维护性好的自动化验收测试

来源:互联网 发布:day one windows 编辑:程序博客网 时间:2024/04/29 09:45
原文链接:http://cwd.dhemery.com/2009/11/wmaat,看完这篇文章感觉挺有用,纯手打勉强翻译个意思吧。

测试自动化脚本是软件开发

测试自动化脚本是软件开发.这个原则意味着我们所知道的关于编写软件的许多知识也适用于测试自动化。然而没有开发软件经验的测试人员对这个的感触并没有那么深刻。
开发软件编写完成后,需要投入到维护的精力的多少,是区分好的测试代码结构的关键因素。一些组织或个人费了很大劲搭建起自动化测试,用了几个月就废弃掉了,当我问是什么导致他们放弃测试自动化,最常见的答案是,测试代码太他喵的难维护了。随便改个鸡毛按钮的位置,测试结果都一片血红,把测试代码改pass的功夫,老子都手工测完八遍了。
但一些公司成功的用自动化测试。他们就没有代码维护成本吗?当然啦,你跺你也麻呀。其中一个重要的区别是,好的代码结构考虑到可能的变数,而不是等代码都跑飞了,然后才幡然醒悟,这块实现变了,操,所有的代码都得改!好的测试开发人员,明白自动化测试类似开发一个软件,涉及到维护成本,时刻注意代码的可维护性。
通常有两种情况下我们需要维护测试脚本,新需求或系统实现的变更。都有可能导致n条case失败,如果不能及时的同步更新自动化脚本,跑出来的测试结果也没有任何意义,我怎么判断哪些失败是真的bug。
我们不能阻止产品经理一拍脑门灵感泉涌上需求,也不能阻止开发大哥菊花一紧大刀阔斧玩重构,唯一能做的就是保持测试代码结构的灵活,提高可维护性以应对各种诡异的情况。
通过含着眼泪改代码总结出来的教训,两个蛋疼的习惯让代码变得难以维护,冗余的细节和代码重复。

验收测试测的是什么
验收测试本质就是对系统的功能点进行访问,判断是否实现了该有的功能。不管用什么技术手段去实现这个访问。
假设我们现在需要测试一个系统的账户创建功能。命令create创建一个账户,输入参数username和password。账户创建的一个功能点就是验证密码的合法性,即接受合法的密码拒绝非法的密码。合法密码的规则为6-16个字符长度,至少包含一个数字,一个字母和一个符号字符。如果密码合法,系统返回"Account Created”,密码非法,返回 "Invalid Password “。
不管这个功能怎么呈现给用户,web也好,GUI app也好,还是通过命令行,又或者是一只染着杀马特发型的草泥马会吐输入非法密码的用户一脸口水,验证密码的合法性是这个功能的本质

冗余的细节
下面是一个RF testcase例子,测试create的密码校验功能。
*** Test Cases ***The create command validates passwords     ${status}= Run ruby app/cli.rb create fred 1234!@$^     Should Be Equal ${status} Invalid Password     ${status}= Run ruby app/cli.rb create fred abcd!@$^     Should Be Equal ${status} Invalid Password     ${status}= Run ruby app/cli.rb create fred abcd1234     Should Be Equal ${status} Invalid Password     ${status}= Run ruby app/cli.rb create fred !2c45     Should Be Equal ${status} Invalid Password     ${status}= Run ruby app/cli.rb create fred !2c456     Should Be Equal ${status} Account Created     ${status}= Run ruby app/cli.rb create fred !2c4567890123456     Should Be Equal ${status} Account Created     ${status}=  Run  ruby app/cli.rb create fred !2c45678901234567     Should Be Equal  ${status}  Invalid Password

这是一条存在许多问题的case,最大的问题就是可读性。除了第二行的casename,The create command validates passwords表明了目的是create的密码校验功能,后面的一片代码很难直观的表述出在干嘛。
通过阅读3,4行,我们了解到密码是1234!@$^,赋值给变量status,然后校验结果为Invalid Password。那中间的这些Run ruby fred 之类的玩意和密码校验有什么毛线关系?这些就是冗余的细节。
冗余的描述破坏代码的可维护性,假设现在我们的安全部门提出,6个字符长度的密码太短了,最小的密码长度改为10字符,这条case要做哪些修改?并不能很容易的找出来。
这是因为testcase没有明确描述出测的是哪个功能。当需求变更的时候,很难快速的找到那些部分修改。冗余的信息降低了可维护性。
所以提高这条case的可维护性就是屏蔽掉杂七杂八的“噪音”,直接可以看到case覆盖的功能。这条例子,大部分描述都是怎么运行一条cli ruby命令调用create 参数username和password分别为fred和abcd!@$^。最后RF再把返回赋值给变量status。谢特!下一行稍微好理解一点,判断返回的status是否是Invalid Password。
RF允许我们通过创建keywords来简化这一过程,一个keywords相当于测试执行中的一个步骤。下面我们来创建一个keyword。
那么keyword咋写捏?假设并不知道系统是怎么实现这个功能,但是我们知道这个SUT的的功能点是create account,创建账户要提交username和password,所以我们的这步就可以写成:
      Create Account fred 1234!@$^
然后我们看下一步,假设我们并不知道系统是怎么实现这个功能,怎么写这一步骤呢?生撸呗:
      Status Should Be Invalid Password
现在前两步就变成这个德行:
      Create Account fred 1234!@$^      Status Should Be Invalid Password
好点了哈,没有那些多余的信息,两个步骤之间的联系变的清晰点了,应该返回密码是合法的。
如果我们现在执行测试,肯定会失败,因为RF并不认识这两个keyword,我们需要实现它:
*** Keywords ***Create Account ${user_name} ${password}    ${status}=  Run  ruby app/cli.rb create ${user_name} ${password}    Set Test Variable   ${status}    Status Should Be ${required_status}    Should Be Equal  ${status}  ${required_status}

一个原则就是,testcase应该专注于what而不是how,把如何实现提出到关键字中。testcase变的清晰易懂。我们用新的keyword重写上面那条case。
** Test Cases **The create command validates passwords    Create Account fred 1234!@$^    Status Should Be Invalid Password    Create Account fred abcd!@$^    Status Should Be Invalid Password    Create Account fred abcd1234    Status Should Be Invalid Password    Create Account fred !2c45    Status Should Be Invalid Password    Create Account fred !2c456    Status Should Be Account Created    Create Account fred !2c4567890123456    Status Should Be Account Created    Create Account fred !2c45678901234567    Status Should Be Invalid Password


代码重复
两个关键字的代价让我们的case变的简洁点了。但是还有个问题就是代码重复,我们要做的事密码校验,一个用户名fred每次都跑出来搞什么鸡毛。每两行就是输入密码和校验结果的动作,不同的只是输入的密码和期望的结果,其他的都是重复的代码。
回头看我们的代码,case要覆盖的功能点是什么,密码校验,而密码校验的核心,接受合法密码和拒绝非法密码。我们再创建两个keywoed
** Keywords **Accepts Password ${valid_password}    Create Account arbitraryUserName ${valid_password}    Status Should Be Account CreatedRejects Password ${invalid_password}    Create Account arbitraryUserName ${invalid_password}    Status Should Be Invalid Password

新的keywords不仅让我们可以简单的复用,更重要的是它描述了每一步测试的目的。我们用新的keyword再重写上面这条case。
** Test Cases **The create command validates passwords    Rejects Password 1234!@$^    Rejects Password abcd!@$^    Rejects Password abcd1234    Rejects Password !2c45    Accepts Password !2c456    Accepts Password !2c4567890123456    Rejects Password !2c45678901234567

变量命名
现在可以很直观的看出,这个testcase是测试接受和拒绝不同的password,但是看这些数据并不知道非法在哪儿。通过创建变量,我们可以给它一个一个显示的描述,例如:
** Variables **${aPasswordWithNoLetters} 1234!@$^

很直观的就知道这数据是测试不含英文字符的非法密码。全部命名之后,我们的testcase就长这样。
** Test Cases **The create command validates passwords    Rejects Password ${aPasswordWithNoLetters}    Rejects Password ${aPasswordWithNoDigits}    Rejects Password ${aPasswordWithNoPunctuation}    Rejects Password ${aTooShortPassword}    Accepts Password ${aMinimumLengthPassword}    Accepts Password ${aMaximumLengthPassword}    Rejects Password ${aTooLongPassword}

再进一步,我们针对不同的方面把它分解成多条case。
** Test Cases **Rejects passwords that omit required character types    Rejects Password    ${aPasswordWithNoLetters}    Rejects Password    ${aPasswordWithNoDigits}    Rejects Password    ${aPasswordWithNoPunctuation}Rejects passwords with bad lengths    Rejects Password    ${aTooShortPassword}    Rejects Password    ${aTooLongPassword}Accepts minimum and maximum length passwords    Accepts Password    ${aMinimumLengthPassword}    Accepts Password    ${aMaximumLengthPassword}

现在我们在来读这条case,只需要瞟一眼就能明确知道覆盖的功能点是什么。如果现在有关于密码长度的需求变更,几分钟就可以找到修改的地方。那如果系统实现层面发生变更呢?我们假设代码被全部推翻重写,不用命令行了,变成了一个web界面包含用户名密码两个输入框和一个create的按钮。如果是我们最初的代码就全废了。而现在我们只需要修改两个关键字,又可以欢快的跑起来了。
Create Account ${username} ${password}    Go To    http://localhost:4567/create    Input Text    username    ${username}    Input Text    password    ${password}    Submit FormStatus Should Be ${required_status}    ${status}=          Get Text    status    Should Be Equal     ${required_status}  ${status}

将实现封装的底层的关键字,和testcase中的数据进行分离,系统实现有变化的时候,只需要修改底层的关键字,case中的数据还是不变的。

0 0
原创粉丝点击