Pom设计模式

自动化测试模型

自动化测试模型概念

自动化测试模型可以看作自动化测试框架与工具设计的思想。自动化测试不仅仅是单纯写脚本运行就可以了,还需要考虑从到如何使脚本运行效率提高,代码复用,参数化等问题。自动化测试模型分为四大类:线性模型,模块化驱动测试,数据驱动,关键字驱动。

线性测试

概念:线性脚本中每个脚本都相互独立,且不会产生其他依赖与调用,其实就是简单模拟用户某个操作流程的脚本。

1
2
3
4
5
6
7
8
9
from selenium import webdriver
from time import sleep

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element_by_id("kw").send_keys("线性测试")
driver.find_element_by_id("su").click()
sleep(2)
driver.quit()

优点:这种模型的优势就是每个脚本都是完整且独立的。

缺点:

  • 开发成本高,用例之间存在重复的操作。
  • 维护成本高,由于重复的操作,当重复的操作发生改变时,则需要逐一进行脚本的修改。

模块化驱动测试

概念:把重复的操作代码封装为独立的公共模块,当用例执行时需要用到这部分,直接调用即可,这就是模块驱动测试。比如登录系统,退出登录等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from selenium import webdriver

class Login:
# 登录
def login(self, driver):
driver.find_element_by_id("loginName").send_keys("student")
driver.find_element_by_id("pwd").send_keys("123456")
driver.find_element_by_css_selector("input[readonly='readonly']").click()

# 退出
def logout(self, driver):
driver.find_element_by_link_text("退出").click()
driver.quit()


if __name__ == '__main__':
driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("http://192.168.68.251/ceshi/front/login.do")
Login().login(driver)
Login().logout(driver)

优点:由于最大限度消除了重复,从而提高了开发效率和提高测试用例的可维护性。

缺点:测试的数据不同,模块化的步骤相同。比如说重复的登录模块,如果登录用户不同,依旧要重复编写登录脚本。

数据驱动测试

概念:将测试中的测试数据和操作分离,数据存放在另外一个文件中单独维护。通过数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变。

新建data包,在data包中新建一个test_data.xls

test_data.xls

新建util包,在util包中新建operation_excel.py

operation_excel.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import xlrd

# 创建操作excel表格的类
class OperationExcel:
# 初始化类 filename:路径+文件名
def __init__(self, filename):
self.table = xlrd.open_workbook(filename)

# 通过表单索引读取数据
def get_data_by_index(self, index=0):
sheet = self.table.sheet_by_index(index) # 获取表单
return self._get_data_info(sheet)

# 通过表单名字读取
def get_data_by_name(self, name):
sheet = self.table.sheet_by_name(name)
return self._get_data_info(sheet)

# 获取数据详情
def _get_data_info(self, sheet):
keys = sheet.row_values(0) # 将表格的第一行作为字典的key
rows = sheet.nrows # 获取所有行数
cols = sheet.ncols # 获取所有列数
data_list = []
for row in range(1, rows):
values = []
for col in range(cols):
value = self.read_cell(sheet, row, col)
values.append(value)
# zip():函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表
# a = [1,2,3] b = [4,5,6] zip(a, b) --> [(1, 4), (2, 5), (3, 6)]
ziped = zip(keys, values)
data_list.append(dict(ziped)) # 将列表转化成字典追加到列表中
return data_list

# 对单元格内容进行类型转换
def read_cell(self, sheet, row, col):
sheet = self.table.sheet_by_index(0)
cell_value = sheet.cell_value(row, col)
cell_type = sheet.cell_type(row, col)
if cell_type == 1: # 当单元格数据类型为str
cell_value = cell_value
elif cell_type == 2 and cell_value % 1 == 0: # 当单元格内容为int
cell_value = int(cell_value)
elif cell_type == 4: # 当单元格数据类型为布尔值
cell_value = True if cell_value == 1 else False # 三目运算符
return cell_value


if __name__ == '__main__':
op_ex = OperationExcel("../data/test_data.xls")
# data_list = op_ex.get_data_by_index()
data_list = op_ex.get_data_by_name("Sheet1")
print(data_list)

新建case包,在test包中新建test_case.py

test_case.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from selenium import webdriver
from util.operation_excel import OperationExcel
from time import sleep
class Login:
# 登录
def login(self, driver, username, password):
driver.find_element_by_id("loginName").send_keys(username)
driver.find_element_by_id("pwd").send_keys(password)
driver.find_element_by_css_selector("input[readonly='readonly']").click()

# 退出
def logout(self, driver):
if "学生" in driver.title:
driver.find_element_by_link_text("退出").click()
sleep(2)
driver.quit()
else:
sleep(2)
driver.quit()


if __name__ == '__main__':
op_ex = OperationExcel("../data/test_data.xls")
data_list = op_ex.get_data_by_name("Sheet1")
for data in data_list:
driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("http://192.168.68.251/ceshi/front/login.do")
Login().login(driver, data['username'], data['password'])
Login().logout(driver)

