从零开始搭建一个简单的ui自动化测试框架03(pytest+selenium+allure)

简介: 三、填充我们的框架设计主类我们首先来实现我们的测试用例的主类设计,这个类主要用以被其他的测试用例继承,来实现一些每个测试用例都会做的事情,具体一点就是:继承unittest,创建一个webdriver的实例,以及每次运行用例时打开和关闭浏览器。

三、填充我们的框架

设计主类

我们首先来实现我们的测试用例的主类设计,这个类主要用以被其他的测试用例继承,来实现一些每个测试用例都会做的事情,具体一点就是:
继承unittest,创建一个webdriver的实例,以及每次运行用例时打开和关闭浏览器。

可能之后还有更多这样的共性的事情会被放到测试主类,到时候我们就继续在测试主类里添加。

我们在之前预留的位置maincase里新建一个py文件,在里面写这个主类MainCase。
(稍微的介绍一下unittest框架,unittest会在正式的测试用例开始之前执行setup方法,在执行用例结束后会执行teardown方法,测试用例是指以test开头的方法,例如test_something这样的名字)

# -*- coding: utf-8 -*-
from selenium import webdriver
from operate.baseoperate import BaseOperate

import unittest

# 主测试类继承自测试框架unittest
class MainCase(unittest.TestCase):
    # 声明一个webdriver
    driver = webdriver
    # 声明一个基础操作类base_operate
    base_operate = BaseOperate

    def setUp(self):
        # 测试之前,启动浏览器,打开一个设定好的网址
        self.driver = webdriver.Chrome()
        self.driver.get("http://open.qq.com")
        # 最大化窗口
        self.driver.maximize_window()
        # 传入webdriver,实例化这个类
        self.base_operate = BaseOperate(self.driver)

    def tearDown(self):
        # 测试完毕的时候关闭浏览器
        self.driver.quit()


if __name__ == '__main__':
    unittest.main()

这样我们的主类就设计好了,之后我们写正式的testcase的时候,只需要继承这个类就可以了。

设计基础操作类和config文件

为了让正式的用例写的更加的爽,我们还需要封装一些基础操作方便调用,这个类放到我们预留的位置operate里,名字叫做BaseOperate。

为了让这个类可以随时调用,我们在前面提到的测试主类里,把webdriver的实例给他,具体的做法是,为BaseOperate这个类设计一个构造函数,在MainCase里传入其webdriver实例。

BaseOperate这个类的代码如下:

# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException

class BaseOperate():
    # 声明一个webdriver类
    operate_driver = webdriver.Chrome

    def __init__(self, driver):
        # 构造函数里将driver传入
        self.operate_driver = driver

MainCase里修改一下,现在是这样:

# -*- coding: utf-8 -*-
from selenium import webdriver
from operate.baseoperate import BaseOperate
import unittest

# 主测试类继承自测试框架unittest
class MainCase(unittest.TestCase):
    # 实例化一个webdriver
    driver = webdriver.Chrome()
    # 声明一个基础操作类base_operate
    base_operate = BaseOperate

    def setUp(self):
        # 测试之前,启动浏览器,打开一个设定好的网址
        self.driver.get("http://open.qq.com")
        # 传入webdriver,实例化这个类
        self.base_operate = BaseOperate(self.driver)

    def tearDown(self):
        # 测试完毕的时候关闭浏览器
        self.driver.close()


if __name__ == '__main__':
    unittest.main()

ok,接下来我们的用例只需要继承自主测试类,就可以直接用base_operate里封装的代码了(后续会上这一块的代码),现在我们要做的是把这个基础操作类填充一下。

想一下,我们要封装什么基础操作才会更加方便我们用例的书写?这个问题其实没有标准答案的,这里附上一些常用的做法。

代码如下:

# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from selenium.common.exceptions import NoSuchElementException
from config.sysconfig import *

import sys
import time

