五分钟搞懂POM设计模式

Posted 程序员江念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五分钟搞懂POM设计模式相关的知识,希望对你有一定的参考价值。

今天,我们来聊聊Web UI自动化测试中的POM设计模式。

为什么要用POM设计模式

前期,我们学会了使用Python+Selenium编写Web UI自动化测试线性脚本

线性脚本(以快递100网站登录举栗):

使用以上代码,最基础最简单的Web UI 自动化测试就做起来了,但是,问题也随之而来,线性脚本的缺点也暴露出来了:

Web UI自动化测试,简单来说,就是模拟人在浏览器上的操作,打开浏览器-定位元素-操作元素-模拟页面动作-断言结果
由于线性脚本中的元素定位、元素操作细节、测试数据、结果验证(断言)是捆绑在一起的,代码会显得非常冗余、可读性差、不可复用、工作量大且可维护性差
刚开始,少数的测试用例维护起来可能很容易,但随着时间迁移、产品迭代、测试套件持续增长,脚本也越来越臃肿,可能需要维护几十个页面,且很多页面是公用的,元素的任何改变都会让我们的脚本变得繁琐复杂、耗时易出错。例如:十几个用例中都用到了A元素,某一天A元素被前端改成了B元素,我们就需要去十几个用到A元素的地方,将A元素修改为B元素
如果可以把公共元素抽取出来,即使元素被前端修改,我们也只需更新元素的定位方式,而不用修改每条测试用例,无论多少用例用到该元素,都只需修改元素定位方式,重新获取元素即可
所以我们引入了PageObject这种解决方案,它可以帮我们解决设计上的问题,可以将testcase和page分层,形成一个非常好的结果
什么是POM设计模式
POM:Page Object Model,页面对象模型的简称

2013年,由Martin Fowler提出了PageObject的观点

  • 作者的观点是一种封装思想,旨在为每个待测页面创建一个页面对象,从而将繁琐的定位操作、操作细节封装到这个页面对象中,对外只提供必要的操作接口,在调用的时候只调用提供的接口,不用去调用操作细节,最终实现程序的高内聚低耦合,使程序模块的可重用性、移植性大大增强 

在这种模式下,对于应用程序中的每个页面都应该有相应单独的页面类(例如:login_page、userinfo_page),类中应该包含此页面上的元素对象和操作这些元素对象所需要的方法

再将流程所关联的页面作为对象,将对象串联起来形成不同的业务流程,例如:在登录页面完成登录操作后跳转到用户中心页面进行个人信息的修改

Selenium官方对PageObject的引入
历史

2015年,Selenium官方对PageObject进行引入:

PageObjects · SeleniumHQ/selenium Wiki · GitHub

2020年,Selenium更新文档地址:

Page object models | Selenium

 PageObject六大原则

 

The public methods represent the services that the page offers

用公共方法表示页面提供的服务

例如:登录页面,有用户名输入框、密码输入框、登录按钮,于是就可以用input_username()代表输入用户名、用input_password()代表输入密码、用click_submit()代表点击登录按钮

Try not to expose the internals of the page

尽量不要暴露页面的内部信息

将操作细节封装成方法,对外只提供对应的方法供调用

Generally don’t make assertions

一般不使用断言

断言要和Page代码分开,不要将断言写在PageObject层

Methods return other PageObjects

方法返回其他PageObjects

例如:首页有个方法是点击登录图标跳转到登录页面,因此这个方法应该返回login_page

Need not represent an entire page

不需要表示整个页面

不需要对页面中的每一个元素进行建模,只需要关注我们需要用到的元素。例如:登录页面除了账号密码登录,还有快捷登录、手机短信登录、扫码登录等

Different results for the same action are modelled as different methods

同一行为的不同结果可以用不同的方法来模拟

例如:对一个页面进行操作,可能出现正确的结果或者错误的结果,可以为这两种不同的结果分别创建两个不同的方法

POM框架

base:base_page,基类,定义项目所需的基础方法,对Selenium一些常用的api进行二次封装,如:find_element、click、send_keys、screenshot、调用javascript脚本的方法以及其他与浏览器相关的操作

为什么要有基类?

