cucumber是ruby世界的bdd框架,开发人员主要与两类文件打交到,feature文件和相应的step文件。feature文件是以feature为后缀名的文件,以given-when-then的方式描述了系统的场景(scenarios)行为;step文件为普通的ruby文件,feature文件中的每个given/when/then步骤在step文件中都有对应的ruby执行代码,两类文件通过正则表达式相关联。笔者在用cucumber watir做回归测试时对cucumber工程的目录结构执行过程进行了研究。
安装好cucumber后,如果在终端直接执行cucumber命令,得到以下输出:
输出结果表明:cucumber期待当前目录下存在名为features的子目录。建好features文件夹后,重新执行cucumber命令,输出如下:
cucumber运行成功,但由于features文件夹下没有任何内容,故得到上述输出结果。
网上大多数关于cucumber的教程都建议采用以下目录结构,所有的文件(夹)都位于features文件夹下。
feature文件(如test.feature)直接位于features文件夹下,可以为每个应用场景创建一个feature文件;与feature文件对应的step文件(如test.rb)位于step_definitions子文件夹下;同时,存在support子文件夹,其下的env.rb文件为环境配置文件。在这样的目录结构条件下执行cucumber命令,会首先执行env.rb做前期准备工作,比如可以用watir新建浏览器窗口,然后cucumber将test.rb文件读入内存,最后执行test.feature文件,当遇到given/when/then步骤时,cucumber将在test.rb中搜索是否有相应的step,如果有,则执行相应的ruby代码。
这样的目录结构只是推荐的目录结构,笔者通过反复的试验得出了以下结论:对于cucumber而言,除了顶层的features文件夹是强制性的之外,其它目录结构都不是强制性的,cucumber将对features文件夹下的所有内容进行扁平化(flatten)处理和首字母排序。具体来说,cucumber在运行时,首先将递归的执行features文件夹下的所有ruby文件(其中则包括step文件),然后通过相同的方式执行feature文件。但是,如果features文件夹下存在support子文件夹,并且support下有名为env.rb的文件,cucumber将首先执行该文件,然后执行support下的其它文件,再递归执行featues下的其它文件。
比如有如下cucumber目录结构:
为了方便记录cucumber运行时的文件执行顺序,在features文件夹下的所有ruby文件中加上以下代码:
puts file.basename(__file__)
此行代码的作用是在一个ruby文件执行时输出该文件的名字,此时执行cucumber命令,得到以下输出(部分)结果:
上图即为ruby文件的执行顺序,可以看出,support文件夹下env.rb文件首先被执行,其次按照字母排序执行c.rb和d.rb;接下来,cucumber将features文件夹下的所用文件(夹)扁平化,并按字母顺序排序,从而先执行a.rb和b.rb,而由于other文件夹排在step_definitions文件夹的前面,所以先执行other文件夹下的ruby文件(也是按字母顺序执行:先f.rb,然后g.rb),最后执行step_definitions下的e.rb。
当执行完所有ruby文件后,cucumber开始依次读取feature文件,执行顺序也和前述一样,即: a.feature --> b.feature --> c.feature
笔者还发现,这些ruby文件甚至可以位于features文件夹之外的任何地方,只是需要在位于features文件夹之内的ruby文件中require一下,比如在env.rb中。
/** * return an elements child given the key (context id), or uses the selector * to get the element. * * @param sel * @param key * element id. * @return {@link androidelement} * @throws elementnotfoundexception */ public androidelement getelement(final uiselector sel, final string key) throws elementnotfoundexception { androidelement baseel; baseel = elements.get(key); uiobject el; if (baseel == null) { el = new uiobject(sel); } else { try { el = baseel.getchild(sel); } catch (final uiobjectnotfoundexception e) { throw new elementnotfoundexception(); } } if (el.exists()) { return addelement(el); } else { throw new elementnotfoundexception(); } } |