class BaseOperate():
    # 声明一个webdriver类
    operate_driver = webdriver.Chrome

    def __init__(self, driver):
        # 构造函数里将driver传入
        self.operate_driver = driver

    def find_element(self, *loc):
        """
        重封装的find方法,接受元祖类型的参数,默认等待元素5秒,寻找失败时自动截图
        :param loc:元组类型,必须是(By.NAME, 'username')这样的结构
        :return:元素对象webelement
        """
        try:
            webelement = WebDriverWait(self.operate_driver, 5).until(lambda x: x.find_element(*loc))
            return webelement
        except (TimeoutException, NoSuchElementException) as e:
            #寻找失败时自动截图至指定目录sreenshot,截图名称为调用方法名(测试用例名)+ 时间戳 + png后缀
            self.operate_driver.get_screenshot_as_file(SCREENSHOTURL + sys._getframe(1).f_code.co_name + time.strftime(ISOTIMEFORMAT, time.localtime(time.time()))+".png")

    def click_element(self, *loc):
        """
        重封装的click方法,将寻找和点击封装到一起,适用于点击次数不多的元素
        :param loc:元组类型,必须是(By.NAME, 'username')这样的结构
        :return:None
        """
        try:
            webelement = self.find_element(*loc)
            webelement.click()
        except (TimeoutException, NoSuchElementException) as e:
            print ('Error details :%s' % (e.msg))

    def is_page_has_text(self, text):
        """
        判断当前页面是否存在指定的文字
        :param text:字符串类型,要判断是否存在的文字
        :return:布尔值,True代表存在,False代表不存在
        """
        nowtime = time.time()
        while self.operate_driver.page_source.find(text) < 0:
            time.sleep(2)
            if time.time() - nowtime >= 30000:
                return False
        return True

    def switch_to_last_handles(self):
        """
        在打开的窗口里选择最后一个
        :return:None
        """
        all_handles = self.operate_driver.window_handles
        self.operate_driver.switch_to_window(all_handles[-1])

    def switch_to_another_hanles(self, now_handle):
        """
        只适用于打开两个窗口的情况,传入现在的窗口句柄后,选择另一个窗口
        :param now_handle:现在的窗口句柄
        :return:
        """
        # 得到当前开启的所有窗口的句柄
        all_handles = self.operate_driver.window_handles 
        # 获取到与当前窗口不一样的窗口
        for handle in all_handles:
            if handle != now_handle:  
                self.operate_driver.switch_to_window(handle)

    def clear_and_sendkeys(self, sendtexts, *loc):
        """
        先清除当前文本框内的文字再输入新的文字的方法
        :param sendtexts:要输入的新的文字
        :param loc:元组类型,必须是(By.NAME, 'username')这样的结构
        :return:None
        """
        try:
            webelement = self.find_element(*loc)
            webelement.clear()
            webelement.send_keys(sendtexts)
        except (TimeoutException, NoSuchElementException) as e:
            print ('Error details :%s' % (e.msg))

这里重点说一下find的封装思路,之前说过,我们希望元素的位置和主代码分离,因此这里就用了selenium的find方法的另一种形式,

find_element (by ='id',value = None )

这样的结构,我们把元素按照ELEMENT = {'登录按钮': (By.ID, 'loginSub')},这样的结构存储在我们预定的config位置里,文件名就叫他emtconfig,之后元素的增加修改删除都在这里维护,当需要用到该元素的时候,就引入这个文件,例如这段代码:

# -*- coding: utf-8 -*-
from maincase.maincase import MainCase
from config.emtconfig import ELEMENT

class Test(MainCase):
    def test_1(self):
        qmarket_loginbtn = self.base_operate.find_element(*ELEMENT['QQ市场首页登录按钮'])

用于参考的emtconfig的内容:

# -*- coding: utf-8 -*-
from selenium.webdriver.common.by import By

# 页面元素
ELEMENT = {
    #首页元素
    'QQ市场首页登录按钮': (By.XPATH, "//*[@id='jmod_topbar']//*/a[text()='登录']"),
    '选择使用账号登录按钮': (By.XPATH, "//*[@id='switcher_plogin']"),
    
    #登录界面元素
    'QQ号文本框': (By.ID, "u"),
    'QQ密码文本框': (By.ID, "p"),
    'QQ登录按钮': (By.ID, "login_button"),
    
    #管理中心元素
    '首页管理中心按钮': (By.XPATH, "//*[@id='jmod_topbar']//*/a[text()='管理中心']"),
    '应用管理中心更新安装包按钮': (By.XPATH, "/html/body//*/a[text()='更新安装包']"),
    
    #应用详情页元素
    '点击上传按钮': (By.XPATH, "//*[@id='j-apk-box']//*/p[text()='更新安装包']"),
    '应用更新说明文本框': (By.XPATH, "//*[@id='j-apk-box']//*/textarea"),
    '提交审核按钮': (By.ID, "j-submit-btn"),
    '确认提交审核': (By.ID, "j-confirm-yes"),
}

