首页 > 其他分享 >全栈测试开发----unittest的设计及实现----自动化测试分层思想(1)

全栈测试开发----unittest的设计及实现----自动化测试分层思想(1)

时间:2023-07-12 21:34:57浏览次数:47  
标签:__ return get unittest driver ---- 测试 type self

  通过unittest框架完成自动化分层操作,实现数据分离,减少代码于数据之间的依赖性,完成报告的生成并自动发送一系列操作。

 

前言:

  有人认为,在进行自动化测试过程中,测试代码只需要包含测试逻辑即可。其实不然,他需要包括很多类的代码,如URL拼接、访问UI控件、HTML/XML的解析等,如果将测试逻辑代码于上面这些类型的代码混合在一起,那么代码就显得非常难以理解,也不容易维护,所以需要使用分层结构来解决自动化测试过程中遇到的这些问题。

 

正文:

 

 一、为什么要写框架

   首先需要了解什么 是框架,。框架用于解决或者处理复杂的问题并将其简单化,从个人方面来说,如果在自动化测试过程中融入了框架,可以提高脚本的规范性,这也是面试的加分项,是自身能力的硬性指标。从实际工作而言,一个好的测试框架可以提升项目的稳定性、健壮性。降低维护成本,也非常容易解决问题,如准确定位问题等。

 

 二、自动化技术

   现如今主要的软件自动化测试技术有录制/回放、脚本技术、数据驱动、关键字驱动、业务驱动。

  在学习数据驱动之前,先了解脚本技术的发展,脚本技术最早是基于线性脚本发展的,所谓线性就是一行一行的顺序执行代码。该阶段只适用于让初学者理解selenium相关API接口的操作,该类型脚本在实际工作中你那个没有任何意义。

  线性脚本之后就是结构化脚本。主要是通过使用selenium+API+Python模块封装面向对象(类于对象)的脚本,该类型脚本主要包括两种类型:模块化脚本、库/包脚本,不同的业务场景会涉及不同的模块。

  随着结构化脚本的发展,慢慢的进入数据驱动时代。数据驱动时代实现了将脚本中的数据于代码进行分离的操作,从而减少了其彼此的依赖,脚本的利用率、可维护性大大提高,但是界面变化对其影响仍然非常大。

  基于这种考虑,数据驱动技术将测试逻辑按照关键字进行分解,形成数据文件,关键字对应封装的业务逻辑,即关键字驱动,这也是数据驱动测试的一种改进。目前大多数测试工具技术处于数据驱动技术到关键字驱动技术之间。

  最后就是根据关键字驱动完成不同类型的业务驱动,业务驱动可以分为接入层业务驱动、业务层业务驱动、数据层业务驱动和性能驱动等。

发展:线性脚本——>结构化脚本——>数据驱动——>关键字驱动

 

三、如何分层封装

  根据不同的设计者、公司定义规则,参考主流分层的架构模式等所得到的分层模型会略有不同,但是整体的核心分层思想不变。

  一般将分层层次定义为4~6层,可以根据实际情况进行封装设计。

    1、页面元素处理层:即Page Object(PO模式)表页面对象管理,将每个页面上的所有元素定义在一个模块中,即使后期要对前段页面进行修改,其脚本的维护也十分方便。

    2、业务流操作层:表示基于页面元素处理层实现业务流的自由组织,实际对应自动化测试的业务流场景的执行测试用例

    3、测试用例层:根据业务流场景设计相应的测试用例并执行,用例的执行都是通过框架完成(unittest、pytest),并且可以很好的自由组织测试用例的执行并分析结果。

    4、数据分离层:将脚本中的所有数据提取出来进行专门的数据模块管理,后期直接修改相应的数据即可,不需要进行底层代码的查看分析。

    5、公共层:进行常量数据的存储、报告的生成、日志的保存、邮件的发送等

    6、主程序入口应用层:执行只需要设定一个入口,最后整体框架只需要执行主程序入口模块、修改数据分离层中的数据、新增测试用例层的用例即可,其他底层进行封装。

 

