Appearance
08你的第一个Web测试框架(二)
通过上一课时的学习,你对 unittest 已经有了一定的认知,这节课我将正式带你搭建 Web 测试框架。
你可以通过下图,对上节课内容进行更清晰的回顾。
实践出真知------创建 Web 测试框架雏形
Web 自动化测试,由于其对应于测试金字塔的 UI 层,所以也常被称为 UI 自动化测试,指的是使用代码模拟真实用户视角,以自动化的方式去执行业务操作,以及进行操作后的检查这样一个过程。
既然是 Web 自动化测试,必然要依托浏览器执行。当前在 Web 自动化测试领域,Selenium/WebDriver 仍然是市场占有率最高的的一款 UI 自动化工具,所以本节课我们就采用 Selenium/WebDriver 来作为我们 Web 自动化测试框架中与浏览器打交道的工具。
其实 Cypress 已严重挑战了 Selenium/WebDriver 的市场霸主地位,并大有后来者居上趋势,想要更多地了解 Cypress 框架,你可以参考我的书《前端自动化测试框架 -- Cypress从入门到精通》。
而 unittest 框架是一个相对完整的框架,可以应对测试用例/测试用例集的生成、测试用例的执行、测试执行后的清理及测试报告,所以如下图所示,两者结合我们就有了 Web 自动化测试框架的雏形:
Web 自动化测试框架雏形图
下面我们按照上一课时提及的"使用 unittest 框架创建测试用例的步骤"把这个框架创建起来。
1.首先,我们先给定项目的文件结构:
python
|--lagouTest
|--tests
|--test_baidu.py
|--__init__.py
|--main.py
|--__init__.py
2.测试类 test_baidu.py 的内容如下:
python
# coding=utf-8
from selenium import webdriver
import unittest
import time
class Baidu(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(30)
self.base_url = "http://www.baidu.com/"
def test_baidu_search(self):
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").send_keys("iTesting")
driver.find_element_by_id("su").click()
time.sleep(2)
search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
self.assertEqual('iTesting' in search_results, True)
@unittest.skip('i want to skip')
def test_baidu_set(self):
driver = self.driver
driver.get(self.base_url + "/gaoji/preferences.html")
m = driver.find_element_by_xpath(".//*[@id='nr']")
m.find_element_by_xpath("//option[@value='10']").click()
def tearDown(self):
self.driver.quit()
if __name__ == "__main__":
unittest.main(verbosity=2)
需要注意的是,要想正确运行 Selenium,需要安装相应的依赖,包括 Selenium 和对应的 WebDriver,我以 Win10 下运行 Chrome 为例:
python
# 1.安装Selenium,假设lagouTest项目在D盘的_Automation文件夹下
# D:\_Automation\lagouTest>pip install selenium
# 2. 安装Chrome Driver
# 从如下地址选择跟你浏览器版本一致的chrome Driver下载:
# http://npm.taobao.org/mirrors/chromedriver
# 并将解压后的chromedriver.exe放到python安装目录下的scripts文件夹下。
# Win10下默认路径为用户目录下的AppData:
# C:\Users\Admin\AppData\Local\Programs\Python\Python38-32\Scripts
# 3.进入环境配置,编辑系统变量里path变量,在最后面加上Chrome的安装路径:
# C:\Program Files\Google\Chrome\Application
"Baidu"这个测试类我写得非常不优雅,项目配置、元素定位、测试数据全部耦合在一块儿,现在先不去管它,我会在后面的课时慢慢优化它,带你认识框架设计的全过程。
3.main.py 的内容如下:
python
# coding=utf-8
import os
import unittest
if __name__ == "__main__":
suite = unittest.defaultTestLoader.discover(os.path.join(os.path.dirname(__file__), "tests"),pattern='*.py',top_level_dir=os.path.dirname(__file__))
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
4.运行 main.py,我们将看到如下结果:
python
test_baidu_search (tests.test_baidu.Baidu) ... ok
test_baidu_set (tests.test_baidu.Baidu) ... skipped 'i want to skip'
----------------------------------------------------------------------
Ran 2 tests in 8.564s
OK (skipped=1)
可以看到这个测试运行成功了。 假设我们需要运行更多的测试用例怎么办?仅仅需要在 tests 文件夹下添加相应的测试类就好了。这样,一个基于 unittest 的 Web 端测试框架的雏形就搭建成功了,是不是非常简单?
实践出真知------优化 Web 测试框架
现在我们的框架虽然可以测试了,但有一个问题:测试报告直接打印在 Console 里,不利于我们查看测试运行的历史。那么能不能把测试报告给持久化呢?我们来看一下解决方案。
1. 直接存储测试运行结果报告
新添加一个测试报告处理文件 txtReport.py
html
|--lagouTest
|--tests
|--test_baidu.py
|--__init__.py
|--main.py
|--__init__.py
|--txtReport.py
其中,txtReport.py 的内容如下:
python
__author__ = 'iTesting'
# -*-coding=utf-8 -*-
import os
import re
import time
class Test(object):
def __init__(self):
self.test_base = os.path.dirname(__file__)
# 获取tests文件夹所在路径
self.test_dir = os.path.join(self.test_base, 'tests')
# 列出所有待测试文件
self.test_list = os.listdir(self.test_dir)
# 定义正则匹配规则,过滤__init__.py和 *.pyc文件
self.pattern = re.compile(r'(__init__.py|.*.pyc)')
# 测试结果写文件
if not os.path.exists(os.path.join(self.test_base,"log.txt")):
f = open(os.path.join(self.test_base,"log.txt"),'a')
else:
f = open(os.path.join(self.test_base,"log.txt"),'w')
f.flush()
f.close()
# 运行符合要求的测试文件并写入log.txt
def run_test(self):
for py_file in self.test_list:
match = self.pattern.match(py_file)
if not match:
os.system('python %s 1>>%s 2>&1' %(os.path.join(self.test_dir,py_file),os.path.join(self.test_base,"log.txt")))
if __name__ == "__main__":
test = Test()
test.run_test()
在 Pycharm 或者命令行里执行这个文件,你会发现测试被运行且运行报告 log.txt 生成在根目录下。
但是你会发现,这个报告还不够好,仅仅是把 Console 里的内容重定向到文件里罢了。正常情况下,我们的测试报告都是比较美观的,比如说 HTML 格式。
2. 使用测试报告模块生成测试报告
常用的测试报告模块有 HTMLTestRunner 和 allure。下面我以 HTMLTestRunner 为例,来演示下如何生成测试报告。而如何使用 allure 生成测试报告,我们放在下节 "09 | 你的第一个 API 测试框架" 讲。
首先,我们更改下项目结构,创建一个生成测试报告的文件 html_reporter.py。更新后的项目结构如下:
java
|--lagouTest
|--tests
|--test_baidu.py
|--__init__.py
|--common
|--html_reporter.py
|--__init__.py
|--HTMLTestRunner.py
|--main.py
|--__init__.py
|--txtReport.py
html_reporter.py 中的内容如下:
python
__author__ = 'iTesting'
import os
import time
import HTMLTestRunner
class GenerateReport():
def __init__(self):
now = time.strftime('%Y-%m-%d-%H_%M', time.localtime(time.time()))
self.report_name = "test_report_" + now + ".html"
self.test_base = os.path.dirname(os.path.dirname(__file__))
if os.path.exists(os.path.join(self.test_base, self.report_name)):
os.remove(os.path.join(self.test_base, self.report_name))
fp = open(os.path.join(self.test_base, self.report_name), "a")
fp.close()
def generate_report(self, test_suites):
fp = open(os.path.join(self.test_base, self.report_name), "a")
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title="Test_Report_iTesting",
description="Below report show the results of auto run")
runner.run(test_suites)
GenerateReport 类有一个构造函数类 init.py,里面实现了 test 文件的建立。另外定义了一个 generate_report 的类方法,来运行并生成测试报告。
可以看到,在 html_reporter.py 中,我导入了 HTMLTestRunner,那么 HTMLTestRunner 模块是如何导入呢?一般情况下通过tungwaiyip.info下载即可。
但 HTMLTestRunner 下载后直接应用于 Python 3 会出现运行错误,所以我直接给你提供一个修复错误后的可用版本,你可以直接进入拉勾教育百度网盘(提取码: y3dw)直接下载。
最后,我们需要改动下 main.py 的内容,使之应用 HTMLTestRunner 这个测试报告。更改后 main.py 的内容如下:
python
__author__ = 'iTesting'
import unittest,os
from common.html_reporter import GenerateReport
if __name__ == "__main__":
suite = unittest.defaultTestLoader.discover(os.path.join(os.path.dirname(__file__),"tests"),\
pattern='*.py',top_level_dir=os.path.dirname(__file__))
html_report = GenerateReport()
html_report.generate_report(suite)
运行 main.py 文件,你将看到一个 html 格式的测试报告文件被生成了, 它的详细内容如下:
至此,我们的第一个 Web 自动化测试框架就优化得差不多了,但是请再次查看 test_baidu.py 这个文件,我们以这个文件里 test_baidu_search 这个类方法为例:
python
def test_baidu_search(self):
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").send_keys("iTesting")
driver.find_element_by_id("su").click()
time.sleep(2)
search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
self.assertEqual('iTesting' in search_results, True)
看看这个方法有哪些弊端:
如果我元素定位改变了(对应例子里是"kw"改变),那么我是不是只能在这个代码文件里改啊? 如果我有多个地方引用,我是不是就要改多遍呢?
看这个方法,元素定位、元素操作都是耦合在一起的,我是不是无法一眼就知道你在做什么操作啊?
如果我有别的测试要重用你这个方法怎么办?
这些问题一抛出来,大家就知道,我们的框架还有待优化的部分,也有部分同学会猜出,这个就必须讲到 Page Object 了,而这个 Page Object 的详细用法,我将会在 "10 | 如何使用 Page Object 设计模型?" 中为你详细讲解。
另外unittest框架其实非常强大,它还可以做Mock,关于Mock的知识"使用"和"自主实现",我将在后续的章节 "20 | 告别依赖,Mock Server" 必杀技中为你详细讲解。
总结
unittest 是 Python 里非常经典的测试框架,借助 unittest,你可以完成任何自动化测试框架的搭建。最近两个课时我们详细讲解了 unittest 这个框架,以及 Web 测试框架的搭建过程。
通过学习,你应该对 unittest 的原理、各个组成部分、详细语法、经典使用方式都了然于胸了,并且你应该能够按照我给的步骤,使用 unittest 一 步步搭建起你自己的 Web 测试框架了。
最后,希望大家能够多学多练,把本节课的代码实际操练一番,为下一课时 "09 | 你的第一个 API 测试框架" 做准备。有任何问题,欢迎你在评论区留言讨论。
想要了解更多关于测试框架的介绍,你可关注我的公众号iTesting,回复"测试框架"查看。