为了知道为什么元素不能被寻找到,在用例执行的时候到底发生了什么,所以我们希望当寻找失败时可以有个截图方便回溯当时的环境状况,因此我们用了py的异常机制,在寻找失败时,执行except下的方法,也就是截图方法,我在config的位置新建了一个sysconfig的文件,里面用于存放一些系统的信息,例如截图的位置或者全局样式等等,我们的截图的方法就用了这个位置。

sysconfig现在的内容:

# -*- coding: utf-8 -*-

# 截图路保存径,绝对路径,也可以用相对路径
SCREENSHOTURL = 'C:/Users/zyj/PycharmProjects/uitest/sreenshot/'

# 时间样式
ISOTIMEFORMAT = '%Y%m%d%H%M%S'

设计逻辑操作类

这个是用于封装一些常用的逻辑操作或者跳转路径的操作的类,预留的位置operate里,名字叫做BusinessOperate。因为是和业务强相关,所以就不举例了,大约的实现方法是:

# -*- coding: utf-8 -*-
from operate.baseoperate import BaseOperate

class BusinessOperate(BaseOperate):
    pass

因为没有实际方法,所以直接pass了,诸位真正用的时候可以自己根据业务需要补充代码。

设计工具类

这个类主要是非selenium的内容,但是用例也会用到的操作,例如打开windows窗口选择文件上传,发送email等,位置位于util里,这里上前文提到的两个实例。
封装打开win窗口的操作,类名为W32Operate:

# -*- coding: utf-8 -*-

import win32gui
import win32con
import sys #要重新载入sys。因为 Python 初始化后会删除 sys.setdefaultencoding 这个方法

reload(sys)
sys.setdefaultencoding('utf-8')
class W32Operate:

   def win_upload(self, filepath):
       """
       上传操作时,打开win的上传弹框,选取指定的文件
       :param filepath: 上传的文件的路径
       :return: None
       """
       dialog = win32gui.FindWindow('#32770', u'打开')  # 对话框
       print (dialog)
       #win32gui.SetForegroundWindow(dialog)
       ComboBoxEx32 = win32gui.FindWindowEx(dialog, 0, 'ComboBoxEx32', None)
       ComboBox = win32gui.FindWindowEx(ComboBoxEx32, 0, 'ComboBox', None)
       Edit = win32gui.FindWindowEx(ComboBox, 0, 'Edit', None)  # 上面三句依次寻找对象,直到找到输入框Edit对象的句柄
       button = win32gui.FindWindowEx(dialog, 0, 'Button', u'打开(&O)')  # 确定按钮Button
       win32gui.SendMessage(Edit, win32con.WM_SETTEXT, None, filepath)  # 往输入框输入绝对地址
       win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button)  # 按button

封装email的操作,类名为EmailOperate(smtp的信息写在了sysconfig里,另外,如果你是采用我之后提到的pytest+allure+jenkins的方法的话,邮件的发送无需在框架内实现,这里写一下仅供参考):

# -*- coding: utf-8 -*-
from email.mime.text import MIMEText
from email.header import Header
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email import encoders
from config.sysconfig import *

import smtplib