四、驱动器对象示例封装

  首先需要封装驱动器对象的获取方法,可以将驱动器对象中的浏览器进行基本设置,如驱动器文件、浏览器下载默认地址、传入不同浏览器参数完成不同浏览器驱动器对象创建等。再着就是元素定位的方式,前面总结的定位方式有三十多种,其中常规八种、复数八种、By形式八种、JS方式六种、jQuery,还有XPath模糊定位、二次定位等,这些都可以直接封装到方法中

 

# -----------------------------------
'''
@Author:C_N_Candy
@Date  :2023/7/9 17:07
@File  :GET_PATH.py
@Desc  :获取当前工作目录路径
'''
# ------------------------------------
import os


def GETPATH():
    path = os.getcwd()  # 获取当前工作目录
    # print(path)
    return path


if __name__ == '__main__':
    GETPATH()
# -----------------------------------
'''
@Author:C_N_Candy
@Date  :2023/7/9 18:01
@File  :Base_Page.py
@Desc  :从所有需要获取驱动器对象的页面抽取出来的基类
'''
# ------------------------------------
from selenium import webdriver
import os
import subprocess
import sys
from selenium.common.exceptions import WebDriverException
from CRMProject.GET_PATH import GETPATH
from selenium.webdriver.common.by import By

# 设定驱动器版本参数,当前自带的驱动器Chrome:70以上当前的火狐:80以上
broser_version = {'Chrome': 70, 'FireFox': 80, 'Edge': 18}


# 此模块相当于封装底层所有相关内容:驱动器对象、元素定位方式
class BasePage(object):
    def __init__(self, url, browsertype="Chrome"):
        """

        :param url: 需要访问的URL
        :param browsertype: 传入浏览器类型实现对应的浏览器驱动器对象创建:忽略大小写;默认使用谷歌浏览器;其值可以是Firefox、IE、Edge
        """
        __browsertype = browsertype.lower()  # 忽略大小写,该变量不能被外界直接访问,将其设置为私有变量
        self.download_path = GETPATH() + "Page_Object\Base\DownLoad"  # 所有资源的下载储存目录
        __driverdir = GETPATH() + "Page_Object\Base\DriverDir"  # 驱动器地址只需要当前模块引用,其他模块不需要引用
        try:
            if __browsertype == "FireFox":
                fireoptions = webdriver.FirefoxProfile()
                # 设定下载方式,2表示保存到指定的文件夹路径
                fireoptions.set_preference("broser.download.folderList", 2)
                # 设定文件夹路径
                fireoptions.set_preference("broser.download.dir", self.download_path)
                """
                去除询问窗口,定义下载文件的文本类型:content-type:text/html text/css text/json
                """
                fireoptions.set_preference("broser.helperApps.neverAsk.saveToDisk", "application/octet-stream")
                self.driver = webdriver.Firefox(firefox_profile=fireoptions,
                                                executable_path=__driverdir + "\geckodriver.exe")

            elif __browsertype == "Chrome":
                options = webdriver.ChromeOptions()
                # options的值都以键值对的形式存在:设置次数最多的就是修改下载的默认路径
                dict1 = {"download.default_directory": self.download_path}
                # 将设定的操作添加到谷歌浏览器中:prefs是谷歌浏览器存在一个选项的关键字名称,不能随意定义
                options.add_experimental_option("prefs", dict1)
                self.driver = webdriver.Chrome(options=options, executable_path=__driverdir + "\chromedriver.exe")

            elif __browsertype == "Ie":
                self.driver = webdriver.Ie()

            elif __browsertype == "Edge":
                # 确定Edge的驱动已经安装的话,
                # os.system("DISM.exe /Online /Add-Capability /CapabilityName:Microsoft.WebDriver~~~~0.0.1.0")
                # self.driver = webdriver.Edge()
                self.__check_edge_driver()
                # 以上都错误的话,则可以抛出驱动器异常,或者print输出错误语句,实际上后期优化,写入日志中
            else:
                print("传入的浏览器类型参数错误")

            self.driver.get(url)
            self.driver.maximize_window()
            self.driver.implicitly_wait(30)

        except:
            print("%s驱动器与当前浏览器不匹配!" % __browsertype)

    # 声明一个方法判定Edge的驱动器是否下载
    def __install_edge_driver(self, username="administrator", password="123456"):
        sub2 = subprocess.run(
            'Isrunase/user:%s/password:%s/domain:/command:"DISM.exe /Online /Add-Capability /CapabilityName:Microsoft.WebDriver~~~~0.0.1.0"/runpath:c:\\' % (
                username, password), capture_output=True)
        # 返回的是输出结果。如果命令执行成功是没有结果输出的,返回码是0
        return (sub2.stdout, sub2.returncode)

    # 实现Edge驱动器是否安装的判定
    def __check_edge_driver(self):
        try:
            self.driver = webdriver.Edge()
        except WebDriverException:
            get_result = self.__install_edge_driver()
            if len(get_result[0]) == 0 and get_result[1] == 0:
                self.driver = webdriver.Edge()
            else:
                print("当前驱动器版本与浏览器版本不一致")
        except Exception:
            print(sys.exc_info())

    @staticmethod
    def get_element1(driver, type, value, js_type='id'):  # 默认id;忽略大小写
        __get_type = type.lower()
        if __get_type == 'id':
            return driver.find_element(By.ID, value)

    # 封装所有定位方式的方法
    def get_element(self, type, value, js_type="id"):
        """

        :param type: 其值可以是id,class,name,tag,link,partial,XPath,CSS,JS,jQuery;如果不是指定类型的参数,则会抛出异常
        :param value:
        :param js_type:
        :return:
        """
        try:
            __get_type = type.lower()
            if __get_type == "id":
                return self.driver.find_element(By.ID, value)
            elif __get_type == "class":
                return self.driver.find_element(By.CLASS_NAME, value)
            elif __get_type == "name":
                return self.driver.find_element(By.NAME, value)
            elif __get_type == "tag":
                return self.driver.find_element(By.TAG_NAME, value)
            elif __get_type == "link":
                return self.driver.find_element(By.LINK_TEXT, value)
            elif __get_type == "XPath":
                return self.driver.find_element(By.XPATH, value)
            elif __get_type == "CSS":
                return self.driver.find_element(By.CSS_SELECTOR, value)
            elif __get_type == "partial":
                return self.driver.find_element(By.PARTIAL_LINK_TEXT, value)
            elif __get_type == "JS":
                return self.js_element(value, js_type)
            elif __get_type == "jQuery":
                pass
            else:
                raise ValueError
        except:
            print(sys.exc_info())

    # JS定位封装
    def js_element(self,value, js_type='id'):
        __js_type=js_type.lower()

        if __js_type=="id":
            return self.driver.execute_script("return document.getElementById('%s')"%value)
        # 如果是name,tag,css返回多个元素对象,默认操作第一个
        elif __js_type=="name":
            return self.driver.execute_script("return document.getElementByName('%s')"%value)[0]
        elif __js_type=="tag":
            return self.driver.execute_script("return document.getElementByTagName('%s')"%value)[0]
        elif __js_type=="CSS":
            return self.driver.execute_script("return document.querySelectorAll('%s')"%value)[0]
        elif __js_type=="class":
            return self.driver.execute_script("return document.getElementByClassName('%s')"%value)
        else:
            raise ValueError


    def get_elements(self,type,value):
        """

        :param type: 其值可以是id,class,name,tag,link,partial,XPath,CSS,JS,jQuery
        :param value:
        :return:
        """
        __get_type = type.lower()
        if __get_type == "id":
            return self.driver.find_elements(By.ID, value)
        elif __get_type == "class":
            return self.driver.find_elements(By.CLASS_NAME, value)
        elif __get_type == "name":
            return self.driver.find_elements(By.NAME, value)
        elif __get_type == "tag":
            return self.driver.find_elements(By.TAG_NAME, value)
        elif __get_type == "link":
            return self.driver.find_elements(By.LINK_TEXT, value)
        elif __get_type == "XPath":
            return self.driver.find_elements(By.XPATH, value)
        elif __get_type == "CSS":
            return self.driver.find_elements(By.CSS_SELECTOR, value)
        elif __get_type == "partial":
            return self.driver.find_elements(By.PARTIAL_LINK_TEXT, value)
        elif __get_type == "jQuery":
            pass
        else:
            raise ValueError

 

 对驱动器对象、各种定位方式封装。源码参考

 

 

参考扩展:

Python 中 OS 模块获取文件/目录路径方法 - 守护往昔 - 博客园 (cnblogs.com)

python3中super()参数意义和用法_super里面的参数_wowocpp的博客-CSDN博客