优点: 通过这种方式,将数据和重复操作分开,可以快速增加相似测试,完成不同数据情况下的测试。

关键字驱动测试

概念:将原本的自动化代码进行代码与数据的分离,再将分离出来的代码二次分离,形成所谓行为代码与测试代码,而后再经由数据内容驱动行为代码,生成测试代码。从而更加便捷地进行自动化测试代码的管理,以及提高自动化的复用性,让使用者更加容易理解和使用自动化 。

结构设计:

  • 逻辑代码的实现,本身不存在任何价值,需要结合业务才能够体现作用。
  • 只有测试代码才可以对系统的功能进行自动化测试。
  • 数据与代码进行分离,但凡数据需要改动,直接修改数据文件即可,不会影响到原有代码的稳定性。

新建keys包,在keys包中新建key_word.py

用于对selenium进行二次封装为关键字类

key_word.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from time import sleep
from selenium import webdriver

# 创建浏览器对象
def open_browser(type_):
try:
driver = getattr(webdriver, type_)()
except:
driver = webdriver.Chrome()
return driver

class Key_Word:
# 构造函数
def __init__(self, type_):
self.driver = open_browser(type_)

# 访问url
def open(self, url):
self.driver.get(url)

# 定位元素
def locator(self, name, value, **kwargs):
return self.driver.find_element(name, value)

# 输入
def input(self, kwargs):
self.locator(**kwargs).send_keys(kwargs['txt'])

# 点击
def click(self, kwargs):
self.locator(**kwargs).click()

# 等待
def wait(self, time):
sleep(time)

# 关闭
def quit(self):
self.driver.quit()

新建case包,在case包中新建test_case.py

用于编写测试用例

test_case.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from keys.key_wrod import Key_Word  # 导入封装的关键字类
import unittest
from ddt import ddt, file_data

@ddt()
class Test_baidu(unittest.TestCase):

def setUp(self) -> None:
pass

@file_data("../data/test_data.yml")
def test_01(self, **kwargs):
self.driver = Key_Word(kwargs['type_'])
self.driver.open(kwargs['url'])
self.driver.input(kwargs['input'])
self.driver.click(kwargs['button'])
self.driver.wait(kwargs['time'])
self.driver.quit()

def tearDown(self) -> None:
pass


if __name__ == '__main__':
unittest.main()

新建data包,在data包中新建test_data.yml

用于创建测试数据

test_data.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-
type_: Chrome
url: https://www.baidu.com
input:
name: id
value: kw
txt: 关键字驱动
button:
name: id
value: su
time: 3
-
type_: Chrome
url: https://www.baidu.com
input:
name: id
value: kw
txt: 测试数据
button:
name: id
value: su
time: 5

pom设计模式

pom设计模式概念

POM模式 —-(page object model)页面自动化模型

在自动化中已经流行起来的一种易于维护和减少代码的设计模式。在自动化测试中,PO对象作为一个与页面交互的接口。测试中需要与页面的UI进行交互时,便调用PO的方法。这样做的好处是,如果页面的UI发生了更改,那么测试用例本身不需要更改,只需更改PO中的代码即可。

优点:

  • 测试代码与页面的定位代码(如定位器或者其他的映射)相分离。
  • 该页面提供的方法或元素在一个独立的类中,而不是将这些方法或元素分散在整个测试中。

将页面分成三层:

  • 表现层
    • 页面中可见的元素,属于表现层 —- 定位器制作
  • 操作层
    • 对页面可见元素的操作 —- 点击、输入等
  • 业务层
    • 在页面中对若干元素操作后所实现的功能

核心架构 —- 组成结构:

  • asset–项目名称
    • common文件夹 —- 放置公共方法
      • base.py —- 对selenium做二次封装
        • 1.打开浏览器
        • 2.输入地址
        • 3.元素定位
        • 4.点击
        • 5.输入
        • 6.判断类型方法
      • 其他公共方法
    • page文件夹 — 存放页面
      • 1.一个页面编写一个.py文件,例如:login_page.py
      • 2.page中封装页面表现层和操作层
      • 3.page中的.py文件继承base.py
    • script文件夹 —存放测试用例
      • 1.封装页面业务层
      • 2.test_login.py
    • data文件夹—存放测试数据
      • login_data.yml
    • report文件夹—存放测试报告

pom设计模式实战

项目结构:

新建common包,common包中新建base.py

对selenium进行二次封装

base.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from time import sleep

def open_browser(browser):
try:
driver_ = getattr(webdriver, browser)()
except:
driver_ = None
print("浏览器输入不合法!!!")
return driver_

# base类
class Base:
# 打开浏览器
def __init__(self, browser='Chrome'):
self.driver = open_browser(browser)
self.driver.maximize_window()

# 输入网址
def open_url(self, url):
self.driver.get(url)

# 元素定位
def find_element(self, locator, timeout=10):
try:
element = WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator))
except:
element = None
print(f"元素{locator}没找到!!")
return element

# 元素定位
def find_elements(self, locator, timeout=10):
try:
elements = WebDriverWait(self.driver, timeout).until(EC.presence_of_all_elements_located(locator))
except:
elements = None
print(f"元素{locator}没找到!!")
return elements

