一、概述
对网页计算器,进行加减乘除的测试操作。通过读取数据文件中的数据来执行测试用例。
数据驱动
网址:https://cal.supfree.net/cal.html
实现步骤:
1.采用po模式的分层思想对页面进行封装(page、object)
2.编写测试脚本
3.使用参数化传入测试数据(parameterized库)
二、项目框架
1.base包(主要封装对驱动浏览器层面的操作方法——selenium库相关的方法)
①base.py:封装元素的基本操作,内有Base类,包含的方法:页面初始化、查找元素方法、点击元素方法、获取value属性方法、截图方法
②get_driver.py:内有GetDriver类,用来实现driver的封装和关闭
2.page包(主要封装计算器的业务方法——加减乘除以及计算器的相关信息)
①__init__.py (创建包时自动生成)定位数据,可以看做是传入函数的一些参数,方便维护修改
如写入服务器域名地址、计算器配置数据、测试用例文件数据
②page_calc.py :内有PageCalc(Base)类并继承Base类,调用Base类中封装的方法对具体元素进行操作
(a)点击数字方法:page_click_num(self,num)遍历字符串时,负数和整数的点击顺序不同所以判断是否为负数
(b)点击加号:page_click_add(self)、点击减号page_click_sub(self)、点击乘号page_click_mul(self)、点击除号page_click_div(self)、点击等号page_click_eq(self)、获取结果page_get_result(self)、点击清屏page_click_clear(self)
(c)组装运算业务方法:完成运算的步骤:a+-*/b=(注意这是不用获取结果)
3.script文件夹(执行文件、测试用例文件)
①index.py:执行用例并生成报告,测试套件与HTMLTestRunner搭配使用
②test_calc.py:内有TestCalc(unittest.TestCase)类并继承unittest.TestCase类
实例化driver对象和page_calc对象
初始化方法:setUpClass、结束方法tearDownClass
测试运算方法:
步骤:调用运算业务方法(组装过的)、断言、截图、抛异常
方法:
测试加法:@parameterized.expand(get_data(page.add_file))、test_add_calc(self,a,b,expcet)、
用parameterized对数据进行动态获取并引用、expcet为运算结果,如果输入数据为数据型,断言时将其转变为字符型
测试减法、测试乘法、测试除法同加法
4.tools包(实现对数据的读取)
①read_json.py:读取json文件中的数据,调用json.load方法
②get_data.py:由于测试用例是json文件,读取出来的数据是字典,而parameterized参数化时数据类型是元组,所以需要将字典转化成元组
③HTMLTestRunner.py:用来生成测试报告,网上可下载此文件
5.data文件夹
json文件,存放测试用例
格式
6.image文件夹:存放截图,当断言错误时,截图保存
7.report文件夹:存放测试报告,html格式
三、
0.安装两个库
pip install selenium
pip install parameterized
1.page包——__init__.py
from selenium.webdriver.common.by import By
"""
存放计算器页面的实际信息,若后续这些信息发生变化或者换个网页的计算器,只需要修改这里的信息即可
"""
"""以下为服务器域名配置地址"""
url = 'https://cal.supfree.net/cal.html'
"""以下是计算器配置数据"""
# 由于数字键是有一定的规律,所以暂时先不用定位,用到的时候再考虑怎么解决
# cala_num = By.CSS_SELECTOR, '#simple{}'
# 定位加号+
calc_add = By.CSS_SELECTOR, '#simpleAdd'
# 定位减号-
calc_sub = By.CSS_SELECTOR, '#simpleSubtr'
# 定位乘号
calc_mul = By.CSS_SELECTOR, '#simpleMulti'
# 定位除号
calc_div = By.CSS_SELECTOR, '#simpleDivi'
# 定位等号
calc_eq = By.CSS_SELECTOR, '#simpleEqual'
# 定位小数点
calc_dot = By.CSS_SELECTOR, "#simpleDot"
# 定位负数符号
calc_neg = By.CSS_SELECTOR, "[value = '+/-']"
# 获取结果
calc_resual = By.CSS_SELECTOR, '#resultIpt'
# 定位清屏
calc_clear = By.CSS_SELECTOR, '#simpleClearAllBtn'
"""以下为测试用例文件"""
add_file = "addCalc.json"
sub_file = "subCalc.json"
mul_file = "mulCalc.json"
div_file = "divCalc.json"
2.base包——get_driver.py
from selenium import webdriver
import page
class GetDriver:
# 初始化类属性
driver = None
# 获取driver
# @classmethod修饰符对应的函数不需要实例化
@classmethod
def get_driver(cls):
# 保证只有一个driver对象
if cls.driver is None:
# 实例化对象
cls.driver = webdriver.Chrome()
# 打开浏览器、计算器页面
cls.driver.get(page.url)
# 最大化
cls.driver.maximize_window()
return cls.driver
# 退出driver
@classmethod
def quit_driver(cls):
if cls.driver:
cls.driver.quit()
cls.driver = None
if __name__ == '__main__':
# 第一次获取浏览器对象
print(GetDriver().get_driver()) # 不需要实例化即可直接调用get_driver()
# 第二次获取浏览器对象
print(GetDriver.get_driver()) # 虽然第一次的浏览器对象没有关闭,但仍然只有一个浏览器是打开的
3.base包——base.py
import time
from selenium.webdriver.support.wait import WebDriverWait
class Base:
# 初始化——处理对象仍是针对浏览器驱动对象
def __init__(self, driver):
self.driver = driver
# 查找元素方法,封装
def base_find_element(self, loc):
"""
:param loc: 元素的定位信息,格式为元组
:return: 返回查找到的元素
"""
# 查找过程中使用显示等待
# *loc:不定长参数,一个星号*可传入元组、列表;两个星号**可传入字典
return WebDriverWait(self.driver, 10).until(lambda x: x.find_element(*loc))
# 点击元素方法,封装
def base_click(self, loc):
self.base_find_element(loc).click()
# 获取value属性
def base_get_value(self, loc):
# 使用get_attribute()方法获取指定的元素属性值
# 注意:返回
return self.base_find_element(loc).get_attribute("value")
# 截图 方法封装
def base_get_img(self):
self.driver.get_screenshot_as_file("../image/{}.png".format(time.strftime("%Y-%m-%d %H-%M-%S")))
4.page包——page_calc.py
from selenium.webdriver.common.by import By
import page
from base.base import Base
class PageCalc(Base):
# 点击数字方法
def page_click_num(self, num):
# num为负数
if num < 0:
flag = True
for n in str(num):
if flag:
# 先跳过负号(即num索引[0])
flag = False
continue
elif n == '.':
self.base_click(page.calc_dot)
else:
# 拆开单个按钮的定位方式
loc = By.CSS_SELECTOR, '#simple{}'.format(n)
self.base_click(loc)
# 最后点击负号
# 网页上的计算器在输入负数的时候,就是在最后输入负号才有效的
self.base_click(page.calc_neg)
# num为正数
else:
for n in str(num):
if n == '.':
self.base_click(page.calc_dot)
else:
loc = By.CSS_SELECTOR, "#simple{}".format(n)
self.base_click(loc)
# 点击加号
def page_click_add(self):
self.base_click(page.calc_add)
# 点击减号
def page_click_sub(self):
self.base_click(page.calc_sub)
# 点击乘号
def page_click_mul(self):
self.base_click(page.calc_mul)
# 点击除号
def page_click_div(self):
self.base_click(page.calc_div)
# 点击等号
def page_click_eq(self):
self.base_click(page.calc_eq)
# 获取结果
def page_get_result(self):
return self.base_get_value(page.calc_resual)
# 清屏
def page_click_clear(self):
self.base_click(page.calc_clear)
# 截图
def page_get_image(self):
self.base_get_img()
# 组装加法业务
def page_add_calc(self, a, b):
self.page_click_num(a)
self.page_click_add()
self.page_click_num(b)
self.page_click_eq()
# 组装减法业务
def page_sub_calc(self, a, b):
self.page_click_num(a)
self.page_click_sub()
self.page_click_num(b)
self.page_click_eq()
# 组装乘法业务
def page_mul_calc(self, a, b):
self.page_click_num(a)
self.page_click_mul()
self.page_click_num(b)
self.page_click_eq()
# 组装除法业务
def page_div_calc(self, a, b):
self.page_click_num(a)
self.page_click_div()
self.page_click_num(b)
self.page_click_eq()
"""
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
以下内容是对该类的测试,正常情况下,导入行为应该放置到文件开始的位置
"""
from selenium import webdriver
import time
if __name__ == '__main__':
driver = webdriver.Chrome()
driver.get(page.url)
pc = PageCalc(driver) # 实例化
# 测试查找数字(√)
pc.page_click_num(-1234) # 能够成功点击
pc.page_click_num(12) # 找出错误:查找正数元素时simple{}前面少个#
pc.page_click_num(12.33)
pc.page_click_clear() # 清屏(√)
# 测试组装的加法(√)
pc.page_add_calc(1, 3)
addtest = pc.page_get_result() # 获取结果(√)
print(addtest) # 4
pc.page_get_image() # 截图能够成功保存到image目录(√)
pc.page_click_clear()
"""
疑问:
1.为什么会发生漏截图的情况?
"""
5.tools包——read_json.py
import json
def read_json(filename):
filepath = '../data/'+filename
# 调用Load方法
with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f)
if __name__ == '__main__':
print(read_json('addCalc.json')) # 与写入的内容一致:字典{键:{值.字典}, 键,{}} (√)
6.tools包——get_data.py
from tools.read_json import read_json
def get_data(filename):
datas = read_json(filename)
# 新建空列表
arrs = []
for data in datas.values():
arrs.append((data['a'], data['b'], data['expect']))
return arrs
if __name__ == '__main__':
arrs = get_data('addCalc.json')
print(arrs)
# 发现错误:append中少些一对括号,没有是数据变成元组形式
7.script文件夹——test_calc.py
import unittest
from parameterized import parameterized
import page
from base.get_driver import GetDriver
from page.page_calc import PageCalc
from tools.get_data import get_data
class TestCalc(unittest.TestCase):
driver = None
# setUp
@classmethod
def setUpClass(cls):
# 获取driver
cls.driver = GetDriver.get_driver()
# 初始化 计算器页面对象
cls.calc = PageCalc(cls.driver)
# tearDown
@classmethod
def tearDownClass(cls):
# 关闭driver
GetDriver.quit_driver()
# 测试加法方法
@parameterized.expand(get_data(page.add_file))
def test_add_calc(self, a, b, expcet):
# 调用计算器业务加法
self.calc.page_add_calc(a, b)
try:
# 断言
self.assertEqual(self.calc.page_get_result(), str(expcet)) # 注意,获取到的结果是字符串,二expcet是数值,因此要转换类型
except:
# 截图
self.calc.page_get_image()
raise
# 测试减法方法
@parameterized.expand(get_data(page.sub_file))
def test_sub_calc(self, a, b, expcet):
# 调用计算器业务减法
self.calc.page_sub_calc(a, b)
try:
# 断言
self.assertEqual(self.calc.page_get_result(), str(expcet))
except:
# 截图
self.calc.page_get_image()
raise
# 测试乘法
@parameterized.expand(get_data(page.mul_file))
def test_mul_calc(self, a, b, expcet):
# 调用计算器业务乘法
self.calc.page_mul_calc(a, b)
try:
# 断言
self.assertEqual(self.calc.page_get_result(), str(expcet))
except:
# 截图
self.calc.page_get_image()
raise
# 测试除法
@parameterized.expand(get_data(page.div_file))
def test_div_calc(self, a, b, expcet):
# 调用计算器业务除法
self.calc.page_div_calc(a, b)
try:
# 断言
self.assertEqual(self.calc.page_get_result(), str(expcet))
except:
# 截图
self.calc.page_get_image()
raise
8.script文件夹——index.py
import time
import unittest
from tools.HTMLTestRunner import HTMLTestRunner
# 定义 测试套件
suite = unittest.defaultTestLoader.discover('../script', pattern='test*.py')
# 定义报告存放位置及文件名称
report_dir = '../report/{}.html'.format(time.strftime("%Y-%m-%d %H-%M-%S"))
# 执行
with open(report_dir, 'wb') as f:
HTMLTestRunner(stream=f, verbosity=2, title='计算器加减乘除测试报告').run(suite)
9.data文件夹——设计测试用例
addCalc.json 有一条结果是错误的,测试截图功能
{
"calc 001":{"a": 2, "b": 3, "expect":5},
"calc_002":{"a": 2, "b": 0.4, "expect" :2.4},
"calc_003":{"a": -4, "b": -9, "expect" :-13},
"calc_004":{"a": 12, "b": 0.34, "expect" :12.34},
"calc_005":{"a": 0.56, "b": 78, "expect" :78.56},
"calc_006":{"a": -90, "b": 10, "expect" :-80},
"calc_007":{"a": 123456, "b": -123456, "expect" :0},
"calc_008":{"a": 12, "b": -12, "expect" :1}
}
减乘除的数据同上,把结果换一下即可
10.运行index.py
结果:生成的报告显示只有一个问题,且并不是因为反例造成的(有四个有问题的测试用例),浏览器并没有自动打开
四、验证每份代码文件是否有问题
在排查过程中发现很多细节上的问题
如:在定位并点击数字元素时,少写一个#,导致num>=0的数没有办法定位到,一直报查找超时的错误
在断言中,判断获取的结果和数据文件中的结果时,expcet没有转换成str()类型,导致断言错误
append中少些一对括号,导致数据不是元组形式的,导致parameterized传参时一直报错提示有“,”
遇到问题:
page_calc.py:在连续测试加减乘除的方法时,每得到一个结果就截一张图,但是永远少一张,但没有报错。
为什么会漏截?猜测运行速度过快?——尝试每得到一个结果截图后,sleep1秒——能够正常获取四张截图
为什么出现这样的情况仍不报错?
五、最终结果
在把错误都排除完后,代码能够执行下去,且结果与预期一致
得到的测试报告