由于每个页面都会频繁使用这些方法,若单纯使用Selenium原始api,可能遇到一些问题,例如:某个按钮未加载完成,但已触发了点击事件,导致元素定位不到而报错。这时就可以对原始api进行二次封装,如:加入等待时间、对异常进行捕获并打印日志等,之后所有的PageObject都继承BasePage类,后续只需要调用这些封装好的方法,增强复用性
假设以后不使用Selenium这个框架,就只需要修改BasePage中的方法,不用去修改具体的测试用例业务代码
pages:page_object,页面对象层,也是PO的核心层,继承BasePage,管理页面元素以及操作元素的方法(将操作元素的动作写成方法)

cases:测试用例层,用于管理测试用例,这里会用到单元测试框架,如:Pytest、Unittest。

data:测试数据层,用于测试数据的管理,数据与脚本分离,降低维护成本,提高可移植性,如:yml 文件数据

config:配置文件层,存放整个项目需要用到的配置项,如:URL、数据库信息等

utils:CommonUtil,公共模块,将一些公共函数、方法以及通用操作进行封装,如:日志模块、yaml 操作模块、时间模块等

run.py:批量执行测试用例的主程序,根据不同需求不同场景进行组装,遵循框架的灵活性和扩展性

logs:日志模块,用于记录和管理日志,针对不同情况,设置不同的日志级别,方便定位问题

reports:测试报告层,用于测试报告的生成和管理,如:基于 Allure 生成的定制化报告

  最后,资源分享

下面这份资源,对于想学习【软件测试】的朋友来说应该是最全面最完整的备战仓库,希望也能帮助到你!

 

五分钟搞懂Vuex

这段时间一直在用vue写项目,vuex在项目中也会依葫芦画瓢使用,但是总有一种朦朦胧胧的感觉。于是决定彻底搞懂它。

看了一下午的官方文档,以及资料,才发现vuex so easy!

作为一个圈子中的人,决定输出一下文档,如果你仔细看完这篇文章,保证你对vuex熟练掌握。

 

我把自己的代码上传到了github,大家有需要的可以拉下来:github

 

先说一下vuex到底是什么?

vuex 是一个专门为vue.js应用程序开发的状态管理模式。

这个状态我们可以理解为在data中的属性,需要共享给其他组件使用的部分。

也就是说,是我们需要共享的data,使用vuex进行统一集中式的管理。

 

vuex中,有默认的五种基本的对象:

  • state:存储状态(变量)
  • getters:对数据获取之前的再次编译,可以理解为state的计算属性。我们在组件中使用 $sotre.getters.fun()
  • mutations:修改状态,并且是同步的。在组件中使用$store.commit(‘‘,params)。这个和我们组件中的自定义事件类似。
  • actions:异步操作。在组件中使用是$store.dispath(‘‘)
  • modules:store的子模块,为了开发大型项目,方便状态管理而使用的。这里我们就不解释了,用起来和上面的一样。

 

 

下面我们正式开始,一步步使用vuex

 

 

1、首先创建一个vue-cli项目

执行下面的命令,创建一个app项目(这里也可以使用其他非webpack模板,以及非app名称)

vue init webpack app

技术图片

2、创建完成之后,我们进入文件夹下,并且运行项目

cd app/
npm run dev

接下来我们在src目录下创建一个vuex文件夹

并在vuex文件夹下创建一个store.js文件

文件夹目录长得是这个样子

技术图片

 

3、目前我们还没有引入vuex,我们需要先下载vuex,并且引入它

在保证我们处于我们项目下,在命令行输入下面命令,安装vuex

npm install vuex --save

技术图片

4、安装成功之后,我们就可以在store.js中尽情玩耍我们的vuex了!

在store.js文件中,引入vuex并且使用vuex,这里注意我的变量名是大写Vue和Vuex 

技术图片
import Vue from ‘vue‘
import Vuex from ‘vuex‘

Vue.use(Vuex)

const state = {
    count: 0
}

export default new Vuex.Store({
    state
})
技术图片

技术图片

接下来,在main.js中引入store

技术图片
import Vue from ‘vue‘
import App from ‘./App‘
import router from ‘./router‘
import store from ‘./vuex/store‘ // 引入store
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
    el: ‘#app‘,
    router,
    store,
    components: { App },
    template: ‘<App/>‘
})
技术图片

