Selenium+python怎么搭建自动化测试框架、执行自动化测试用例、生成自动化测试报告、发送测试报告邮件

本人在网上查找了很多做自动化的教程和实例,偶然的一个机会接触到了selenium,觉得非常好用。后来就在网上查阅各种selenium的教程,但是网上的东西真的是太多了,以至于很多东西参考完后无法系统的学习和应用。

以下整理的只是书中自动化项目的知识内容,介绍怎么搭建自动化测试框架、执行自动化测试用例、生成自动化测试报告、发送测试报告邮件....,具体的Selenium和python语言基础不做介绍

一、项目结构介绍

下面逐级介绍此目录与文件的作用

复制代码
mztstpro/
 
|-----bbs/
 
| |-----data/
 
| |-----report/
 
| |------image/
 
| |-----test_case/
 
| |------models/
 
| |----driver.py
 
| |----function.py
 
| |----myunit.py
 
| |------page_obj/
 
| |----*Page.py
 
| |------*_sta.py
 
|-----driver/
 
|-----package/
 
|-----run_bbs_test.py
 
|-----startip.bat
 
|-----自动化测试项目说明文档.docx

1.mztestpro测试项目

bbs:用于存放BBS项目的测试用例、测试报告和测试数据等。

driver:用于存放浏览器驱动。如selenium-server-standalone-2.47.0jar、chromedriver.exe、IEDriverServer.exe等。在执行测试前根据执行场景将浏览器驱动复制到系统环境path目录下。

package:用于存放自动化所用到的扩展包。例如:HTMLTestRunner.py属于一个单独模块

run_bbs_test.py:项目主程序。用来运行社区(BBS)自动化用例。

startup.bat:用于启动selenium server,默认启动driver目录下的selenium-server-standalone-2.44.0.jar。

自动化测试项目说明文档.docx:介绍当前项目的架构、配置和使用说明。

2.bbs目录

data:该目录用来存放测试相关数据。

report:用于存放HTML测试报告。其下面创建了image目录用于存放测试过程中的截图。

test_case:测试用例目录,用于存放测试用例及相关模块。

3.test_case

models:该目录下存放了一些公共的配置函数及公共类。

page_obj:该目录用于存放测试用例的页面对象(Page Object)。根据自定义规则,以"*Page.py"命名的文件为封装的页面对象文件。

*_sta.py:测试用例文件。根据测试文件匹配规则,以"*_sta.py"命名的文件被当作自动化测试用例执行。

二、编写公共模块

首先定义驱动文件:

...\mztestpro\bbs\test_case\models\driver.py

driver.py

复制代码
# __author__ = 'Ztiny'
# -*-coding:utf-8-*-
 
from selenium.webdriver import Remote
from selenium import webdriver
 
# 启动浏览器驱动
def browser():
    driver = webdriver.Firefox()
    # host = '192.168.0.132:5555' #运行主机 :端口号(默认本机:127.0.0.1:4444)
    # dc = {'browserName':'internet explorer','version':'','platfrom':'WINDOWS','javascriptEnabled':True}
    # # dc = {'browserName':'firefox','version':'','platfrom':'ANY','javascriptEnabled':True,'marionette':False,}#指定浏览器 ('chrome','firefox')
    # driver = Remote(command_executor='http://' + host + '/wd/hub',
    #                 desired_capabilities=dc)
    return driver
 
if __name__ == '__main__':
    dr = browser()
    dr.get("http://www.mayi.com")
    dr.quit()

定义浏览器驱动函数browser(),该函数可以进行配置,根据我们的需要,配置测试用例在不同的主机及浏览器下运行。

自定义测试框架类:

...\mztestpro\bbs\test_case\models\myunit.py

myunit.py

复制代码
# __author__ = 'Ztiny'
#-*-coding:utf-8-*-
from selenium import webdriver
from driver import browser
import unittest
 
class MyTest(unittest.TestCase):
 
    def setUp(self):
        self.driver = browser()
        self.driver.implicitly_wait(10)
        self.driver.maximize_window()
 
    def tearDown(self):
        self.driver.quit()
 
if __name__ == '__main__':
    unittest.main()

定义MyTest()类用于集成unittest.TestCase类,因为笔者创建的所有测试类中setUp()与tearDown()方法所做的事情相同,所以,将他们抽象为MyTest()类,好处就是在编写测试用例时不再考虑这两个方法的实现。

定义截图函数:

...\mztestpro\bbs\test_case\models\function.py

function.py

复制代码
# __author__ = 'Ztiny'
#-*-coding:utf-8-*-
 
from selenium import webdriver
import os
 
#截图函数
def insert_img(driver, file_name):
    base_dir = os.path.dirname(os.path.dirname(__file__))
    base_dir = str(base_dir)
    base_dir = base_dir.replace('\\','/')
    base = base_dir.split('test_case')[0]
    file_path = base + "report/image/" + file_name
    driver.get_screenshot_as_file(file_path)
 
if __name__ == '__main__':
    driver = webdriver.Ie()
    driver.get("http://www.baidu.com")
    insert_img(driver,'baidu.jpg')
    driver.quit()

创建截图函数insert_img(),为了保持自动化项目的移植性,采用相对路径的方式将测试截图保持到.\report\image目录中。

三、编写Page Object

首先创建基础Page基础类(百度主页为例):

...\mztestpro\bbs\test_case\page_obj\base.py

base.py

复制代码
# __author__ = 'Ztiny'
#-*-coding:utf-8-*-
 
class Page(object):
    '''
    页面基础类,用于所有页面的继承
    '''
 
    baidu_url = 'https://www.baidu.com'
 
    def __init__(self,selenium_driver,base_url = baidu_url,parent =None):
        self.base_url = base_url
        self.driver = selenium_driver
        self.timeout = 30
        self.parent = parent
 
    def _open(self,url):
        url = self.base_url + url
        self.driver.get(url)
        assert self.on_page(),'Did not land on %s' % url
 
    def find_element(self,*loc):
        return self.driver.find_element(*loc)
 
    def find_elements(self,*loc):
        return self.driver.find_elements(*loc)
 
    def open(self):
        self._open(self.url)
 
    def on_page(self):
        return (self.driver.current_url).encode('utf-8') == (self.base_url + self.url)
 
    def script(self,src):
        return self.driver.execute_script(src)

创建页面基础类,通过__init__()方法初始化参数:浏览器驱动、URL地址、超时时长等。定义基本方法:open()用于打开BBS地址:find_element()和find_elements()分别用来定位单个与多个元素;创建script()方法可以更简便地调用JavaScript代码。当然还可以对更多的WebDriver方法进行重定义。

创建BBS登录对象类:

...\mztestpro\bbs\test_case\page_obj\loginPage.py

loginPage.py

复制代码
# __author__ = 'Ztiny'
# -*-coding:utf-8-*-
 
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from base import Page
from time import sleep
 
class login(Page):
    '''
    用户登录界面
    '''
 
    url = '/'
 
    #Action
    baidu_login_user_loc = (By.LINK_TEXT,u'登录')
 
    #弹出登录窗口
    def baidu_login(self):
        self.find_element(*self.baidu_login_user_loc).click()
 
    login_username_loc = (By.ID,'TANGRAM__PSP_8__userName')
    login_password_loc = (By.ID,'TANGRAM__PSP_8__password')
    login_button_loc = (By.ID,'TANGRAM__PSP_8__submit')
 
    #登录用户名
    def login_username(self, username):
        self.find_element(*self.login_username_loc).clear()
        self.find_element(*self.login_username_loc).send_keys(username)
 
    #登录密码
    def login_password(self, password):
        self.find_element(*self.login_password_loc).clear()
        self.find_element(*self.login_password_loc).send_keys(password)
 
    #登录按钮
    def login_button(self):
        self.find_element(*self.login_button_loc).click()
 
    #统一登录入口
    def user_login(self, username="**********@qq.com", password="*********"):
        '''获取用户名和面登录'''
        self.open()
        self.baidu_login()
        self.login_username(username)
        self.login_password(password)
        self.login_button()
        sleep(2)
 
    user_error_hint_loc = (By.LINK_TEXT,u"账号不能为空")
    pawd_error_hint_loc = (By.LINK_TEXT,u"密码不能为空")
    user_login_success_loc = (By.LINK_TEXT,u'Ztiny')
 
    #用户名错误提示
    def user_error_hint(self):
        return self.find_element(*self.user_error_hint_loc).text
 
    #密码错误提示
    def pawd_error_hint(self):
        return self.find_element(*self.pawd_error_hint_loc).text
 
    #登录成功用户名
    def user_login_success(self):
        return self.find_element(*self.user_login_success_loc).text