【Python】python之subprocess模块详解_python subprocess_伐尘的博客-CSDN博客

Python os 模块详解 - 知乎 (zhihu.com)

import os # 导入必要模块
 
main_path="I:/s/ss/"#文件保存路径,如果不存在就会被重建
if  not os.path.exists(main_path):#如果路径不存在
    os.makedirs(main_path)
import os
filename = '/Users/joseph/work/python/poem.txt'

if not os.path.exists(filename):
    os.system(r"touch {}".format(path))#调用系统命令行来创建文件

 

标签:__,return,get,unittest,driver,----,测试,type,self
From: https://www.cnblogs.com/Candy123/p/17519597.html

相关文章

  • 110.成员初始化列表会在什么时候用到?它的调用过程是什么?
    110.成员初始化列表会在什么时候用到?它的调用过程是什么?1.当初始化一个引用成员变量时;structMyClass{constintmya;int&myb;MyClass(inta,int&b):mya(a),myb(b){}~MyClass(){}};2.当初始化一个非静态的常量成员时;inta=1;classMyClass{......
  • k8s 中的卷
    前面的文章我们分享了pod,RC,RS,DaemonSet,CJ,Service等各种资源今天我们来分享一波如何将磁盘挂载到容器中,在docker里面这种技术叫做数据卷,感兴趣的小伙伴可以查看一下文章:【Docker系列】docker学习六,探究一下数据卷容器对于一个pod,他有自己的CPU,RAM,网络接口等资源都是可......
  • 109.怎么快速定位错误出现的地方?
    109.怎么快速定位错误出现的地方?1.如果是简单的错误,可以直接双击错误列表里的错误项或者生成输出的错误信息中带行号的地方就可以让编辑窗口定位到错误的位置上。2.对于复杂的模板错误,最好使用生成输出窗口。多数情况下出发错误的位置是最靠后的引用位置。如果这样确定不了错......
  • 111.在进行函数参数以及返回值传递时,可以使用引用或者值传递,其中使用引用的好处有哪
    111.在进行函数参数以及返回值传递时,可以使用引用或者值传递,其中使用引用的好处有哪些?对比值传递,引用传参的好处:1)在函数内部可以对此参数进行修改2)提高函数调用和运行的效率(因为没有了传值和生成副本的时间和空间消耗)如果函数的参数实质就是形参,不过这个形参的作用域只是在函......
  • vue3 图片懒加载
    使用vue第三方库useIntersectionObserver创建文件directives/index.js导入第三方库import{useIntersectionObserver}from'@vueuse/core'exportconstlazyPlugin={install(app){app.directive('img-lazy',{mounted(el,binding){......
  • 112.说一说strcpy、sprintf与memcpy这三个函数的不同之处
    112.说一说strcpy、sprintf与memcpy这三个函数的不同之处1.复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。2.复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3......
  • HJ17 坐标移动
    1.题目读题HJ17 坐标移动  考查点 2.解法思路 代码逻辑 具体实现 publicclassHJ017{publicstaticList<String>directs=Arrays.asList("A","D","W","S");publicstaticvoidmain(String[]args){Scan......
  • 面向对象基本概念
    什么是面向对象?一切皆对象,用面向对象的思想去编码,描述您的需求。 1.面向对象编程(Object-OrientedProgramming,简称OOP)是一种编程范式,它将数据和操作数据的方法封装在一个对象中。2.面向对象(Object-Oriented)是一种编程范式或方法论,它将数据和操作数据的方法封装在一个称为对......
  • TortoiseGit v2.60
    TortoiseGit在大年初二迎来了大版v2.60Released:2018-02-17ThisisplannedtobethelastversioncompatiblewithMsysGit1.9.5(i.e.,GitforWindows<1.9.5).==Features==*Fixedissue#3089:ShowparentSHA1oncherrypickingamergecommit*Fixedi......
  • EZ CD Audio Converter
    CD转换抓轨软件(EZCDAudioConverter)是一个多合一音乐转换以及抓取软件,它提供了许多的功能。例如:它可以让我们转换声音文件,也可以让我们将音乐CD转换成MP3这类的格式,甚至是让我们将音乐刻录成音乐CD。原名EasyCD-DAExtractor的EZCDAudioConverter是一款相当老牌的......