然我我们在任意一个组件中就可以使用我们定义的count属性了。

这里我们在helloWorld中使用一下,去除helloworld.vue中不用的标签

<template>
  <div class="hello">
    <h3>{{$store.state.count}}</h3>
  </div>
</template>

打开我们刚才运行项目的浏览器,可以看到已经使用成功了!

技术图片

并且在vue开发工具中我们可以看到我们定义的变量count

技术图片

到这一步,已经成功了一小半!vuex很简单吧?

回想一下,我们只需要在下载安装使用vuex,在我们定义的store.js中定义state对象,并且暴露出去。

在main.js中使用我们的store.js(这里是为了防止在各个组件中引用,因为main.js中,有我们的new Vue 实例啊!)

 

现在我们已经使用了vuex中的state,接下来我们如何操作这个值呢? 没错!用mutations和actions

我们继续操作store.js文件

我们在sotre.js中定义mutations对象,该对象中有两个方法,mutations里面的参数,第一个默认为state,接下来的为自定义参数。

我们在mutations中定义两个方法,增加和减少,并且设置一个参数n,默认值为0,然后在Vuex.Store中使用它

技术图片
/**
 * mutations 里面放置的是我们操作state对象属性的方法
 */
const mutations = {
    mutationsAddCount(state, n = 0) {
        return (state.count += n)
    },
    mutationsReduceCount(state, n = 0) {
        return (state.count -= n)
    }
}
export default new Vuex.Store({
    state,
    mutations
})
技术图片

然后我们在helloWorld.vue中,使用这个方法

还记得我们如何在组件中使用mutations吗?就和自定义事件非常相似

技术图片
<template>
  <div class="hello">
    <h3>{{$store.state.count}}</h3>
    <div>
      <button @click="handleAddClick(10)">增加</button>
      <button @click="handleReduceClick(10)">减少</button>
    </div>
  </div>
</template>
技术图片
技术图片
methods: {
    handleAddClick(n){
      this.$store.commit(‘mutationsAddCount‘,n);
    },
    handleReduceClick(n){
      this.$store.commit(‘mutationsReduceCount‘,n);
    }
  }
技术图片

来浏览器看一下效果如何!

我们可以看到每当触发事件时,我们都可以在vue开发工具中看到我们触发的mutations方法,以及参数

完美!

技术图片

 

接下来就是actions,actions是异步操作

创建actions对象,并且使用

这里我在两个方法中使用了两个不同的参数,一个是context,它是一个和store对象具有相同对象属性的参数。在第二个函数中,我是直接使用了这个对象的commit的方法。

凭大家喜好就行

技术图片
const actions = {
    actionsAddCount(context, n = 0) {
        console.log(context)
        return context.commit(‘mutationsAddCount‘, n)
    },
    actionsReduceCount({ commit }, n = 0) {
        return commit(‘mutationsReduceCount‘, n)
    }
}
export default new Vuex.Store({
    state,
    mutations,
    actions
})
技术图片

在helloWorld.vue中

在methods中,增加两个方法,使用dispath来触发

 <div>异步操作</div>
  <div>
    <button @click="handleActionsAdd(10)">异步增加</button>
    <button @click="handleActionsReduce(10)">异步减少</button>
  </div>
技术图片
handleActionsAdd(n){
      this.$store.dispatch(‘actionsAddCount‘,n)
    },
    handleActionsReduce(n){
      this.$store.dispatch(‘actionsReduceCount‘,n)
    }
技术图片

进入浏览器看下效果如何!

最后就是getters

我们一般使用getters来获取我们的state,因为它算是state的一个计算属性

技术图片
const getters = {
    getterCount(state, n = 0) {
        return (state.count += n)
    }
}
export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
})
技术图片
<h4>{{count}}</h4>
const getters = {
    getterCount(state) {
        return (state.count += 10)
    }
}

getters算是非常简单的了。

到这里,如果全都看懂了,vuex你已经没有压力了。

但是vuex官方给了我们一个更简单的方式来使用vuex, 也就是 {mapState, mapMutations, mapActions, mapGetters}

只要我们把上面基础的搞懂,这些都不在话下,只是方面我们书写罢了。

