• 【E2E Testing】学习(4):Protractor了解


    时间紧迫,先罗列一些内容

    • Protractor的安装与使用
    • Protractor的语法解释
    • 术语以及原理
    • 一些普遍的JS调试语法

    安装与使用


    API接口

    • by与locator
    • element与elementFinder/
    • promise
    • element.all
    • browser

    基于Protractor编写的测试角度,核心就是找到DOM元素,并与其交互,然后验证其交互结果是否与预期一致(说白了,就是写一个模拟器,模拟用户在网页上的操作动作)。所以查找DOM元素(这还涉及网页的功能逻辑)并与之交互(网页的交互逻辑)非常重要。

    by与locator

    首先是在DOM树中找到目标的DOM元素。可以才用by函数,它提供了多种查找DOM元素的方式。以下面这行代码为例

    by.id('gobutton')
    

    by函数用查找id的方式确定一个locator对象(也就是说,这个locator对象,应该是网页DOM树中、id为‘gobutton’的元素)。

    element与elementFinder

    接下来需要有函数去接收这个locator对象,才能对DOM元素进行交互操作。以下面这行代码为例

    element(by.id('gobutton'))
    

    Protractor提供了一个全局的对象(类或者什么都可以,现在看来都差不多)element,它可以去接收上述的locator对象,并返回一个elementFinder对象。需要说明的是,这个时候对于elementFinder对象无法操作。只有对它发出相关的指令后,才有交互。举个例子:

    element(by.id('gobutton')).click();
    

    该代码在获得elementFinder对象之后,要寻求与其进行交互,交互动作为click事件。除了click方法外,elementFinder对象有很多方法,如

    //--view code--
    <ul class="items">
      <li>First</li>
      <li>Second</li>
      <li>Third</li>
    </ul>
    
    //--testing code--
    element.all(by.css('.items li')).then(function(arr) {
      expect(arr.length).toEqual(3);
    });
    View Code

    promise

    所有elementFinder对象的方法,返回值都是一个promise对象,举个例子:

    getTitleText() {
        return element(by.css('.navbar-brand')).getText() as Promise<string>;
    }
    

    这个函数(方法)封装了elementFinder的getText方法,并返回了promise对象(字符型)。所以当想要验证交互结果的时候,需要对promise对象再进行操作。

    //--view code--
    <ul class="items">
      <li>First</li>
      <li>Second</li>
      <li>Third</li>
    </ul>
    
    //--testing code--
    element.all(by.css('.items li')).then(function(arr) {
      expect(arr.length).toEqual(3);
    });
    View Code

     测试代码先获取样式为item li的所有元素(array对象),然后获取这个对象,再去计算对象包含的元素数量,并判断是否等于3。该代码传递出两个信息:

    1. 可以使用then方法输出值。假设输出的对象(数组、文本、属性值都可以)为object,实际上形式应该是.then(function(object)){...}。花括号内可以对传入的参数(对象object)进行操作。
    2. 直接使用expect方法来操作值。示例代码其实是先输出值,再操作值。但是完全可以将两步合并成为一步。将arr这个数组对象,用element.all(by.css('.items li')).then(function(arr)代替

    element.all

    顺便说说element.all。尽管是element对象的一种方法,但是返回值是elementFinder array,即elementFinder的数组集合。所以,其实这种方法就是另一类elementFinder对象了。它的方法首先是针对array对象的,然后若提取出其中的元素,也可以调用elementFinder的方法。举些例子:

    //--view code--
    <ul class="items">
      <li class="one">First</li>
      <li class="two">Second</li>
      <li class="three">Third</li>
    </ul>
    
    //--testing code--
    //1.filter, actually it is a iteration
    element.all(by.css('.items li')).filter(function(elem, index) {
      return elem.getText().then(function(text) {
        return text === 'Third';
      });
    }).first().click();
    //this paragraph of code would act on each element in the array and find/filter the ones whose text is 'Third'.
    //btw, return the first element and click it
    
    //2.get-->Get an element within the ElementArrayFinder by index.
    expect(element.all(by.css('.items li')).get(0).getText()).toBe('First');
    
    //3.first-->Get the first matching element for the ElementArrayFinder.
    //4.last-->Get the last matching element for the ElementArrayFinder.
    
    //5.count-->Count the number of elements represented by the ElementArrayFinder.
    expect(element.all(by.css('.items li')).count()).toBe(3);
    
    //6.each-->Calls the input function on each ElementFinder represented by the ElementArrayFinder.
    element.all(by.css('.items li')).each(function(element, index) {
      // Will print 0 First, 1 Second, 2 Third.
      element.getText().then(function (text) {
        console.log(index, text);
      });
    });
    //this function is equal to iteration
    
    //7.map-->Apply a map function to each element within the ElementArrayFinder.
    let items = element.all(by.css('.items li')).map(function(elm, index) {
      return {
        index: index,
        text: elm.getText(),
        class: elm.getAttribute('class')
      };
    });
    expect(items).toEqual([
      {index: 0, text: 'First', class: 'one'},
      {index: 1, text: 'Second', class: 'two'},
      {index: 2, text: 'Third', class: 'three'}
    ]);
    //this function reminder me of HashMap?
    
    //8.reduce-->Apply a reduce function against an accumulator and every element found using the locator (from left-to-right).
    let value = element.all(by.css('.items li')).reduce(function(acc, elem) {
      return elem.getText().then(function(text) {
        return acc + text + ' ';
      });
    }, '');
    expect(value).toEqual('First Second Third ');
    //this is like string joint?
    
    more information, see the official document:http://www.protractortest.org/#/api?view=ElementArrayFinder
    View Code

    特别说明的是,对于elementFinder对象(无论是一个对象还是数组对象),都可以用$$(elementFinder array object)/$(elementFinder object)缩写简化(主要是有时候看到这样的缩写,要是不知道的话,就懵了)

    element.all(by.css('.items li')).then(function(arr) {
      expect(arr.length).toEqual(3);
    });
    
    // Or using the shortcut $$() notation instead of element.all(by.css()):
    $$('.items li').then(function(arr) {
      expect(arr.length).toEqual(3);
    });
    

     还有对浏览器的操作browser


    术语以及原理

    • 异步
    • 链式调用

    这两个术语是Protractor框架代码运行以及编写的特点。我不太懂,所以也写一写。 

    异步

    说个不成熟的理解。异步特别像多任务同时进行,具体说,你用element对象的不同方法,又是按照id找,又是按照样式找,又是点击,又是键入数值,这些都可以一起完成。谁先谁后,完全取决于代码执行的速度。那么同步呢?就是一个任务完成后再是另一个任务。(特别有意思的是,我研究生学优化的时候,同步的概念和计算机这里的异步是一个意思)。知乎上,我选了一个很有趣的例子,可以说明这个问题。

    链式调用

    Protractor对象的采用链式调用。一开始觉得这个名词很高深,仔细了解过恍然大悟,其实早有接触过。先说非链式调用,以C#.NET为例,比如

     double val = zero2null2(double.Parse(tb_val.Rows[i][0].ToString()));
    

    ToString是第一个函数,double.Parse是第二个函数,zero2null2是第三个函数。这样阅读代码,是从里往外读,带来一定的不便利,而且书写代码也很麻烦。如果没有事先想好,那么多半是写了里面,而是剪切掉,写外面的函数在粘进来。

    那么链式调用呢?还是之前的代码,假想一下每个方法函数的返回值都返回object对象,有n多种方法,然后就这么写

     double val = tb_val.Rows[i][0].ToString().ToDouble().ToNull().
    

    这样一来,代码不管是阅读还是编写都很流畅。要声明的是,这个理解不专业,但是足够用了。


    普遍的JS语法

    • describe
    • it
    • beforeAll, beforEach, afterAll, afterEach

    看到团队代码里面有这些语法内容,不甚了解。所以也一并记录在这里。

    descibe

    describe是Jasmine的全局函数,Jasmine是JS的一种单元测试框架。describe的参数有两个:1)字符串-作为测试内容名字或者标题;2)包含实现测试的代码

    describ(string, function(){
         //pass  
    }
    );
    

     可以理解成,string就是一个user story的名字,function就是这个user story的故事情节。当然,describe可以嵌套,就是一个大故事里有很多的章节。

     it

    it也是Jasmine的全局函数。it的参数也是两个:1)字符串;2)测试的主体

    it(string, function(){
        //pass
    });
    

    it可以理解成一个故事中的一段实质的故事内容,人物对话,动作行为等等,都是在it中发生。所以it中自有的变量是私有变量,it通常包含在一个describe中。

    expect

    前面介绍Protractor的时候有提到过expect,elementFinder对象发出交互指令后,会得到一个promise对象,这个对象的值无法直接获取(动作无所谓啦),只能通过1)then;2)expect来操作。其中then方法也是取值,只有expect才是方法命令。expect也可以作用在it中,就是对行为的断言,返回结果true/false。关于expect的断言方法还有很多,详见参考资料。https://www.ibm.com/developerworks/cn/web/1404_changwz_jasmine/

    Setup与Teardown:beforeEach,beforeAll,afterEach与afterAll

    在执行测试用例时,会准备大量的工作。建立数据库,建立用户交互场景等等,同时测试完成后,我们也需要释放资源。如果对一个user story(如果很长、很复杂的话)中每个行为都如此准备,显然代码很冗余、模块的复用性也不好。因此可以使用setup和teardown,前者是测试之前的准备工作,后者是测试之后的清理工作。Jasmine中共有4个全局函数来执行这两种操作。

    beforeAll  [在测试套件(describe块)中所有测试用例执行之前执行一遍beforeAll函数]
    beforeEach  [在每一个测试用例(it块)执行之前都执行一遍beforeEach 函数]
    afterEach  [在每一个测试用例(it块)执行之后都执行一遍afterEach 函数]
    afterAll   [在测试套件(describe块)中所有测试用例执行之后执行一遍afterAll函数]
    

    这些函数都只有一个输入参数,就是内容的执行函数。这些函数和it块之间有运行的逻辑先后,理解它很有必要。  

    desribe("Check the priority", function(){
        beforAll(function(){
            console.log('this is beforall');
        });
        beforeEach(function(){
            console.log('this is beforeeach');
        });
        it("It1",function(){
            console.log('I am the 1st it');
        });
        it("It2",function(){
            console.log('I am the 2nd it');
        });
        describe("I am the child story",function(){
            console.log('I am the child story');
        });
        it("It3",function(){
            console.log('I am the 3rd it');
        });
        console.log('I am the parent story');
        afterEach(function(){
            console.log('this is the aftereach');
        });
        afterAll(function(){
            console.log('this is the afterAll');
        });
    });
    View Code

     以上测试代码的结果是:

    I am the parent story
    this is beforall
    this is beforeeach
    I am the 1st it
    this is the aftereach
    this is beforeeach
    I am the 2nd it
    this is the aftereach
    this is beforeeach
    I am the child story
    this is the aftereach
    this is beforeeach
    I am the 3rd it
    this is the aftereach
    this is the afterAll
    View Code

     所以比较清楚了,describe中不属于任何it块或者setup/teardown的内容先运行。然后是beforall。之后从上往下找到第一个it(嵌套的describe这时也属于一个it),前面是beforeeach,再运行it的代码,最后是aftereach的代码。接着再是第二个it,前后分别是beforeeach和aftereach。之后的it同样如此。最后是afterall。  


    参考资料:

    1. 使用protractor操作页面元素
    2. Jasmine中的describe和it
    3. JavaScripy单元测试框架:Jasmine初探
    4. Jasmine的断言
    5. 链式调用资料1
    6. 链式调用资料2
  • 相关阅读:
    ExtJs学习准备工作(二) firebug firefox插件的安装 全新时代
    Hibernate系统中调试SQL方式 全新时代
    Eclipse工程出现红叉导致无法编译的问题 全新时代
    javascript 取table中内容
    Asp.Net中清空所有textbox的几种方法
    SQL Server:使用系统存储过程实现的通用分页存储过程
    C# 检查网络是否连通
    sq分页原理
    SQL Server:日志备份和差异备份还原中的常见问题示例
    javascript:连接数据库
  • 原文地址:https://www.cnblogs.com/RicardoIsLearning/p/12955022.html
Copyright © 2020-2023  润新知