Python3+Selenium2完整的自动化测试实现之旅(七):完整的轻量级自动化框架实现

一、前言

        前面系列Python3+Selenium2自动化系列博文,陆陆续续总结了自动化环境最基础环境的搭建、IE和Chrome浏览器驱动配置、selenium下的webdriver模块提供的元素定位和操作鼠标、键盘、警示框、浏览器cookie、多窗口切换等场景的方法、web自动化测试框架、python面向对象和POM设计模型以及python下的单元测试模块unittest模块。本来计划陆续循序渐进的继续写一些篇章总结python面向对象编程、python下的logging日志模块、os.path路径处理模块、time模块处理时间,如格式化时间输出以及一些第三方模块如读取和向excel文件中写入数据的模块:xlrd和xlwt,写完这些后,再开始写本篇的终极目标:编写一个轻量级的自动化测试框架。最终放弃这样做,原因:楼主发现python面向对象编程、python的这些标准库模块或者第三方模块其实很多博客已经做了很好的总结,总之,会学习的人,百度后总能从一大堆的文章中,查看并甄选出对自己解决问题或者思考有帮助和收获的文章,如百度python logging模块使用,多看博文就一定能找到对自己有帮助的文章。曾经楼主也是在学习实践中遇到很多坑,也是根据IDE输出上提示的错误自己先思考解决,还是不行就百度一下或者看书,并深度学习下这块的内容,然后再去解决问题,从这些博文以及自己买的python类的书籍中也是受益良多,这也是楼主一直以来自己的学习方式。这里,每个人都有自己的学习和思考问题的方式,找准适合自己的学习方式并执行它,完成一个阶段目标,然后设置下一个新目标,并为之努力。因此,在写这个轻量级的自动化web测试框架前,我跳过了上述诸多内容,包括且不限于:python面向对象编程、python常用标准库loggging、time、os.path运用等等,在后面的轻量级框架代码中会有部分注释,对于这些python相关的内容学习,大家根据自己的情况去充实,坚持学习并持之以恒。在楼主身边,有太多类似的人学习总是三天晒鱼、两天打网的,完全沉不下心来学习东西,浅尝辄止,没有积淀,如果认定一个东西就去想办法搞定,加油!楼主也在为自己新的目标fighting中,当然目标是广义的,可以是生活方面、工作方面、情感方面.......,好像跑偏题了O(∩_∩)O,这些人生鸡汤似的废话就不说了,看看下面这个web自动化测试框架是如何实现的吧~

 二、项目架构说明

       该项目架构基于楼主公司的一款B/S软件设计,大家也可以根据自己的被测软件来构建适合自己的架构层级,当然也可以参考楼主的。做自动化测试项目,当搞懂了思想和方法,其实都是万变不离其宗,就跟写代码一样,语言万千种,唯一不变的就是语言中殊途同归的思想,因此,玩会了套路自然就能凌驾于套路之上,运用并加入自己的东西。在PyCharm中新建如下的项目层级:

       有过开发经历的小伙伴都知道,有个好的交互式开发工具对于我们创建和管理清晰的项目架构很方便,PyCharm就是一款交互良好的python开发工具。楼主上面的项目层级中部分目录和目录下的文件没展开,下面显示一个完整的目录结构,并说明每个目录是用来干嘛?放什么东西?

        当然这个项目层级设计,不是楼主一时间就固定下来的,也是在不断的摸索和采坑中,不断调整出的一个适合自己的框架目录层级。项目框架设计好了后,接下来就是慢慢补充内容一步步实现上面每个目录需要的东西。来吧,开始造轮子~

 三、配置文件设计

       首先,对于上面的config.ini的配置文件进行配置。说到配置文件,不管是开发人员还是测试人员都不会陌生,还有xml、txt等格式的配置文件,配置文件就是用来配置一些参数和固定的变量值,一般是程序固定不变的东西我们就放这里面,用于程序直接调用,如果修改配置文件中变量的值,程序调用该变量就会产生不同的输出行为。如在做自动化测试时,我们可以将测试的不同浏览器写入到该文件中,当我们需要调用firefox浏览器时,将参数设置成firefox即可,测试脚本将会在火狐浏览器进行。如下图,在配置文件中设置了浏览器参数、测试url、邮件服务器、邮件发送和接收者等参数。

