基于CodeceptJS, 从0到1搭建E2E自动化测试框架

CodeceptJS

CodeceptJS是基于现代web技术的E2E自动化测试框架,基于 Feature 和 Scenario 两个粒度来组织测试让它看起来更有 E2E 测试的样子,它支持最新的ES6语法,同时也屏蔽各种复杂的回调细节,所有的测试用例都是以第一人称来做,让测试代码阅读起来更像是自然语言。

Quick Start

第一步,先让测试跑起来

按照quick start搭建了环境,写出了第一个测试-登陆,感觉so easy.

Feature('My First Test');

Scenario('test something', ({ I }) => {
  I.amOnPage('https://github.com');
  I.click('Sign in');
  I.fillField('username', 'myName');
  I.fillField('password', 'myPassword');
  I.click('Sign in');
  I.see('Welcome, myName');
});

CI

第一个测试成功了,赶紧放到CI上跑一跑,每天定时运行,自动做回归测试,想想都觉得很美好。
那么问题来了,CI server上没有安装测试工具所需的环境依赖怎么办?在哪个环境运行,DEV,QA,UAT?运行完以后有report吗?

别急,都有现成的解决方案。
在CI server上启动一个包含测试环境依赖的docker,先让测试运行在最可控的QA环境,搜索一款最适合当前工具的report,比如allure,跟devops小哥哥pair一下,jenkinsfile就生成了,E2E自动化测试每天定时跑起来了,测试运行失败就会发送消息到日常slack工作群,及时发现问题~