就这么简单,这里我们用到了es6的扩展运算符。如果不熟悉的同学还是去看看阮一峰大神的《Es6标准入门》这本书,我是看完了,受益匪浅!

技术图片
<script>
import {mapState, mapMutations, mapActions, mapGetters} from ‘vuex‘
export default {
  name: ‘HelloWorld‘,
  data () {
    return {
      msg: ‘Welcome to Your Vue.js App‘
    }
  },
  methods: {
    ...mapMutations({
      handleAddClick: ‘mutationsAddCount‘,
      handleReduceClick: ‘mutationsReduceCount‘
    }),
    ...mapActions({
      handleActionsAdd: ‘actionsAddCount‘,
      handleActionsReduce: ‘actionsReduceCount‘
    })
    // handleAddClick(n){
    //   this.$store.commit(‘mutationsAddCount‘,n);
    // },
    // handleReduceClick(n){
    //   this.$store.commit(‘mutationsReduceCount‘,n);
    // },
    // handleActionsAdd(n){
    //   this.$store.dispatch(‘actionsAddCount‘,n)
    // },
    // handleActionsReduce(n){
    //   this.$store.dispatch(‘actionsReduceCount‘,n)
    // }
  },
  computed: {
    count(){
      return this.$store.getters.getterCount
    }
  }
}
</script>
技术图片

 

同理,getters和 state也可以使用 mapState,mapGetters

 

如果你更懒的话,我们可以使用数组,而非对象,或者es6里面的对象简写方式

就像这种

技术图片

 

最后,如果大家发现有什么问题,或者错误的地方,欢迎留言交流。

 

 

 

分割线----------------------------------------------------------------------
看到后台一些小伙伴问我用的什么编辑器,我这里用的是vscode,然后把我自己的一些配置给贴上,喜欢的话可以粘贴复制一下。

最近的效果是这样子的~~

字体用的是Fira Code 很好用的一种字体

技术图片

 

 

 

技术图片
{
    "editor.fontFamily": "Fira Code",
    "editor.fontLigatures": true,
    "editor.cursorStyle": "block",
  "editor.fontSize": 16,
  "editor.lineHeight": 24,
  "editor.lineNumbers": "on",
  "editor.minimap.enabled": false,
  "editor.renderIndentGuides": false,
  "editor.rulers": [120],
  "workbench.colorTheme": "Andromeda",
  "workbench.iconTheme": "vscode-great-icons",
  "editor.detectIndentation": false,
  "editor.tabSize": 2,
  "editor.quickSuggestions": {
    "other": true,
    "comments": true,
    "strings": true
  },
  "files.associations": {
    "*.cjson": "jsonc",
    "*.wxss": "css",
    "*.wxs": "javascript"
  },
  "emmet.includeLanguages": {
    "wxml": "html"
  },
  "minapp-vscode.disableAutoConfig": true,
  "window.zoomLevel": -1,
  "[vue]": {
    "editor.defaultFormatter": "octref.vetur"
  },
  "todo-tree.defaultHighlight": {
    "type": "text",
},
"todo-tree.customHighlight": {
    "TODO": {
        "icon": "check",
        "foreground": "#000",
        "background": "#cecb32",
        "iconColour": "#fffc43"
    },
    "FIXME": {
        "icon": "alert",
        "foreground": "#fff",
        "background": "#ca4848",
        "iconColour": "#ff4343"
    }
},
"todo-tree.highlights.customHighlight": {
  "TODO": {
    "icon": "check",
    "foreground": "#000",
    "background": "#cecb32",
    "iconColour": "#fffc43"
  },
  "FIXME": {
    "icon": "alert",
    "foreground": "#fff",
    "background": "#ca4848",
    "iconColour": "#ff4343"
  }
},
"todo-tree.highlights.defaultHighlight": {
  "type": "text"
}
}
技术图片

 

以上是关于五分钟搞懂POM设计模式的主要内容,如果未能解决你的问题,请参考以下文章

APP自动化测试很重要?一文带你搞懂怎么测APP

APP自动化测试很重要?一文带你搞懂怎么测APP

十五分钟上手SoFlu全自动软件工程平台

UI自动化测试POM设计之-maven工程

3分钟带你搞懂Selenium工具和自动测试框架

3分钟带你搞懂Selenium工具和自动测试框架