至于为什么这个ini配置文件需要编辑成【###】然后下面是参数或者变量的赋值,自己百度学习:ini配置文件格式,看下是怎么编辑的?都有哪些要素?

、日志类模块的实现

       说到日志,大家都明白日志的作用,最明显的作用就是在程序的关键位置或者步骤节点下设置日志输出,当程序运行时,会输出日志,日志是我们查看程序运行情况和查找错误的重要手段。因此,对于自动化测试也是如此,我们需要知道自动化执行的情况以及执行错误的情况发生了什么,那就需要给你的自动化测试项目封装一个日志类的功能模块,用于输出日志。python语言封装了一个叫logging的标准库模块,能够设置日志等级以及怎么输出、输出到哪里。对于logging模块,大家可以自己针对性去学习该模块的使用。

       我们在上面的项目层级的models目录下创建log.py文件,在这个模块文件下定义一个叫做Logger的日志类,完成日志类的封装,编辑如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
'''
  Code description:封装日志类,定义日志文件输出格式和日志输出级别
  Create time:2018-11-8
  Developer:
'''
# -*- coding: utf-8 -*-
import logging
import time
import os.path
class Logger(object):
    def __init__(self,logger,CmdLevel = logging.INFO,FileLevel = logging.INFO):
        self.logger = logging.getLogger(logger)
        self.logger.setLevel(logging.DEBUG)   # 设置日志默认级别为DEBUG
        fmt = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s') # 设置日志输出格式
        currTime = time.strftime('%Y%m%d%H%M',time.localtime(time.time()))  # 格式化当前时间
        log_path = os.path.dirname(os.path.abspath('E:\V2200_AutoTest\\testcase')) + '/log/logs/'  # 设置日志文件保存路径
        # log_path = os.path.dirname(os.path.abspath('.')) + '/log/logs/'      # 相对路径写法
        # print(os.path.dirname(os.path.abspath('E:\V2200_AutoTest\\testcase')))
        print('得到的日志路径为:', log_path)
        log_name = log_path + currTime + '.log'  # 设置日志文件名称
 
        # 设置由文件输出
        fh = logging.FileHandler(log_name,encoding='utf-8'# 采用utf-8字符集格式防止出现中文乱码
        fh.setFormatter(fmt)
        fh.setLevel(FileLevel)  # 日志级别为INFO
 
        # 设置日志由控制台输出
        # sh = logging.StreamHandler(log_name)
        # sh.setFormatter(fmt)
        # sh.setLevel(CmdLevel)
        self.logger.addHandler(fh)  # 添加handler
    def getlog(self):
        return self.logger

     这样我们就自定义封装了一个简单的日志类模块,设置了日志输出级别、输出格式以及输出日志的位置,后面其他模块需要输出日志时,就可以调用引入该日志类。

 五、浏览器模块的实现

     日志类实现简单封装后,继续造轮子~。此部分用于封装浏览器模块,主要实现打开和关闭不同浏览器的方法,这里就用到了POM的思想,咱们封装了浏览器的类型和打开关闭方法,那么后面每条测试脚本就可以直接调用打开和关闭浏览器方法,脚本只需要专注具体的测试业务逻辑的实现即可。在models目录下新建broser_engine.py文件,自定义一个叫做BrowserEngine类,实现浏览器模块的封装,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
'''
  Code description:封装浏览器引擎类,读取配置文件实现浏览器类型的选择,并封装打开浏览器和退出的方法
  Create time:2018-11-12
  Developer:
'''
# -*- coding: utf-8 -*-
import configparser          # python解析配置文件模块
import os.path
from selenium import webdriver
from V2200.test.models.log import Logger     # 引入日志类模块
 
logger = Logger(logger="BrowserEngine").getlog()    # 实例化对象logger
 
class BrowserEngine(object):
    def __init__(self, driver):
        self.driver = driver   # 初始化构造函数,将参数driver self化便于后面创建的方法直接自动调用
    def open_browser(self, driver):
        '''
        :param driver: 读取配置文件,返回driver
        :return:
        '''
        config = configparser.ConfigParser()
        file_path = os.path.abspath('E:\V2200_AutoTest\V2200\config\config.ini')   # 绝对路径写法
        #print('得到的读取config文件的路径:',file_path)
        config.read(file_path,encoding='UTF-8')    # 读取配置文件
        browser = config.get("browserType", "browserName")
        logger.info("选择的浏览器是: %s ." % browser)
        url = config.get("testServer", "URL")
        logger.info("测试的平台URL是: %s" % url)
 
        if browser == "Firefox":
            driver = webdriver.Firefox()
            logger.info("Starting firefox browser.")
        elif browser == "Chrome":
            driver = webdriver.Chrome()
            logger.info("Starting Chrome browser.")
        elif browser == "Ie":
            driver = webdriver.Ie()
            logger.info("Starting IE browser.")
 
        driver.get(url)                        # 得到测试的url
        logger.info("浏览器的版本为:%s" % driver.capabilities['version'])  # 获取浏览器版本
        driver.maximize_window()
        logger.info("最大化浏览器窗口.")
        driver.implicitly_wait(10)
        return driver
 
    def quit_browser(self):
        self.driver.quit()

 六、页面基类的实现

        此部分用于封装页面基类,主要用于封装一些常用的公共方法,如截图方法、元素定位方法、元素通用操作方法、警示框处理方法等等,只要软件页面一些常用的操作都可以写在该页面基类中,这个页面基础类就类似于一个公共函数库一样,封装这些方法,后面有需要的地方直接调用即可。如下代码,已经封装了8大元素定位方法、截图方法、鼠标点击方法、警示框处理方法等,后续根据自己的需要自行补充丰富一些常用的功能函数或者方法。这里可以着重看下8大元素定位方法的封装~

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
'''
  Code description:页面基类,封装所有页面共用的方法
  Create time:2018-11-13
  Developer:
'''
# -*- coding: utf-8 -*-
import time
import os.path
from V2200.test.models.log import Logger
from selenium.common.exceptions import NoSuchElementException    # selenium下封装的判断元素是否存在的模块
logger = Logger(logger='BasePage').getlog()
class BasePage(object):
    # 构造方法,初始化参数driver,用于后面的方法直接调用
    def __init__(self,driver):
        self.driver = driver
    # 浏览器前进
    def forward_browser(self):
        self.driver.forward()
        logger.info("在当前页面中点击浏览器前进.")
    # 浏览器后退
    def back_browser(self):
        self.driver.back()
        logger.info("在当前页面中点击浏览器后退.")
    # 设置隐式等待时间
    def wait(self,seconds):
        self.driver.implicitly_wait(seconds)
        logger.info("设置隐式时间:%d 秒." % seconds)
    # 关闭当前窗口
    def close_window(self):
        try:
            self.driver.close()
            logger.info("关闭当前窗口.")
        except NameError as e:
            logger.error("关闭当前窗口出错,抛出错误提示:%s." % e)
    # 截图功能:得到截图并保存图片到项目image目录下
    def get_window_img(self):
        file_path = os.path.dirname(os.path.abspath('.')) + '/image/'  # 设置存放截图的路径
        # print('截图保存路径为:%s' % file_path)
        timeset = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))       # 格式化时间
        pic_name = file_path + timeset + '.png'                                   # 定义截图文件名称
        try:
            self.driver.get_screenshot_as_file(pic_name)
            logger.info('截图成功,图片保存路径为:/image.')
        except Exception as e :
            logger.error('截图出现异常',format(e))
            self.get_window_img()
 
    # 8大页面元素(对象)定位方法的封装
    def find_element(self,selector):
        '''
        使用‘=>’作为字符串分割符,后续实际测试用例根据输入的元素selector_by和selector_value 进行选择元素的定位类型
        :param selector:
        :return: element
        '''
        element = ''
        if '=>' not in selector:
            return self.driver.find_element_by_id(selector)
        selector_by = selector.split('=>')[0]   # 按=>分割符进行切割字符串,返回一个列表,得到列表的第一个元素,即元素的定位方法
        selector_value = selector.split('=>')[1# 得到列表的第二个元素,即元素定位的值
 
        if selector_by == 'i' or selector_by == 'id':
            try:
                element = self.driver.find_element_by_id(selector_value)
                logger.info("定位元素OK,实际定位元素方法:%s ,定位的元素的属性值:%s" % (selector_by,selector_value))
            except NoSuchElementException as e:
                logger.error("没找到元素,抛出异常:%s" % e)
                self.get_window_img()  # 截取当前窗口
        elif selector_by == 'n' or selector_by == 'name':
            element = self.driver.find_element_by_name(selector_value)
        elif selector_by == 'c' or selector_by == 'class_name':
            element = self.driver.find_element_by_class_name(selector_value)
        elif selector_by == 'l' or selector_by == 'link_text':
            element = self.driver.find_element_by_link_text(selector_value)
        elif selector_by == 'p' or selector_by == 'partial_link_text':
            element = self.driver.find_element_by_partial_link_text(selector_value)
        elif selector_by == 't' or selector_by == 'tag_name':
            element = self.driver.find_element_by_tag_name(selector_value)
        elif selector_by == 'x' or selector_by == 'xpath':
            try:
                element = self.driver.find_element_by_xpath(selector_value)
                logger.info("定位元素OK,实际定位元素方法:%s ,定位的元素的属性值:%s" % (selector_by, selector_value))
            except NoSuchElementException as e:
                logger.error("没找到元素,抛出异常:%s" % e)
                self.get_window_img()  # 截取当前窗口
        elif selector_by == 'c' or selector_by == 'css_selector':
            element = self.driver.find_element_by_css_selector(selector_value)
        else:
            raise NameError("请输入正确的目标元素类型.")
        return element  # 返回变量element
 
    # 封装输入框方法
    def type(self,selector,text):
        el = self.find_element(selector)
        el.clear()
        try:
            el.send_keys(text)
            logger.info("输入的文本内容为:%s" % text)
        except NameError as e:
            logger.error("输入的内容异常,抛出异常:%s" % e)
            self.get_window_img()
 
    # 清除文本内容
    def clear(self,selector):
        el = self.find_element(selector)
        try:
            el.clear()
            logger.info("清除输入框文本信息OK")
        except NameError as e:
            logger.error("清除输入框内容失败:抛出异常: %s" % e)
            self.get_window_img()
    # 封装点击元素的动作
    def click(self,selector):
        el = self.find_element(selector)
        try:
            el.click()
            logger.info("点击元素动作完成")
        except NameError as e:
            logger.error("点击事件失败,抛出异常:%s" % e)
    # 获取打开的url地址标题
    def get_page_title(self):
        logger.info("当前打开的url地址标题为:%s" % self.driver.title)
        return self.driver.title
 
    # 获取警示框,并得到提示框信息和关闭提示框
    def get_alert(self):
        el = self.driver.switch_to.alert    # 获取窗口弹窗的方法
        try:
            assert '用户名或者密码错误' in el.text    # el.text方法获取提示框内容
            logger.info("弹窗提示正确")
            el.accept()                              # 点击弹窗确认按钮
        except Exception as e:
            print('弹窗提示错误', format(e))
 
    @staticmethod        #  静态方法:不强制要求传递参数,类可以不用实例化就能调用该方法
    def sleep(seconds):
        time.sleep(seconds)
        logger.info("等待时间是:%s 秒" % seconds)

 七、登陆页面元素的封装

        在上面我们实现了页面基类的封装,下图为楼主公司的一个软件登陆页面,在page_obj目录下新建home_page.py实现这个登陆页面元素定位和元素操作方法的封装

 

      代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
'''
  Code description: 继承基类,封装登陆页面所有的元素和元素的操作方法
  Create time:2018-11-16
  Developer:
'''
# -*- coding: utf-8 -*-
from V2200.test.models.base_page import BasePage
import xlrd                                    # excel操作相关的模块
from V2200.test.models.log import Logger
excelfile_path = 'E:\V2200_AutoTest\V2200\data\\testdata\elementData.xlsx'
workbook = xlrd.open_workbook(excelfile_path)
table_sheetName = workbook.sheet_by_name('登陆页面业务组件')
logger = Logger(logger='HomePage').getlog()
class HomePage(BasePage):
    def __init__(self,driver):
        BasePage.__init__(self,driver)        # 继承父类,并调用父类的初始化方法
        self.input_username = table_sheetName.cell(1,1).value   # 读取excel表中用户名输入框元素
        self.input_password = table_sheetName.cell(2,1).value   # 读取excel表中密码输入元素
        self.rempwd = table_sheetName.cell(3,1).value           # 读取excel表中是否记住密码按钮元素
        self.loginBtn = table_sheetName.cell(4,1).value         # 读取excel表中登陆按钮元素
        self.centerBtn = table_sheetName.cell(6,1).value        # 读取excel表中切换到中心用户的按钮
        logger.info("读取excel文件中登陆页面相关元素数据完成")
    def center_user(self):
        self.click(self.centerBtn)
    def user(self,text):
        self.type(self.input_username,text)
    def pwd(self,text):
        self.type(self.input_password,text)
    def ifrempwd(self):
        self.click(self.rempwd)
    def login(self):
        self.click(self.loginBtn)

  这里引入了第三方的xlrd模块,用于读取excel文件中的数据,当然还有xlwt模块用于向excel写数据,说白了这两个模块就是实现操作excel,上面代码只用到了xlrd模块,在编写上面登陆页面的封装前,咱们先将登陆页面定位的元素和元素属性写到对应的excel表中,这样做的好处就是实现测试数据和测试脚本的分离,如果页面元素发生变化,那么我们就只需要修改excel中的元素属性而不需要修改代码,在data/testdata目录下新建名称为elementData.xlsx的文件,excel编辑内容如下:

    后续各个页面的元素都是定位以及元素的数据都是可以写在不同的sheet中,至于xlrd模块具体向excel中读数据的方法以及使用,这里也是不做介绍,自己百度学习练习下就知道了。

软件其他页面的封装也是类似,按照上面的思想来就OK了。

 八、登陆页面测试脚本的编写

    通过上面封装的基类和登陆页面类,在unittest框架下开始编写具体的测试脚本。测试脚本在testcase下,如登陆功能的脚本我们写在:testcase/login_page_case目录下,其他页面的脚本写在对应的目录下。在testcase/login_page_case目录下创建:test_login_success.py和test_login_unsuccess.py分别表示登陆成功的脚本和登陆不成功的脚本。如下代码:

登陆成功代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
'''
  Code description:测试登陆  Create time:2018-11-20  Developer:
'''
# -*- coding: utf-8 -*-
import unittest
# unittest执行测试用例,默认是根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。
import time
from V2200.test.models.browser_engine import BrowserEngine
from V2200.test.page_obj.home_page import HomePage
import xlrd
excelfile_path = 'E:\V2200_AutoTest\V2200\data\\testdata\elementData.xlsx'
workbook = xlrd.open_workbook(excelfile_path)
table_sheetName = workbook.sheet_by_name('登陆页面业务组件')
class Login(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # 测试前置条件
        browser = BrowserEngine(cls)
        cls.driver = browser.open_browser(cls)
    @classmethod
    def tearDownClass(cls):
        # 测试结束后环境的复原
        cls.driver.quit()
# case1:正确的用户密码登陆
    def test_1_login_sucess(self):
        homepage = HomePage(self.driver)
        homepage.user(table_sheetName.cell(1,2).value)     # 读取excel中的数据
        homepage.pwd(table_sheetName.cell(2,2).value)
        homepage.ifrempwd()
        homepage.login()
        time.sleep(2)
        try:
            assert '视频监控' in homepage.get_page_title()
            print('test title success')
            homepage.get_window_img()  # 调用Basepage类封装的截图方法
        except Exception as e:
            print('test title error', format(e))
 
if  __name__  ==  '__main__':
    unittest.main()            # 将一个单元测试模块变成可以直接运行的测试脚本

  登陆不成功代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
'''
  Code description:测试登陆  Create time:2018-11-20  Developer:
'''
# -*- coding: utf-8 -*-
import unittest
# unittest执行测试用例,默认是根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。
import time
from V2200.test.models.browser_engine import BrowserEngine
from V2200.test.page_obj.home_page import HomePage
import xlrd
# from V2200.test.page_obj.link_page import LinkPage
excelfile_path = 'E:\V2200_AutoTest\V2200\data\\testdata\elementData.xlsx'
workbook = xlrd.open_workbook(excelfile_path)
table_sheetName = workbook.sheet_by_name('登陆页面业务组件')
class LoginUnsuccess(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # 测试前置条件
        browser = BrowserEngine(cls)
        cls.driver = browser.open_browser(cls)
    @classmethod
    def tearDownClass(cls):
        # 测试结束后环境的复原
        cls.driver.quit()
# case2:错误的用户+正确的密码登陆
    def test_2_login_erroruser(self):
        homepage = HomePage(self.driver)
        homepage.user(table_sheetName.cell(1, 3).value)     # 读取excel中的数据
        homepage.pwd(table_sheetName.cell(2, 3).value)
        homepage.ifrempwd()
        homepage.login()
        time.sleep(2)
        try:
            assert '视频监控' in homepage.get_page_title()
            print('test title success')
            homepage.get_window_img()  # 调用Basepage类封装的截图方法
        except Exception as e:
            print('test title error',format(e))
 
# case3:正确的用户+错误的密码登陆
    def test_3_login_errorpasswd(self):
        homepage = HomePage(self.driver)
        homepage.user(table_sheetName.cell(1, 4).value)     # 读取excel中的数据
        homepage.pwd(table_sheetName.cell(2, 4).value)
        homepage.ifrempwd()
        homepage.login()
        time.sleep(2)
        try:
            assert '视频监控' in homepage.get_page_title()
            print('test title success')
            homepage.get_window_img()  # 调用Basepage类封装的截图方法
        except Exception as e:
            print('test title error', format(e))
# case4:错误的用户+错误的密码登陆
    def test_4_login_erroruser_errorpasswd(self):
        homepage = HomePage(self.driver)
        homepage.user(table_sheetName.cell(1, 5).value)     # 读取excel中的数据
        homepage.pwd(table_sheetName.cell(2, 5).value)
        homepage.ifrempwd()
        homepage.login()
        time.sleep(2)
        try:
            assert '视频监控' in homepage.get_page_title()
            print('test title success')
            homepage.get_window_img()  # 调用Basepage类封装的截图方法
        except Exception as e:
            print('test title error', format(e))
 
if  __name__  ==  '__main__':
    unittest.main()            # 将一个单元测试模块变成可以直接运行的测试脚本

 九、测试执行控制模块

      完成上面的测试脚本编写后,对于自动化测试还需要有一个测试执行控制的部分,用来控制执行哪些用例集,生成HTML可视化的测试报告,并实现测试报告邮件发送。

在runtest目录下新建run_all_case.py,编辑如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
'''
  Code description: TestLoader测试case,并执行得到的所有测试集,生成html文件的测试报告并邮件发送测试报告
  Create time:2018-11-20
  Developer:
'''
# -*- coding: utf-8 -*-
import HTMLTestRunner1     # 导入开源的测试报告生成HTML格式的模块
import os.path
import time
import unittest
import configparser        # 解析配置文件模块
from email.mime.text import MIMEText
from email.header import Header
import smtplib
"""
发邮件需要用到python两个模块,smtplib和email,这俩模块是python自带的,只需import即可使用。
smtplib模块主要负责发送邮件,email模块主要负责构造邮件。
其中MIMEText()定义邮件正文,Header()定义邮件标题。MIMEMulipart模块构造带附件
 
"""
# ===============定义邮件发送============
def send_mail(file_new):
 
    config = configparser.ConfigParser()
    file_path = os.path.dirname(os.path.abspath('.')) + '/V2200/config/config.ini'
    config.read(file_path, encoding='UTF-8')                 # 读取config配置文件
    emailserver = config.get("emailserver", "emailservice")
    from_user = config.get("emailfrom_user", "from_user")
    from_passwd = config.get("emailfrom_passwd", "from_passwd")
    to_user = config.get("emailto", "to_user")
 
    f = open(file_new,'rb')
    mail_boy = f.read()
    f.close()
    msg = MIMEText(mail_boy,'html','utf-8')        # 定义邮件正文
    msg['Subject'] = Header('V2200自动化测试报告','utf-8'# 定义邮件标题
    smtp = smtplib.SMTP()
    smtp.connect(emailserver)                     #  连接邮箱服务器
    smtp.login(from_user,from_passwd)   #  邮件发送方登陆
    smtp.sendmail(from_user,to_user,msg.as_string())  # 邮件发送者和接收者
    smtp.quit()
    print("邮件已经发送,请注意查收!")
 
# ==============找到最新生成的测试报告文件===========
def new_report(report_path):
    lists = os.listdir(report_path)   # 得到项目目录下所有的文件和文件夹
    lists.sort(key=lambda fn:os.path.getmtime(report_path + '\\' + fn))  # 将得到的文件和文件夹按创建时间排序
    file_new = os.path.join(report_path,lists[-1])  # 获取最新创建的文件
    print(file_new)
    return file_new
# 测试用例路径
# case_path = os.path.join(os.getcwd(),'testcase')
case_path = os.path.abspath('E:\V2200_AutoTest\\testcase')
print(case_path)
# 测试报告路径
report_path = os.path.abspath('E:\V2200_AutoTest\\testreport')
print(report_path)
def all_case():
    '''
    找到case_path路径下所有以test_login开头的测试用例文件,保证每个子目录都是一个包文件,即该目录下
    有__init__.py文件,才能获取到多个目录下的所有test*.py的文件下的所有测试用例
    '''
    all_case = unittest.defaultTestLoader.discover(case_path,pattern="test_login*.py",top_level_dir=None)
    print(all_case)
    return all_case
if __name__ == '__main__':
    # 获取当前时间,并格式化时间
    now_time = time.strftime("%Y-%m-%d-%H_%M_%S",time.localtime(time.time()))
    # html测试报告路径
    report_html = os.path.join(report_path,"result_"+now_time+".html")
    fp = open(report_html,'wb')     # 打开一个文件,将测试结果写入该文件中
    '''
    wb:以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,
    即原有内容会被删除。如果该文件不存在,创建新文件
    '''
    runner = HTMLTestRunner1.HTMLTestRunner(stream=fp,
                                           title=u'V2200自动化测试报告,测试结果如下:',
                                           description=u'用例执行情况:')
 
    runner.run(all_case())   # 执行所有测试case
    fp.close()
    mail_report = new_report(report_path)
    send_mail(mail_report)

  该代码,编写了怎么获取需要执行的测试用例脚本和引入生成可视化测试报告的模块和发送邮件模块等,对于这几个模块自己多学习下就能掌握。

、测试执行效果

    通过上面这些类的封装以及测试脚本的编写,算是完成了我们自动化测试框架的基本具备的东西。忙活了这么久,是时候来看看咱们的效果了。PyCharm中运行run_all_case.py,运行完成后的效果如下:

 咱们再看看log/logs路径下生成的日志,就如下图这样:

 同时执行完成后,在testreport目录下会生成HTML格式的可视化测试报告文件,用浏览器打开效果如下:

 这报告是不是很酷炫啊,O(∩_∩)O哈哈~

     还有测试报告发送邮件给到指定的邮箱哦,如果你的自动化测试执行完了,可以把该自动化测试报告自动邮件发给你的leader,领导看到了是不是对你另眼相看?楼主上面的代码设置发送的是楼主公司内网使用的邮箱:foxmail,效果如下:

到这里算是完成了咱们自动化测试框架,并取得了一定的成果~~

十一、整个自动化测试框架的总结和反思

        其实到第十节的介绍,楼主算是成功的做出了一个轻量级的测试框架,but,回过头来继续思考,还是有诸多需要优化和待下一步解决的问题:

1.页面基类还需要补充更多的公共函数或者方法;

2.可视化HTML测试报告内容还不够丰富,没有完善的测试执行失败的用例的详细描述和测试截图附件显示;

3.整个框架的部分逻辑还需要优化和改进;

4.待解决的问题:没实现测试脚本的持续集成和定时执行,现在想到的是配合jenkins持续集成来达到自动构建测试执行任务;

5.想独立开发一个web测试平台,现在想到的是学习Django的web框架来开发一个自动化测试平台;

    对于这样不足和构想,楼主也是会继续学习相关的知识,并一步步实现它,对于看到该博客的朋友们也可以给楼主一些好的建议和指出错误,希望有对自动化测试有兴趣的朋友,大家共同学习和进步哦。

 

posted @   那年故乡的明月  阅读(1256)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· 地球OL攻略 —— 某应届生求职总结
点击右上角即可分享
微信分享提示