Python+Selenium+unittest自动化测试框架

一. 自动化测试框架

1. 什么是自动化测试框架

简单来说,自动化测试框架就是由一些标准,协议,规则组成,提供脚本运行的环境。自动化测试框架能够提供很多便利给用户高效完成一些事情,例如,结构清晰开发脚本,多种方式、平台执行脚本,良好的报告去跟踪脚本执行结果。

框架具有以下一些优点:
1)代码复用
2)最大覆盖率
3)很低成本维护
4)很少人工干预
5)简单报告输出

2. 框架基本组件

我们来思考下框架组成部分:

1)测试数据分离【需要配置文件管理】
2)业务逻辑代码和测试脚本分离
3)报告和日志文件输出
4)自定义的库的封装
5)管理、执行脚本方式
6)第三方插件引入
7)持续集成

image.png

3. 概要设计

概要设计包括了四个大的模块:公共库模块(装饰器、测试报告生成包、发送邮件管理)、用例仓库(测试用例)、页面管理(单独对Web页面进行抽象,封装页面元素和操作方法)、执行模块

概要设计类图:


类图1.jpg

目录图:


image.png
  1. common目录是公共库,用来存放装饰器、测试报告生成包、发送邮件管理等模块包
  2. config目录中存放的是测试配置相关的文件,文件类型ini,包括测试的网址、浏览器驱动等信息
  3. interface和model存放的是接口和页面的基础类,各页面的接口和元素的封装:一个页面封装为一个类,一个元素封装为一个方法
  4. report用来存放输出的测试报告和截图
  5. salesplatform_unit目录下,testcase用来存放测试用例,testsuites用来存放测试用例集
  6. main.py 执行用例

3.1 详细设计与实现

3.1.1 页面管理

页面模式是页面与测试用例之间的桥梁,它将每个页面抽象成一个单独的页面类,为测试用例提供页面元素的定位和操作。


image.png

Page作为基类包含一个driver、url成员变量,它用来标记Selenium中的WebDriver,以便在BasePage的派生类中定位页面元素。ModelPage等作为派生类,可以提供相应页面元素的定位和操作方法。比如测试对象的登录页面:

image.png

从页面可以看出,需要操作的页面元素分别为: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   : huoronghui@sunlands.com
# @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 公共库模块

公共库模块是为创建测试用例服务的,它主要包括装饰器、测试报告生成包、发送邮件管理等。
公共库模块涉及到的功能一般多而杂,在设计的时候只要遵循高内聚低耦合就可以了。
下图是该项目用到的一些功能模块:


image.png

3.1.3 用例仓库

用例仓库主要用来组织自动化测试用例。每条测试用例都被抽象成一个独立的类,并且均继承自unittest.TestCase类。 Python中的unittest库提供了丰富的测试框架支持,包括测试用例的setUp和tearDown方法,在实现用例的过程中可以重写。依托页面管理和公共库模块实现的页面方法和公共函数,每一个测试用例脚本的书写都会非常清晰简洁:


image.png

扩展基础TestCase类:TestCase.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2019-03-22 11:42
# @Author  : Huo
# @Email   : huoronghui@sunlands.com
# @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 <ronghui.huo@kgc.cn>
"""

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)

测试报告:


image.png

4.优化与改进

对于该测试框架,基本满足web对象的自动化需求,但还是有些可以改进提高的地方,比如:

  1. 参数化用例
  2. log记录
  3. 结合业界优秀的自动化框架和实践持续改进

5. 其他

测试数据的存放

  1. 对于账号密码这种全局参数,可以写在ini配置文件中,然后用cf.get随时获取数据
  2. 对于一次性消耗的数据,比如每次生成新的用户openId,可以用随机函数的方式获取
  3. 对于一个接口中有多组参数的数据,可以参数化,放到json、excel、yaml、text中
  4. 对于可以反复使用的数据,比如订单的各种状态需要造数据的情况,可以放到 数据库,每次数据初始化,用完后再清理
  5. 对于少量的静态数据,比如一个接口的测试数据,也就 2-3 组,可以写到 py 脚本的开头,十年八年都不会变更的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,237评论 6 537
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,957评论 3 423
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 177,248评论 0 382
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,356评论 1 316
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,081评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,485评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,534评论 3 444
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,720评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,263评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,025评论 3 356
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,204评论 1 371
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,787评论 5 362
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,461评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,874评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,105评论 1 289
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,945评论 3 395
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,205评论 2 375

推荐阅读更多精彩内容