创建登录页面对象,对用户登录页面上的用户名/密码输入框、登录按钮和提示信息等元素的定位进行封装。除此之外,还创建user_login()方法作为系统统一登录的入口。关于对操作步骤的封装可以放在Page Object当中,也可以放在测试用例当中,这个主要根据具体的需求来衡量。这里之所以要放在Page Object当中,主要考虑到还会有其他的测试用例调用到该登录方法。为username 和 password 入参数设置了默认值是为了方便其他用例在调用user_login()时不用再传递登录用户信息,因为该系统大多用例的执行使用该账号即可,同时也方便了在账号失效时的修改。

四、编写测试用例

现在开始编写测试用程序,因为前面已经做好了基础工作,此时测试用例的编写将会简单的许多,更能集中精力考虑用例的设计和事项。

创建BBS登录类:

...\mztestpro\bbs\test_case\login_sta.py

此处需要注意文件名的创建。例如,假设登录页的对象命名为loginPage.py,那么关于测试登录的用例文件应该命名为login_sta.py,这样方便后期用例报错时问题跟踪。尽量把一个页面上的元素定位封装到一个"*Page.py"文件中,把针对这个页面的测试用例集中到一个"*_sta.py"文件中

login_sta.py

复制代码
# __author__ = 'Ztiny'
#-*-coding:utf-8-*-
 
from time import sleep
import unittest, random ,sys
sys.path.append("./models")
sys.path.append("./page_obj")
from models import myunit, function
from page_obj.loginPage import login
 
class loginTest(myunit.MyTest):
    '''测试用户登录'''
 
    def user_login_verify(self, username='',password=''):
        login(self.driver).user_login(username,password)
 
    def test_login1(self):
        '''用户名、密码为空登录'''
        self.user_login_verify()
        po = login(self.driver)
        self.assertEqual(po.user_error_hint(),"账号不能为空")
        self.assertEqual(po.pawd_error_hint()."密码不能为空")
        function.insert_img(self.driver,"user_pawd_empty.jpg")
 
    def test_login2(self):
        '''用户名正确,密码为空登录'''
        self.user_login_verify(username="*******")
        po = login(self.driver)
        self.assertEqual(po.pawd_error_hint(),"密码不能为空")
        function.insert_img(self.driver,"paqd_empty.jpg")
 
    def test_login3(self):
        '''用户名为空,密码正确'''
        self.user_login_verify(password="*******")
        po = login(self.driver)
        self.assertEqual(po.user_error_hint(),"账号不能为空")
        function.insert_img(self.driver,"user_empty.jpg")
 
    def test_login4(self):
        '''用户名与密码不匹配'''
        character = random.choice('abcdefghijklmnopqrstuvwxyz')
        username = "zhangsan" + character
        self.user_login_verify(username=username,password="123456")
        po = login(self.driver)
        self.assertEqual(po.pawd_error_hint(),"密码与账号不匹配")
        function.insert_img(self.driver,"user_pwad_error.jpg")
 
    def test_login5(self):
        '''用户名、密码正确'''
        self.user_login_verify(username='********@qq.com',password='********')
        sleep(2)
        po = login(self.driver)
        self.assertEqual(po.user_login_success(), u'Ztiny')
        function.insert_img(self.driver ,"user_pwd_ture.jpg")
 
if __name__ == '__main__':
    unittest.main()

首先创建loginTest()类,继承myunit.Mytest()类,关于Mytest()类的实现,请翻看前面代码。这样就省去了在每一个测试类中实现一遍setUp()和tearDown()方法。

创建user_login_verify()方法,并调用loginPage.py中定义的user_login()方法。为什么不直接调用呢?因为user_login()的入参已经设置了默认值,原因前面已经解释,这里需要重新将其入参的默认值设置为空即可。

前三条测试用例很好理解,分别验证:

用户名密码为空,点击登录

用户名正确,密码为空,点击登录

用户名为空,密码正确,点击登录

第四条用例验证错误用户名和密码登录。在当前系统中如果反复使用固定错误的用户名和密码,系统会弹出验证码输入框。为了避免这种情况的发生,就需要用户名进行随机变化,此处的做法用固定前缀"zhangsan",末尾字符从a~z中随机一个字符与前缀进行拼接。

第五条用例验证正确的用户名和密码登录,通过获取用户名作为断言信息

在上面的测试用例中,每条测试用例结束时都调用function.py文件中的insert_img函数进行截图。当用例运行完成后,打开...\report\image\目录将会看到用例执行的截图文件。

五、执行测试用例

