Skip to content

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,回复"测试框架"查看。