pipeline {
  agent {
    kubernetes {
      label 'e2e-test'
      defaultContainer 'test'
      yaml """
        apiVersion: v1
        kind: Pod
        metadata:
        labels:
          app: e2e-app
        spec:
          containers:
          - name: codecepjs
            image: codeception/codeceptjs
            command:
            - cat
            tty: true
      """
    }
  }

    stages {
        stage('Run e2e in qa') {
            steps {
                catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    container('codecepjs') {
                    sh 'codeceptjs run --grep @qa -o \'{ "helpers": {"TestCafe": {"url": "https://www.cnblogs.com"}}}\' --plugins allure'
                    }
                  }
                }
        }
    }

    post {
        always {
            script {
                allure([
                        includeProperties: false,
                        jdk: '',
                        properties: [],
                        reportBuildPolicy: 'ALWAYS',
                        results: [[path: 'output']]
                ])
            }
        }
        failure {
            slackSend(color: '#FF0000',
                    channel: '#jenkins-channel',
                    message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL}) \nCommitter: (${getCommitter()})")
        }
        fixed {
            slackSend(color: '#00FF00',
                    channel: '#jenkins-channel',
                    message: "BACK TO NORMAL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}

新的问题

CodeceptJS支持语义化的元素定位符,可以自动判断定位符类型并且寻找元素,对于编写测试代码来说确实很方便,不用辛辛苦苦找元素定位了,可是有些元素通过文本作为定位符找不到啊,而且通过文本找元素好像很慢?还有哪些元素定位的方式?
CodeceptJS的操作就支持amOnPage,click,fillField吗?我还有一些其他的操作,应该怎么写啊?
CodeceptJS的断言只支持see吗?我还有地方要用not see,它也能支持吗?

这几个问题抛出了学习一个自动化测试工具的基本路径,那就是学习元素定位符,操作,断言。

其中元素定位符对于一般的UI自动化测试工具都是通用的,而操作和断言,每个工具提供的语法不一样。然而,一旦掌握一种工具,其他任何工具学习起来都很快。学好数理化,走遍天下都不怕~简直是一招在手,天下我有~

持续优化

此刻,我们的元素定位符,操作,断言已经写得很溜了。
当我开始写越来越多的测试后发现,每个测试都要登陆一下,怎么样才能减少这种重复操作呢?
除了登录,我们的代码中很多重复操作,怎么样封装他们,方便调用呢?
dev小伙伴升级了依赖库,导致元素定位变化了,我需要大面积修改元素定位符,怎样才能提高元素定位符的通用性,避免此类情况再次发生?

第一步,提取公共元素,比如文本框,当我需要使用某个文本框时只需要输入文本框的label就能定位到它了;

//封装公共元素
editInput: function (inputName, text) {
      let locator = `//label[contains(., "${inputName}")]/preceding-sibling::input`;
      this.clearField(locator);
      this.appendField(locator, text);
    },
    
//

第二步,提取公共step,比如click tab作为common step放到一个文件里,当我在任何页面需要click tab时,只需要写上tab上的文字就可以了,codeceptjs对button和link做了一层封装,所以看到quickstart里面,click sign in的元素定位符只需要写上文字就可以了;

第三步,提取公共function,比如登录,搜索等不同页面常用操作放到common function文件里面;

第四步,提取每个页面的公共function,针对一个页面的某个feature如果有多种测试场景,此时公共function可以复用。

场景设计的巧思

测试场景设计需要许多巧思,这样才能让E2E在日常运行中更为流畅。

  • 尽可能精简。因为E2E保证的是主要功能和流程正确,掺杂太多细节验证会让代码复杂度急剧增加,维护成本太高,同时还会让自己陷入我到底应该把哪些测试点加入到E2E的焦虑中。先用20%的精力做成80%的事情,再用剩下的80%的精力想想怎么拔高20%,比如提高团队的质量意识。
  • 每个测试场景尽可能独立。这样在其中一个场景报错时,不会影响其他场景的测试结果,并且任何时候想单独运行某个或某几个测试都可以立即运行,不用修改代码才能运行。场景独立带来的副作用就是,会有许多重复操作,这时就需要封装来解决这些冗余代码。
  • 在测试套件中进行登录操作。测试场景独立的话,每个测试开始前都需要进行登录操作,这会拉慢测试运行速度,如果测试工具支持的话,设置为在每个测试套件开始前进行登录操作,这会大大减少重复的登录操作。
  • 需要考虑数据的初始化。数据初始化的方案要么是通过页面操作去造数据,它的缺点是添加了大量和测试场景无关的额外操作,使得代码复杂加运行时间变长;要么通过API造数据,但需要E2E测试工具支持;要么通过连接数据库,直接向数据库插入数据,缺点是数据库表之间关联关系复杂的话,难以摸清这些关联关系,容易造出脏数据,同时连接数据库也需要E2E测试工具支持;我最倾向的方式是在不受影响的环境中运行E2E,使用提前准备好的固定数据,当然用完就失效的数据,比如跟状态相关的数据,还是只能通过前三种方式创建。
  • 需要考虑数据使用后的还原。这样才能在下次运行同一个测试时,不会因为数据被改变而失败。
  • 加tag区分不同环境的测试。针对不同环境运行的测试场景不一样的情况,加tag区分不同环境,运行时加上tag参数即可。

更多思考

E2E总是被诟病,运行太慢,维护频繁,反馈太晚。但在实际使用过程中,E2E发现了不少新功能破坏旧功能的情况,然而单元测试没有发现这些问题(UT覆盖率也很高)。那么为了更好地使用E2E,就需要不断优化它。运行太慢,那么就在多个docker里面并发运行;维护频繁,那么就尽量用不容易变化的元素定位符,并且多封装,一旦发生变化改动也不会很多;反馈太晚,额。。。它的属性就是如此,用它来做日常的回归测试保证旧功能完好是个不错的选择。

最后的成果

目前我们的项目上,一共有31个测试场景,单个docker容器里面运行全部测试耗时大概13分钟,每次部署QA或者UAT后自动trigger E2E分别在不同环境运行,再也不用担心Dev的重构或者环境部署不当造成的bug没有及时发现了。另外,因为有很多公共元素和公共步骤的封装,添加新的测试代码也很容易。唯一令人头大的点就是,它有时候有点不稳定,报一些手动操作没有的bug,这类bug debug起来很难,也不知道算不算bug。。。

上一篇:paddlex 使用-2 GUI版本


下一篇:使用波束搜索的端到端神经网络系统中的上下文语音识别(论文翻译)