class EmailOperate:

    def send_mail_with_file(self, filepath, filename, subject):
        """
        发送一个带有附件(测试报告)的邮件,邮件的配置项来自sysconfig
        :param filepath: 报告文件的路径
        :param filename: 报告文件的名称
        :param subject: 邮件标题
        :return: None
        """
        # 邮件对象:
        msg = MIMEMultipart()
        msg['From'] = "UIAutoTester"
        msg['To'] = "you"
        msg['Subject'] = Header(subject, 'utf-8').encode()

        # 邮件正文是MIMEText:
        msg.attach(MIMEText('请查收自动化测试结果,见附件', 'plain', 'utf-8'))

        # 添加附件:
        with open(filepath, 'rb') as f:
            # 设置附件的MIME和文件名:
            mime = MIMEApplication(open(filepath, 'rb').read())
            # 加上必要的头信息:
            mime.add_header('Content-Disposition', 'attachment', filename=filename)
            mime.add_header('Content-ID', '<0>')
            mime.add_header('X-Attachment-Id', '0')
            # 把附件的内容读进来:
            mime.set_payload(f.read())
            # 用Base64编码:
            encoders.encode_base64(mime)
            # 添加到MIMEMultipart:
            msg.attach(mime)

        server = smtplib.SMTP(smtp_server, 587)
        server.starttls()
        server.set_debuglevel(1)
        server.login(from_addr, password)
        server.sendmail(from_addr, [to_addr], msg.as_string())
        server.quit()

用例的组织和管理

我们之前预留了testsuite的位置,现在我们可以在这个位置新建我们自定义的测试用例集,使用unittest框架的话,用例的组织可以用三种方法,addTest(添加具体测试用例至用例集),makeSuite(加载某个类下的所有测试用例至用例集),以及discover(加载某个路径下的所有类里的所有测试用例至用例集),三种方法的示范代码如下:
addTest:

# coding = utf-8  
import unittest  

suite = unittest.TestSuite()  
suite.addTest(你的测试用例类名('该类名下的测试用例方法名'))  
  
if __name__=='__main__':  
    #执行用例  
    runner=unittest.TextTestRunner()  
    runner.run(suite)  

makeSuite:

# coding = utf-8  
import unittest  
  
suite = unittest.TestSuite(unittest.makeSuite(你的测试用例类名))  
  
if __name__=='__main__':  
    #执行用例  
    runner=unittest.TextTestRunner()  
    runner.run(suite)  

discover:

# coding = utf-8  
import unittest  
  
suite = unittest.TestLoader().discover("你的测试用例类的路径")  
  
  
if __name__=='__main__':  
    #执行用例  
    runner=unittest.TextTestRunner()  
    runner.run(suite)  
生成测试报告

传统的和unittest框架配套的报告模块是HTMLTestRunner,使用方法也很简单,首先是下载,把下载的文件放到...\Python27\Lib目录下,然后用的时候import一下就行。
我们可以把代码写到testsuite文件里的最下面,报告的位置就用我们预留的testreport位置,例如:

if __name__=='__main__':
    #执行测试
    filename = config.ReportUrl + time.strftime(config.ISOTIMEFORMAT, time.localtime(time.time())) + 'result.html'
    fp = file(filename, 'wb')

    runner = HTMLTestRunner.HTMLTestRunner(
        stream=fp,
        title='YCG Test Report',
        description='YcgkfsApp All Testcase'
        )

    runner.run(suite)

这样执行完了testsuite之后,就会在testreport目录下生成一个html文件格式的报告,结合之前写的email方法,就可以把报告发出去了,这一段就不举例了,因为后面会介绍一个更加简便的方法去做这件事。
生成的报告界面如下:

img_379372ca085ef835b70905070482d69e.png
image

