一. 自动化测试框架
1. 什么是自动化测试框架
简单来说,自动化测试框架就是由一些标准,协议,规则组成,提供脚本运行的环境。自动化测试框架能够提供很多便利给用户高效完成一些事情,例如,结构清晰开发脚本,多种方式、平台执行脚本,良好的报告去跟踪脚本执行结果。
框架具有以下一些优点:
1)代码复用
2)最大覆盖率
3)很低成本维护
4)很少人工干预
5)简单报告输出
2. 框架基本组件
我们来思考下框架组成部分:
1)测试数据分离【需要配置文件管理】
2)业务逻辑代码和测试脚本分离
3)报告和日志文件输出
4)自定义的库的封装
5)管理、执行脚本方式
6)第三方插件引入
7)持续集成
3. 概要设计
概要设计包括了四个大的模块:公共库模块(装饰器、测试报告生成包、发送邮件管理)、用例仓库(测试用例)、页面管理(单独对Web页面进行抽象,封装页面元素和操作方法)、执行模块
概要设计类图:
目录图:
- common目录是公共库,用来存放装饰器、测试报告生成包、发送邮件管理等模块包
- config目录中存放的是测试配置相关的文件,文件类型ini,包括测试的网址、浏览器驱动等信息
- interface和model存放的是接口和页面的基础类,各页面的接口和元素的封装:一个页面封装为一个类,一个元素封装为一个方法
- report用来存放输出的测试报告和截图
- salesplatform_unit目录下,testcase用来存放测试用例,testsuites用来存放测试用例集
- main.py 执行用例
3.1 详细设计与实现
3.1.1 页面管理
页面模式是页面与测试用例之间的桥梁,它将每个页面抽象成一个单独的页面类,为测试用例提供页面元素的定位和操作。
Page作为基类包含一个driver、url成员变量,它用来标记Selenium中的WebDriver,以便在BasePage的派生类中定位页面元素。ModelPage等作为派生类,可以提供相应页面元素的定位和操作方法。比如测试对象的登录页面:
从页面可以看出,需要操作的页面元素分别为:Username,Password和Login按钮,它们对应的操作为输入用户名和密码,点击登录按钮,具体代码级别的实现如下:
页面基类Page.py:
class Page:
"""
页面基础类
"""
url = None
def __init__(self, driver: webdriver.Chrome, url: str = None):
"""
初始化页面
:param url:
"""
self.driver = driver
self.driver.implicitly_wait(3)
if url is not None or self.url is not None:
self.driver.get(url if url is not None else self.url)
try:
self.driver.maximize_window()
except exceptions.WebDriverException:
pass
self.current_handle = self.driver.current_window_handle
handlers = self.driver.window_handles
self.driver.switch_to.window(handlers[-1])
登录页面Login.py:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2019-03-22 11:42
# @Author : Huo
# @Email : [email protected]
# @File : Login.py
# @Content : 登录页面封装
from models import Page
class Login(Page):
"""
登录
"""
def act_login(self, username, password):
"""
登录
:param username:
:param password:
:return:
"""
self.find_element_by_id("username").send_keys(username)
self.find_element_by_id("password").send_keys(password)
self.find_element_by_css_selector("button.ant-btn-lg").click()
def act_logout(self):
"""
退出登录
:return:
"""
self.find_element_by_link_text("登出").click()
3.1.2 公共库模块
公共库模块是为创建测试用例服务的,它主要包括装饰器、测试报告生成包、发送邮件管理等。
公共库模块涉及到的功能一般多而杂,在设计的时候只要遵循高内聚低耦合就可以了。
下图是该项目用到的一些功能模块:
3.1.3 用例仓库
用例仓库主要用来组织自动化测试用例。每条测试用例都被抽象成一个独立的类,并且均继承自unittest.TestCase类。 Python中的unittest库提供了丰富的测试框架支持,包括测试用例的setUp和tearDown方法,在实现用例的过程中可以重写。依托页面管理和公共库模块实现的页面方法和公共函数,每一个测试用例脚本的书写都会非常清晰简洁:
扩展基础TestCase类:TestCase.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2019-03-22 11:42
# @Author : Huo
# @Email : [email protected]
# @File : TestCase.py
# @Content : 扩展基础TestCase类
import unittest
from selenium import webdriver
from common import utils
from common.utils.Config import Config
class TestCase(unittest.TestCase):
"""
扩展基础TestCase类.
"""
config = {}
driver = None
close_browser_current_tab_on_tear_down = True
@classmethod
def setUpClass(cls):
"""
测试开始前执行的动作.
:return:
"""
# 处理弹窗
options = webdriver.ChromeOptions()
prefs = {
'profile.default_content_setting_values':
{
'notifications': 1
}
}
options.add_experimental_option('prefs', prefs)
cls.driver = webdriver.Chrome(chrome_options=options)
def tearDown(self):
"""
每个测试结束时执行的动作.
:return:
"""
utils.ScreenShot.save_screen_shot(self.driver, '用例完成自动截图', sub_directory_name='completed')
if self.close_browser_current_tab_on_tear_down is True:
self.driver.close()
@classmethod
def tearDownClass(cls):
"""
测试全部结束时执行的动作.
:return:
"""
cls.driver.quit()
登录界面测试用例:LoginTestCase.py
from . import TestCase
import models
from common.utils.Config import Config
from common import decorators
class LoginTestCase(TestCase):
"""
登录页测试用例
"""
@classmethod
def setUpClass(cls):
TestCase.setUpClass()
cls.login = models.Login(url=Config.config().cf.get("URL", "login_url"))
def tearDown(self):
"""将关闭浏览器标签的标志设置为False"""
self.close_browser_current_tab_on_tear_down = False
def test_login(self):
"""测试登录"""
self.login.act_login(Config.config().cf.get("User", "username"), Config.config().cf.get("User", "password"))
current_url = self.login.get_current_page_url()
self.assertEqual(current_url, "https://testsalesp.sunlands.wang/html/index.html#/home", "登录失败,url不正确")
3.1.4 用例执行模块(控制器)
执行模块主要用来控制测试用例脚本的批量执行,形成一个测试集。
#!/usr/bin/env python3
# _._ coding:utf-8 _._
#
"""
运行测试用例,形成测试报告
:author: ronghui.huo
"""
import os
import time
import unittest
import argparse
from salesplatform_unit import testcases
from salesplatform_unit import testsuites
from common.unittest_.runner import HTMLTestRunner
run_path = os.path.split(os.path.realpath(__file__))[0]
"""
举例说明:
python3 main.py -e production -r 正式环境,生成测试报告,所有套件
python3 main.py -ss Course dev环境,不生成测试报告,CourseTestSuite套件
"""
parser = argparse.ArgumentParser()
parser.add_argument('-e', '--environment', default='development',
help='运行的测试环境,默认为:development。可选值:development pre-production production')
parser.add_argument('-r', '--report', action="store_true", help='生成HTML测试报告')
parser.add_argument('-ss', '--suites', default=[], nargs='*', help='设置运行的测试套件,若不设置则执行所有套件')
opts = parser.parse_args()
all_test_suites = {_suite[:-9].lower(): _suite for _suite in testsuites.__dict__ if 'TestSuite' in _suite}
if __name__ == '__main__':
suite = unittest.TestSuite()
for suite_name in opts.suites or all_test_suites.keys():
if suite_name.lower() not in all_test_suites.keys():
raise NameError("无法找到对应的测试套件:{0}".format(suite_name))
suite.addTests(testsuites.__dict__[all_test_suites[suite_name.lower()]])
if opts.report is False:
runner = unittest.TextTestRunner()
else:
report_path = os.path.join(run_path, 'report')
now = time.strftime('%Y-%m-%d %H-%M-%S')
filename = os.path.join(report_path, now + 'report.html')
fp = open(filename, 'wb')
runner = HTMLTestRunner(stream=fp, title='课程库测试结果', description='测试报告.')
runner.run(suite)
测试报告:
4.优化与改进
对于该测试框架,基本满足web对象的自动化需求,但还是有些可以改进提高的地方,比如:
- 参数化用例
- log记录
- 结合业界优秀的自动化框架和实践持续改进
5. 其他
测试数据的存放
- 对于账号密码这种全局参数,可以写在ini配置文件中,然后用cf.get随时获取数据
- 对于一次性消耗的数据,比如每次生成新的用户openId,可以用随机函数的方式获取
- 对于一个接口中有多组参数的数据,可以参数化,放到json、excel、yaml、text中
- 对于可以反复使用的数据,比如订单的各种状态需要造数据的情况,可以放到 数据库,每次数据初始化,用完后再清理
- 对于少量的静态数据,比如一个接口的测试数据,也就 2-3 组,可以写到 py 脚本的开头,十年八年都不会变更的