Day 5 测试编程讲义
本篇讲义非常偏技术属性,需要用到比较多编程知识。阅读此讲义,需要对以下技术有一定了解:
- 数据库表存储
- 循环与分支语句
- 基础设计模式
- 面向对象封装
- Python 库的使用
0 主要内容
- 1 T8_测试数据驱动
- 2 T9_测试业务抽离
- 3 T10_底层驱动封装
1 T8_测试数据驱动
1.1 什么是数据驱动
-
什么是数据驱动
主要的数据驱动方式有两种:
- 通过 文本文件或者 Excel 文件存储数据,并通过程序读取数据,遍历所有的行
- 通过数据库存储数据,并通过程序和 SQL 脚本读取数据,遍历所有的行
通过 CSV 文件 或者 MySQL 数据库,是主流的数据驱动方式。当然数据驱动也可以结合单元测试框架的参数化测试进行编写(此部分本文不做具体描述)。
无论使用了 哪一种(CSV 或者 MySQL),读取数据后都要进行遍历操作。
1.2 使用 csv
csv 是一种纯文本的格式,主要用来存储数据。
import csv
csv_file = open("xxx.csv", "r", encoding="utf8")
csv_data = csv.reader(csv_file)
for row in csv_data:
# 进行测试
# 使用字典类型
data_to_test = {
"key1": row[0],
"key2": row[1]
}
csv_file.close()
1.3 使用 MySQL
import pymysql
connect = pymysql.connect(host="xx", port=3306, user="root", passwd="xxx", db="xx")
cur = connect.cursor()
cur.execute("SELECT...")
mysql_data = cur.fetchall()
for row in mysql_data:
# 进行测试
# 使用字典类型
data_to_test = {
"key1": row[0],
"key2": row[1]
}
cur.close()
connect.close()
- 需要掌握的知识点:
- python的字典类型
dict
类型 - python的读写文件
- python的读写数据库
- for循环
- 注意资源的释放
- 关闭数据库游标和连接
- 关闭文件
- python的字典类型
2 T9_测试业务抽离
2.1 Page-Object设计模式本质
-
Page-Object设计模式的本质
Page Object设计模式是Selenium自动化测试项目的最佳设计模式之一,强调测试、逻辑、数据和驱动相互分离。
Page Object模式是Selenium中的一种测试设计模式,主要是将每一个页面设计为一个Class,其中包含页面中需要测试的元素(按钮,输入框,标题等),这样在Selenium测试页面中可以通过调用页面类来获取页面元素,这样巧妙的避免了当页面元素id或者位置变化时,需要改测试页面代码的情况。当页面元素id变化时,只需要更改测试页Class中页面的属性即可。
它的好处如下:
- 集中管理元素对象,便于应对元素的变化
- 集中管理一个page内的公共方法,便于测试用例的编写
- 后期维护方便,不需要重复的复制和修改代码
具体的做法如下:
- 创建一个页面的类
- 在类的构造方法中,传递 WebDriver 参数。
- 在测试用例的类中,实例化页面的类,并且传递在测试用例中已经实例化的WebDriver对象。
- 在页面的类中,编写该页面的所有操作的方法
- 在测试用例的类中,调用这些方法
2.2 Page 如何划分
一般通过继承的方式,进行按照实际Web页面进行划分。
- 主页
- 子模块主页
- 分类页
- 详情页
- 查看
- 编辑
- 增加
2.3 Page-Object 类如何实现
实现的示例
-
Page 基类
设计了一个基本的 Page类,以便所有的页面进行继承,该类标明了一个sub page类的基本功能和公共的功能。
-
全局变量: self.base_driver,让所有的子类都使用的。
# 基类的变量,所有继承的类,都可以使用 base_driver = None
-
构造方法:
-
传递 driver的构造方法
# 方法 def __init__(self, driver: BoxDriver): """ 构造方法 :param driver: ":BoxDriver" 规定了 driver 参数类型 """ self.base_driver = driver
-
-
私有的常量:存放元素的定位符
LOGIN_ACCOUNT_SELECTOR = "s, #account" LOGIN_PASSWORD_SELECTOR = "s, #password" LOGIN_KEEP_SELECTOR = "s, #keepLoginon" LOGIN_SUBMIT_SELECTOR = "s, #submit" LOGIN_LANGUAGE_BUTTON_SELECTOR = "s, #langs > button" LOGIN_LANGUAGE_MENU_SELECTOR = "s, #langs > ul > li:nth-child(%d) > a" LOGIN_FAIL_MESSAGE_SELECTOR = "s, body > div.bootbox.modal.fade.bootbox-alert.in > div > div > div.modal-body"
-
成员方法:
-
每个子类都需要的系统功能:
-
open
def open(self, url): """ 打开页面 :param url: :return: """ self.base_driver.navigate(url) self.base_driver.maximize_window() sleep(2)
-
-
所有子类(页面)都具有的业务功能
- select_app
- logout
-
-
Sub Pages(s)子类
具体的页面的类,定义了某个具体的页面的功能
-
必须继承基类
class MainPage(BasePage):
特定页面的业务
使用基类的
self.base_driver
成员变量
-
Tests 类
这部分描述的是具体的测试用例。
-
声明全局变量
base_driver = None base_url = None main_page = None
-
调用各种页面(pages)
-
实例化Page
self.main_page = MainPage(self.base_driver)
-
使用page的对象,调用成员方法
self.main_page.open(self.base_url) self.main_page.change_language(lang)
-
3 T10_底层驱动封装
3.1 为什么需要封装 Selenium
-
什么是封装
封装是一个面向对象编程的概念,是面向对象编程的核心属性,通过将代码内部实现进行密封和包装,从而简化编程。对Selenium进行封装的好处主要有如下三个方面:
- 使用成本低
- 不需要要求所有的测试工程师会熟练使用Selenium,而只需要会使用封装以后的代码
- 不需要对所有的测试工程师进行完整培训。也避免工作交接的成本。
- 测试人员使用统一的代码库
- 维护成本低
- 通过封装,在代码发生大范围变化和迁移的时候,不需要维护所有代码,只需要变更封装的部分即可
- 维护代码不需要有大量的工程师,只需要有核心的工程师进行封装的维护即可
- 代码安全性
- 对作为第三方的Selenium进行封装,是代码安全的基础。
- 对于任何的代码的安全隐患,必须由封装来解决,使得风险可控。
- 使用者并不知道封装内部的代码结构。
- 使用成本低
3.2 封装的概念与基本操作
-
关键方法的封装思路
封装的具体示例:
-
找到一个指定输入框(selector),并且输入指定的字符(text)
type(selector, text)
不用在业务逻辑中,使用多次的
find_element_by_id(...))
def type(self, selector, text): """ Operation input box. Usage: driver.type("i,el","selenium") """ el = self._locate_element(selector) el.clear() el.send_keys(text)
-
找到一个可以点击的元素(selector),并且点击(click)
click(selector)
def click(self, selector): """ It can click any text / image can be clicked Connection, check box, radio buttons, and even drop-down box etc.. Usage: driver.click("i,el") """ el = self._locate_element(selector) el.click()
-
找到一个指定的frame,并且切换进去
switch_to_frame(selector)
def switch_to_frame(self, selector): """ Switch to the specified frame. Usage: driver.switch_to_frame("i,el") """ el = self._locate_element(selector) self.base_driver.switch_to.frame(el)
-
找到一个指定的select,并且通过index进行选择
select_by_index(selector, index)
def select_by_index(self, selector, index): """ It can click any text / image can be clicked Connection, check box, radio buttons, and even drop-down box etc.. Usage: driver.select_by_index("i,el") """ el = self._locate_element(selector) Select(el).select_by_index(index)
以上的代码是封装了
_locate_element()
的几种方法,在具体使用封装过的代码的时候,只需要简单的调用即可。接下来的重点,是介绍_locate_element(selector)
的封装方式。- 查找元素:
find_element_by_...)
- 支持各种的查找:8种方式都需要支持,必须通过
selector
显示出分类-
selector
中需要包含一个特殊符号 - 实例化 封装好的类的时候,需要约定好是什么特殊符号
- 强制性用
硬编码 hard code
来实例化,例如,
或者?
或者 其他非常用字符=>
- 或者,构造方法中,传递
this.byChar
- 强制性用
-
- 要把查找到元素的返回给调用的地方:必须要有返回值,类型是
WebElement
def _locate_element(self, selector): """ to locate element by selector :arg selector should be passed by an example with "i,xxx" "x,//*[@id='langs']/button" :returns DOM element """ if self.by_char not in selector: return self.base_driver.find_element_by_id(selector) selector_by = selector.split(self.by_char)[0].strip() selector_value = selector.split(self.by_char)[1].strip() if selector_by == "i" or selector_by == 'id': element = self.base_driver.find_element_by_id(selector_value) elif selector_by == "n" or selector_by == 'name': element = self.base_driver.find_element_by_name(selector_value) elif selector_by == "c" or selector_by == 'class_name': element = self.base_driver.find_element_by_class_name(selector_value) elif selector_by == "l" or selector_by == 'link_text': element = self.base_driver.find_element_by_link_text(selector_value) elif selector_by == "p" or selector_by == 'partial_link_text': element = self.base_driver.find_element_by_partial_link_text(selector_value) elif selector_by == "t" or selector_by == 'tag_name': element = self.base_driver.find_element_by_tag_name(selector_value) elif selector_by == "x" or selector_by == 'xpath': element = self.base_driver.find_element_by_xpath(selector_value) elif selector_by == "s" or selector_by == 'css_selector': element = self.base_driver.find_element_by_css_selector(selector_value) else: raise NameError("Please enter a valid type of targeting elements.") return element
-
-
面向对象编程思想的运用
- 构造方法
- 类
- 普通方法
-
封装后的方法如何被调用
使用上面的封装类,就需要指定特定的 selector
类型 示例(分隔符以逗号 ,
为例)描述 id "account" 或者 "i,account" 或者 "id,account" 分隔符左右两侧不可以空格 xpath "x,//*[@id="s-menu-dashboard"]/button/i" css selector "s,#s-menu-dashboard > button > i" link text "l,退出" partial link text "p,退" name "n,name1" tag name "t,input" class name "c,dock-bottom 具体调用示例
def login(self, account, password, keep): """ 登录系统 :param account: :param password: :param keep: :return: 返回保持登录复选框的 checked 值 """ self.base_driver.type(self.LOGIN_ACCOUNT_SELECTOR, account) self.base_driver.type(self.LOGIN_PASSWORD_SELECTOR, password) current_checked = self.get_current_keep_value() if keep: if current_checked is None: self.base_driver.click(self.LOGIN_KEEP_SELECTOR) else: if current_checked == "true": self.base_driver.click(self.LOGIN_KEEP_SELECTOR) actual_checked = self.get_current_keep_value() self.base_driver.click(self.LOGIN_SUBMIT_SELECTOR) sleep(2) return actual_checked
3.3 测试报告的生成
如何生成测试报告
测试报告的种类
-
HTML 测试报告的生成
HTML测试报告需要引入HTMLTestRunner
HTMLTestRunner是基于Python2.7的,我们的课程讲义基于Python3.x,那么需要对这个文件做一定的修改。
测试的示例代码如下
# 声明一个测试套件 suite = unittest.TestSuite() # 添加测试用例到测试套件 suite.addTest(RanzhiTests("test_ranzhi_login")) # 创建一个新的测试结果文件 buf = open("./result.html", "wb") # 声明测试运行的对象 runner = HTMLTestRunner.HTMLTestRunner(stream=buf, title="Ranzhi Test Result", description="Test Case Run Result") # 运行测试,并且将结果生成为HTML runner.run(suite) # 关闭文件输出 buf.close()
- 相关学习