目录
相关文章
|
9天前
|
Web App开发 编解码 Linux
使用Selenium自动化测试解决报告生成失败问题及Linux部署指南
这篇文章介绍了使用Selenium自动化测试解决报告生成失败问题的方法,包括Linux环境下的部署指南和代码实现。
17 1
使用Selenium自动化测试解决报告生成失败问题及Linux部署指南
|
1天前
|
传感器 物联网 测试技术
未来科技浪潮中的领航者:区块链、物联网与虚拟现实的融合与创新探索自动化测试之美——以Selenium为例
【8月更文挑战第30天】本文深入探讨了当前最前沿的技术趋势——区块链、物联网和虚拟现实,并分析了它们各自的发展脉络及相互之间的融合可能性。我们将通过具体应用场景描绘这些技术如何塑造未来社会的面貌,同时提供代码示例以加深理解。文章旨在为读者揭示这些技术背后的巨大潜力,以及它们将如何影响我们的工作和生活方式。
|
3天前
|
存储 测试技术 数据安全/隐私保护
自动化测试小技巧之Airtest-Selenium和Excel的无缝协作
【8月更文挑战第26天】在自动化测试中,Airtest-Selenium 与 Excel 的无缝协作能显著提升测试效率与可维护性。通过将 Excel 作为数据源,可轻松存储和读取测试用例数据;测试结果可自动记录在 Excel 中,便于跟踪与分析;利用 Excel 管理测试用例,简化了用例的增删改查;此外,还能自动截图并记录日志,方便问题定位。这种方式不仅提高了自动化测试的灵活性,还使得测试过程更加透明与高效。
|
3天前
|
敏捷开发 测试技术 数据安全/隐私保护
自动化测试的高效之路:如何利用Python和Selenium提升测试效率
【8月更文挑战第28天】本文旨在探讨通过Python语言结合Selenium框架来提高软件测试的效率。文章不仅介绍了自动化测试的基本概念,还提供了具体的代码示例,帮助读者理解如何实现自动化测试脚本,并指出了在实施过程中可能遇到的问题及其解决方案。通过本文,读者将学会如何有效地使用Python和Selenium工具,以减少重复性工作,提升测试流程的效率与准确性。
|
4天前
|
IDE Java 测试技术
探索自动化测试框架:以Selenium为例
【8月更文挑战第27天】在软件开发的海洋中,自动化测试是那把能指引船只安全航行的灯塔。本文将带你走进自动化测试的世界,重点介绍如何使用Selenium这一流行的自动化测试工具,来构建强大的测试脚本。我们将一起学习如何安装和配置Selenium,编写基本的测试用例,以及如何处理测试中的等待和异常处理。通过这篇文章,你将能够掌握自动化测试的基本概念和技巧,为你的软件开发之旅增添一份保障。
|
4天前
|
Web App开发 测试技术 持续交付
自动化测试之美:如何利用Selenium和Python提升测试效率
【8月更文挑战第27天】在软件开发的海洋中,自动化测试犹如一艘高速航行的帆船,让质量保证的过程更加迅速而精准。本文将引导你了解如何结合Selenium和Python这两大利器,构建强大的自动化测试框架,从而显著提升测试工作的效率和效果。通过深入浅出的讲解,我们不仅会探索这两个工具的基本使用方法,还会学习如何设计高效的测试用例,以及如何处理测试过程中遇到的各种问题。无论你是测试新手还是希望提高测试技能的开发者,这篇文章都将为你打开一扇通往更高效测试世界的大门。
|
7天前
|
人工智能 Java 测试技术
自动化测试之美:如何用Selenium提升Web应用的质量保证
【8月更文挑战第24天】 在软件开发的海洋里,自动化测试如同一艘救生艇,帮助开发团队保持代码质量的同时,还能确保他们不会淹没在功能的迭代和bug修复中。Selenium,作为一个用于Web应用程序测试的工具,它的强大之处在于模拟真实用户操作的能力。本文将通过浅显易懂的语言和实际代码示例,引导读者理解Selenium的魅力所在,以及如何有效利用它来提升Web应用的测试效率和覆盖率。
|
9天前
|
前端开发 测试技术 UED
【测试效率对比】深入分析:为何UI自动化测试的投资回报率通常低于接口自动化测试?
这篇文章深入分析了UI自动化测试与接口自动化测试的投资回报率(ROI)问题,指出UI自动化测试在某些情况下的ROI并不低,反驳了没有实施过UI自动化就轻易下结论的观点,并强调了实践的重要性和自动化测试在项目迭代中的作用。
20 1
|
9天前
|
XML Java 测试技术
Selenium WebDriver自动化测试(基础篇):不得不掌握的Java基础
关于Selenium WebDriver自动化测试的Java基础篇,涵盖了Java的变量、数据类型、字符串操作、运算符、流程控制、面向对象编程、关键字用法、权限修饰符、异常处理和IO流等基础知识点,为进行自动化测试提供了必要的Java语言基础。
12 1
|
16天前
|
Web App开发 数据采集 测试技术
五分钟轻松掌握 Python 自动化测试 Selenium
本文主要介绍了 Selenium 相关内容,主要涉及 Selenium 知识面,从开始的 Python 小案例,到后面的 API 全面了解,以及 Selenium 的常用功能,到最后的 XPATH 以及爬虫的认知。这些内容已经能够全面,且具有实践性。
下一篇
云函数