为了在测试用例运行过程中不影响做其他事,笔者选择调用远程主机或虚拟机来运行测试用例,那么这里就需要使用Selenium Grid(其包含Selenium Server)来调用远程节点。

创建...\mztestpro\startup.bat文件,用于启动...\mztestpro\driver\目录下的Selenium Server。

startup.bat

复制代码
@echo off
echo 启动hub
java -jar .\mztestpro\driver\selenium-server-standalone-2.40.0.jar -role hub -host 192.168.0.102 -port 4444
echo 启动node
java -jar .\mztestpro\driver\selenium-server-standalone-2.40.0.jar -role node -port 5555 -hub  http://192.168.0.102:4444/grid/register

双击startup.bat文件,启动Selenium Server创建主hub节点。在远程主机或虚拟机中通样需要启动Selenium Server创建node节点。

创建用例执行程序:...\mztestpro\run_bbs_test.py

run_bbs_test.py

复制代码
# __author__ = 'Ztiny'
#-*-coding:utf-8-*-
 
from HTMLTestRunner import HTMLTestRunner
from email.mime.text import MIMEText
import smtplib
import unittest
import time
import os
# =========================邮件接收者============================
mailto_list=["********@qq.com"]
#============= 设置服务器,用户名、口令以及邮箱的后缀===============
mail_host="smtp.163.com"
mail_user="******@163.com"
mail_pass="*******"
#===========================发送邮件============================
def send_mail(to_list,file_new):
    '''''
    to_list:发给谁
    sub:主题
    content:内容
    send_mail("aaa@126.com","sub","content")
    '''
    f = open(file_new, 'rb')
    mail_body = f.read()
    f.close()
    me=mail_user
    msg = MIMEText(mail_body,'html','utf-8')
    msg['Subject'] = u'自动化测试报告'
    msg['From'] = me
    msg['To'] = ";".join(to_list)
    try:
        s = smtplib.SMTP()
        s.connect(mail_host,25)
        s.login(mail_user,mail_pass)
        s.sendmail(me, to_list, msg.as_string())
        s.close()
        return True
    except Exception, e:
        print str(e)
        return False
 
#==============查找测试报告目录,找到最新生成的测试报告文件==========
def new_report(testreport):
    lists = os.listdir(testreport)
    lists.sort(key=lambda fn:os.path.getatime(testreport + "\\" + fn))
    file_new = os.path.join(testreport,lists[-1])
    print(file_new)
    return file_new
 
if __name__ == '__main__':
    now = time.strftime("%Y-%m-%d %H_%M_%S")
    filename = './bbs/report/' + now +'result.html'
    fp = open(filename,'wb')
    runner = HTMLTestRunner(stream=fp,
                            title=u'百度登录自动化测试报告',
                            description=u'环境 :window 7 浏览器:firefox')
    discover = unittest.defaultTestLoader.discover('./bbs/test_case',
                                                   pattern='*_sta.py')
    runner.run(discover)
    fp.close()
    file_path = new_report('./bbs/report/')
 
    if send_mail(mailto_list,file_path):
        print u"发送成功"
    else:
        print u"发送失败"

执行过程中并没有任何改动,集成HTMLTestRunner生成的HTML测试报告,以及集成自动发邮件功能等。唯一需要注意的是,脚本中的路径建议使用相对路径,以便于项目被移动到任意目录下执行。

打开...\mztestpro\driver.py 文件,修改脚本运行的节点及浏览器。现在可以通过运行run_bbs_test.py来执行测试项目了。

小结:

如果你完成了前面的操作,那么这只是自动化项目的开始,不过,我们已经把基本架构设计完成,后面大部分工作就是编写各个页面的*Page.py以及测试用*_sta.py。在这个过程中会遇到各种各样的问题,如元素定位、架构的扩展,需要读者自己去克服这些问题。

ps:对自动化感兴趣的同学可以加下方小卡片QQ群,和大佬一起学习交流吧↓

相关推荐
databook2 小时前
Manim实现闪光轨迹特效
后端·python·动效
Juchecar3 小时前
解惑:NumPy 中 ndarray.ndim 到底是什么?
python
用户8356290780513 小时前
Python 删除 Excel 工作表中的空白行列
后端·python
Json_3 小时前
使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
后端·python·fastapi
数据智能老司机10 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机11 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机11 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机11 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i11 小时前
drf初步梳理
python·django
每日AI新事件11 小时前
python的异步函数
python