# 元素操作
# 输入
def send_keys(self, locator, text):
element = self.find_element(locator)
element.clear()
element.send_keys(text)

# 点击
def click(self, locator):
self.find_element(locator).click()

# 下拉框处理
def select(self, locator):
element = self.find_element(locator)
return Select(element)

# 通过index选择
def select_by_index(self, locator, index):
self.select(locator).select_by_index(index)

# 通过value选择
def select_by_value(self, locator, value):
self.select(locator).select_by_value(value)

# 通过可见文本选择
def select_by_text(self, locator, text):
self.select(locator).select_by_visible_text(text)

# 弹窗处理
def alert(self):
return self.driver.switch_to.alert

# 关闭弹窗
def alert_close(self, bool_=True):
if bool_:
self.alert().accept()
else:
self.alert().dismiss()

# 获取弹窗文本
def alert_text(self):
return self.alert().text

# 发送文本至警告框
def alert_send_keys(self, value):
self.alert().send_keys(value)

# 判断文本内容
def is_text_in_element(self, locator, text, timeout=10):
result = WebDriverWait(self.driver, timeout).until(EC.text_to_be_present_in_element(locator, text))
return result

# 判断value内容
def is_value_in_element(self, locator, value, timeout=10):
result = WebDriverWait(self.driver, timeout).until(EC.text_to_be_present_in_element_value(locator, value))
return result

# 判断单选框是否选中
def is_selected(self):
result = self.driver.is_selected()
return result

# 获取页面title
def title(self):
return self.driver.title

# 等待时间
def sleep(self, time):
sleep(time)

# 关闭浏览器
def quit(self):
self.driver.quit()

新建page包,在page包中新建login_page.py

用于封装页面的表现层和操作层(继承Base类)

login_page.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""
login_page.py
封装页面的表现层
封装页面的操作层
需要继承Base类
"""
from common.base import Base

# 登录页面地址
login_url = "http://192.168.68.251"

class LoginPage(Base):

"""封装表现层:制作定位器"""
# 用户名输入框定位器
login_name_loc = ("id", "loginName")
# 密码输入框定位器
password_loc = ("id", "pwd")
# 忘记密码按钮定位器
forget_password_loc = ("link text", "忘记密码?")
# 登录按钮定位器
login_btn_loc = ("css selector", "input[readonly]")

"""
封装操作层:元素操作
每一个元素操作都写成一个方法
"""
# 输入用户名
def input_login_name(self, username):
self.send_keys(self.login_name_loc, username)

# 输入密码
def input_password(self, password):
self.send_keys(self.password_loc, password)

# 点击登录按钮
def click_login_btn(self):
self.click(self.login_btn_loc)

新建script包,在script包中新建login_test.py

用于封装业务层(测试用例)

login_test.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""
login_test.py
封装业务层
"""
from page.login_page import LoginPage, login_url
import unittest
from ddt import ddt, file_data, data, unpack

@ddt()
class TestLogin(unittest.TestCase):

def setUp(self) -> None:
self.login_page = LoginPage()
self.login_page.open_url(login_url)

@data(("student", "123456"), ("teacher", "19216868251"))
@unpack
def test_login_data(self, username, password):
self.login_page.input_login_name(username)
self.login_page.input_password(password)
self.login_page.click_login_btn()
self.assertIn("任务列表", self.login_page.title(), msg="登录失败!!!")

@file_data("../data/login_data.yml")
def test_login_file_data(self, username, password):
self.login_page.input_login_name(username)
self.login_page.input_password(password)
self.login_page.click_login_btn()
self.assertIn("任务列表", self.login_page.title(), msg="登录失败!!!")

def tearDown(self) -> None:
self.login_page.quit()


if __name__ == '__main__':
unittest.main()

新建data包,在data包中新建login_data.yml

用于定义测试数据

login_data.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-
username: student
password: 123456
-
username: ''
password: 123456
-
username: student1
password: 123456
-
username: STUDENT
password: 123456
-
username: student
password: ''
-
username: student
password: 111111

新建report包,在report包中新建run_login_html.py

用于执行测试用例生成测试报告并存放

run_login_html.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import unittest
import time
import HTMLTestRunner

# 确定测试用例文件路径
test_dir = "../script"
# 将用例添加到测试套件中
discover = unittest.defaultTestLoader.discover(test_dir, pattern='*test.py')
# 指定测试报告存放位置
report_dir = "../report"
# 给测试报告命名
now_time = time.strftime("%Y-%m-%d %H-%M-%S")
report_filename = report_dir + "/" + now_time + "_report.html"
# 创建并读写文件
stream = open(report_filename, "wb")
# 使用第三方插件执行测试用例生成测试报告
runner = HTMLTestRunner.HTMLTestRunner(stream=stream, verbosity=2, title="登录模块测试", description="登录测试执行结果")
# 执行测试套件中的测试用例
runner.run(discover)
# 关闭文件
stream.close()

html报告结果: