自动化基础

杂七杂八

1.相对并发和绝对并发:

绝对并发是指同一时刻并发用户对服务器同时发送请求

相对并发是指同一段时间并发用户对服务器同时发送请求

2.性能瓶颈:

①硬件上的性能瓶颈:

​ 一般指的是CPU,内存,磁盘读写的瓶颈

②应用软件上的性能瓶颈:

​ 服务器操作系统的参数配置,数据库的参数配置,web服务器参数配置,中间件的参数配置。

③应用程序的性能瓶颈:

​ 开发人员开发出来的应用程序(SQL语句,数据库的设计,业务逻辑,算法等。)

④操作系统的性能瓶颈:

​ 一般是指windows,linux等,物理内存不足

⑤网络上的性能瓶颈:

​ 防火墙,交换机等。

3.配置git中ssh密钥遇到的问题:

报错:Unable to negotiate with 120.26.106.212 port 22: no matching host key type f

https://blog.csdn.net/weixin_42085115/article/details/123872782

4.JS的组成部分:

ECMAScript:描述该语言的语法和基本对象

文档对象模型(DOM):描述了处理网页内容的方法和接口

浏览器对象模型(BOM):描述了与浏览器进行交

5.XML的设计宗旨是为了传输和存取数据,把数据从HTML中分离出来,XML不能代替HTML,简化数据传输和数据共享。

6.XPath路径表达式:

Nodename选取此节点的所有子节点
/直接子节点
//选取当前节点的子孙节点
.选取当前节点
选取父节点
@选取属性

Xpath示例

Bookstore选取bookstore的所有子节点
/ Bookstore选取根元素Bookstore
Bookstore/book选取属于Bookstore下的所有book元素
//book选取所有book子元素,无论在文档的何处
Bookstore//book选取Bookstore下面的所有book元素,不考虑位置
//@lang选取名为lang的所有属性

7.python中的os库:与操作系统有关的操作,文件,目录,执行系统命令等。

获取本地文件目录时:os.getcwd()+os.sep+’文件名称’

其中:getcwd是获取文件的路径,sep是通用分隔符。

8.自动化测试分为三类:

单元自动化测试(代码),接口自动化测试(接口),UI自动化测试(对象为UI页面)

适合做自动化测试的场景

A) 回归测试

B) 冒烟测试

C) 重点功全面测试

D) 部分线上功能的监测

E) 业务场景的覆盖测试

自动化测试开展的原则

A) UI和接口相结合

B) 不能盲目追求用例的覆盖率

C) 注重可扩展性和可维护性

Selenium特点

A) 免费,开源

B) 支持多语言,多平台,多浏览器,技术成熟

C) 分布式(把测试用例分布到不同的测试机器上执行,相当于分发机的功能)

基点

元素属性

窗口句柄的切换以及浏览器的大小,位置等
from selenium import webdriver
from time import sleep
import chardet
import codecs  # 用来改写文件的编码格式的库
import os
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
print(driver.current_window_handle)  # 输出当前窗口的句柄
driver.find_element(By.XPATH, '//*[@id="hotsearch-content-wrapper"]/li[1]/a/span[2]').click()
a = driver.window_handles
print(driver.window_handles)  # 输出所有窗口的句柄
driver.switch_to.window(a[1])
print(driver.current_window_handle)  # 再次输出当前窗口句柄
# selenium4中提供了一个可以跳转新的网页的方法
# driver.switch_to.new_window('window')
# driver.switch_to.new_window('title')

winsize = driver.get_window_size()  # 获取浏览器窗口大小
print(type(winsize))
driver.set_window_size(500, 800)  # 设置浏览器的大小
print(driver.get_window_size())
driver.set_window_position(300, 400)  # 设置浏览器位置
print(driver.get_window_position())  # 输出浏览器位置
print(driver.title + "----------" + driver.current_url + "----------" + driver.current_window_handle)  # 输出网页标题+当前网页URL+当前网页的句柄
sleep(2)
# 改写txt的编码格式问题
# f=open("write.txt","a")#打开文件,如果没有则创建该文件
# pa=driver.page_source#获取网页的源码
# f.write(pa.decode)#将网页源代码写入txt文件中
driver.maximize_window()  # 最大化窗口
sleep(2)
driver.minimize_window()  # 最小化窗口
sleep(2)
driver.fullscreen_window()  # 浏览器窗口全屏
sleep(2)
driver.back()  # 网页后退
sleep(2)
driver.refresh()  # 刷新网页
sleep(2)
driver.forward()  # 网页前进
sleep(2)
driver.close()  # 关闭当前浏览器
sleep(2)
driver.quit()  # 停止运行
元素,可见不可见
from selenium import webdriver
from time import sleep

from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains

driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
#注意:按钮上的文字是不能通过text来获取的
aaaa=driver.find_element(By.CSS_SELECTOR,'#s-top-left > a:nth-child(1)').text#CSS选择器获取其中的文本内容,若无文本内容,则获取内容为空  <新闻>
aaa1=driver.find_element(By.CSS_SELECTOR,'#s-top-left > a:nth-child(1)')
bbbb=driver.find_element(By.CSS_SELECTOR,'#s-top-left > a:nth-child(1)').size#获取元素的大小
cccc=driver.find_element(By.CSS_SELECTOR,'#s-top-left > a:nth-child(1)').tag_name#获取元素的标签名称
print(aaaa)
print(bbbb)
print(cccc)
#当获取的元素无对应的属性时,会返回一个空的字符串
dddd=driver.find_element(By.ID,'kw')#百度搜索框
eeee=driver.find_element(By.ID,'su')#百度输入框
print(dddd.get_attribute('id'))
print(dddd.get_attribute('name'))
print(dddd.get_attribute('value'))
print(eeee.get_attribute('value'))
#输出元素的CSS属性值
print(aaa1.value_of_css_property("height"))#输出元素的高
print(aaa1.value_of_css_property("width"))#输出元素的宽
print(aaa1.value_of_css_property("font-family"))#输出元素所使用的字体
print(aaa1.is_displayed())#判断页面元素是否可见,若可见。则返回ture,否则返回flase
#当一个元素被另一个元素遮挡时,也会出现元素无法进行操作的情况
print(aaa1.is_enabled())#判断页面元素是否可用,若可用。则返回ture,否则返回flase
print(aaa1.is_displayed())#判断元素的选中状态,若选中或取消选中复选框又或者是单选按钮有加载时间的时候,要合理的使用等待,然后再去判断选中状态
#webdrive封装了一套鼠标操作包,鼠标操作流程,鼠标操作固定写法:ActionChains(driver).click(ele).perform()
#click()单击鼠标操作,double_click():双击鼠标操作
print("当前窗口的句柄为:"+driver.current_window_handle)  # 输出当前窗口的句柄
driver.get("http://sahitest.com/demo/clicks.htm")
a = driver.window_handles
print(driver.window_handles)# 输出所有窗口的句柄,获取窗口的所有句柄时只能获取通过点击跳转的窗口的句柄,通过webdriver获取的句柄无法获取
driver.switch_to.window(a[1])
dddd.send_keys()
#双击鼠标操作
aaa2=driver.find_element(By.CSS_SELECTOR,'body > form > input[type=button]:nth-child(10)')
if(aaa2.is_enabled()==True):
    ActionChains(aaa2).double_click().perform()
driver.quit()
鼠标操作
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from time import sleep

driver = webdriver.Chrome()
driver.get("http://sahitest.com/demo/clicks.htm")
#双击鼠标操作
aaa=driver.find_element(By.CSS_SELECTOR,'body > form > input[type=button]:nth-child(8)')
sleep(2)
#ActionChains(driver).double_click(aaa).perform()#这里鼠标双击写的不规范
#在采用方法的形式后书写如下:
#action = ActionChains(self.driver)
#action.double_click(element_double_click)

if(aaa.is_enabled()==True):
    ActionChains(driver).double_click(aaa).perform()#鼠标双击
else:
     print("页面元素不可见")
sleep(2)
bbb=driver.find_element(By.XPATH,'/html/body/form/input[4]')

if(bbb.is_enabled()==True):
    ActionChains(driver).context_click().perform()#鼠标右击操作
else:
    print("页面元素不可见")
sleep(2)
driver.quit()
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from time import sleep

driver = webdriver.Chrome()
driver.get("https://sahitest.com/demo/dragDropMooTools.htm")
a=driver.find_element(By.XPATH,'//*[@id="dragger"]')
b=driver.find_element(By.XPATH,'/html/body/div[2]')
sleep(2)
#鼠标的连续拖动如何实现
ActionChains(driver).drag_and_drop(a,b).perform()#模拟鼠标拖动的效果
sleep(2)
键盘操作
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from time import sleep

from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
driver.find_element(By.ID,'kw').send_keys('狗子')
sleep(2)
driver.find_element(By.ID,'kw').send_keys(Keys.CONTROL,'a')#模拟键盘的组合键操作
driver.find_element(By.ID,'kw').send_keys(Keys.CONTROL,'c')
sleep(2)
driver.find_element(By.ID,'kw').clear()#清空数据
sleep(2)
driver.find_element(By.ID,'kw').send_keys(Keys.CONTROL,'v')
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from time import sleep

from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()
driver.get("https://sahitest.com/demo/clickCombo.htm")#模拟按住CTRL键不松开,单机某控件的情况
a=driver.find_element(By.XPATH,'/html/body/div/div')
sleep(2)
ActionChains(driver).key_down(Keys.CONTROL).perform()
a.click()
sleep(2)
driver.quit()

python中submit和click的区别:

  • click是在元素页面上的事件,submit是属于表单控件上的事件。
  • 点击提交按钮的时候,一般是先触发click事件,然后再触发submit事件
  • click->click响应事件->submit->submit响应事件
  • click单击元素,submit提交表单
  • button按钮不支持s
复选框
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()
driver.get("https://sahitest.com/demo/clicks.htm")
ele = driver.find_element(By.XPATH,'/html/body/ul/li/a/label/input')
ele.click()
sleep(2)
if ele.is_selected():
    print("pass")
sleep(2)
ele.send_keys(Keys.SPACE)#按住空格键能够选中或者取消选中复选框
sleep(2)
driver.quit()#如果有一组元素,可以通过find_elements来存储属性值相同的一组元素,,将其存储为一个列表,然后循环读取并对列表进行操作即可
**select下拉列表
#导入select有两种方式:
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.select import Select
#这两种方式没有任何区别,通过查看ui包的源码, ui包中就是调用的select中的select方法
#选择下拉框
sel = Select(driver.find_element(By.ID, 's4Id'))  # 实例化select
sleep(2)
all_options = sel.options  # potions返回的一个列表,选择列表中的所有元素
sleep(2)
print("options的返回值类型为:{}".format(type(all_options)))
mylist1 = []  # 定义一个空数组
for i in all_options:
    mylist1.append(i.text)  # 将属性中的text存入数组中
print("下拉列表的选项有:{}".format(mylist1))

for j in range(0, len(all_options)):  # 通过选项获取元素值来循环
    sel.select_by_index(j)
first = sel.first_selected_option.text
print("第一个被选中的元素是:{}".format(first))
res = sel.is_multiple#判断下拉列表是否支持多选,结果返回布尔类型
print("该下拉列表是否支持多选:{}".format(res))

sleep(2)
sel.select_by_index(1)  # 采用索引值进行定位
sleep(2)
sel.deselect_by_index(1)
sleep(2)
sel.select_by_value('o2val')  # 采用value值进行定位
sleep(2)
sel.deselect_by_value('o2val')
sleep(2)
sel.select_by_visible_text('o3')  # 采用文本值进行定位
sleep(2)
sel.deselect_by_visible_text('o3')
sleep(2)
all = sel.all_selected_options  # 通过all来获取所有被选中的元素
mylist2 = []
for k in all:
    mylist2.append(k.text)
print("所有被选中的选项文字是:{}".format(mylist2))
sleep(2)
driver.quit()

注意事项:

  • inde从0开始;
  • value是的属性值,并不是显示再下拉列表中的值;
  • visible_text是选择中的的值,即显示在文本框上的值;
  • 采用visible_text是以空格开头时,定位的传入值可以输入多个空格,也可以不加空格
  • 当text中间有空格的时候,只有一个空格
  • 当text中有nbsp时,根据nbsp的数量加空格

如果select中的标签中含有:multiple = ‘multiple’,这时就意味着该下拉列表的选项可以多选

select包中提供了3中选择选择项的方法和4中取消选择选项的方法。

options属性返回的是选项的元素,如果想获取文本,需要使用".text"

在html中"&nbsp"和空格的区别:无论输入多少个空格,页面上显示也只有一个空格,“&nbsp”则是输入几个就有几个空格

框架

iframe的切换方法有4种:

  • 通过id切换(有id属性),driver.switch_to.frame(“iframe1”)
  • 通过name切换(有name属性,且唯一),driver.switch_to.frame(“name1”)
  • 通过index切换(index从0开始,0代表页面中的第一个iframe),driver.switch_to.frame(0)
  • 通过定位元素切换:driver.switch_to.frame(iframe_element)

#driver.switch_to.parent_frame()#切换上一级页面(父框架)

#driver.switch_to.default_frame()#切换上一级页面(最原始的页面)

driver.switch_to_frame也可以切换框架,但是不建议使用,在后续的版本中它可能会被废弃。*

日期时间控件

注意事项:

  • 日期时间控件一般不去用自动化验证控件的功能,第一是已经有很多的团队验证过其功能的健全性。而是成本太高。
  • 有些日期的控件有readonly属性。
  • …send_keys(“002020/06/06”)#日期时间的定位
文件下载

​ 文件下载的本质是标签,类似与链接,单机链接会跳转到新的页面,下载链接会在单击后下载文件。

.click()之后便会下载到默认目录

  • 单击下载后,在文件下载完成之前不能退出浏览器,否则文件时不会保存成功的。一般会加个等待时间。
下载文件到指定目录:

即使在浏览器中修改默认下载地址,通过webdriver打开的浏览器也初始化浏览器。

文件上传
文件的MD5值

Message-DigestAlgorithm5(信息-摘要算法)是一种不可逆的加密算法,MD5的值类似与一个文件的“数字指纹”,如果一个文件做了任何的改动,MD5值就会发生变化

windows上查看文件的MD5:

进入指定目录:certutil -hashfile 新建文本文档.txt MD5(或者对文件使用绝对路径

linux上查看文件的MD5:

md5sum –c 文件名.md5

复杂控件的操作

操作Ajax选项

Ajax:是一种创建交互式,快速动态网页应用的网页开发技术。通过后台与服务器进行少量的数据交换,Ajax可以使网页实现异步更新。

#操作Ajax选项列表
driver.get("https://sogou.com/")
ele = driver.find_element(By.XPATH,'//*[@id="query"]')
ele.click()
sleep(2)
ele2 = driver.find_element(By.XPATH,'//*[@id="vl"]/div[1]/ul')
ele3 = ele2.find_elements(By.TAG_NAME,'li')#通过标签便利整个ul列表
print(len(ele3))
for i in range(0,len(ele3)):
    ele.send_keys(Keys.ARROW_DOWN)#键盘操作下键
#通过模糊匹配选择选项
driver.get("https://sogou.com/")
ele = driver.find_element(By.XPATH,'//*[@id="query"]')
ele.send_keys("狗子")
sleep(1)
secont = driver.find_element(By.XPATH,'//*[@id="vl"]/div[1]/ul[1]/li[contains(.,"表情包")]').click()#
sleep(2)
driver.quit()

selenium等待机制

强制等待(sleep()):主要是帮助验证每个页面的加载时间是否超时。

隐式等待(implicitly_wait()):等待固定时间,在固定时间内反复检查,隐式等待作用域全局,可以随时更改。

显示等待():可自定义等待条件,在规定时间内满足操作的条件就会继续执行,否则会一直等到超时。可以自定义等待条件,针对单一或者一组元素生效。

WebDriver提供了WebDriverWait和expected_conditions类来实现显示等待;

WebDriverWait用来定义超时时间,轮询频率等,expected_conditions提供了一些预制条件,作为测试脚本进行后续操作的判断依据。

WebDriverWait提供了两个方法until和until_not,until在规定的时间内调用method方法,直到其返回值不为False,

until_not则相反,在规定的时间内调用method方法,直到其返回值为False。

#判断页面title
driver.get("https://www.baidu.com")
try:
    ele = WebDriverWait(driver,10).until(EC.title_is("百度一下,你就知道")) #判断title
    print(type(ele)) #输出返回值类型
    driver.find_element(By.LINK_TEXT,'地图').click() #如果匹配,就执行该语句
# 程序捕捉到异常的时候直接抛出异常,不会执行下面的程序
except Exception as e: 
    raise e #将异常信息存储到e中并抛出异常

python中的异常类型:

TypeError:将不同类型的数据进行运算操作时,会引发TypeError(不同类型间的无效操作)异常。

ZeroDivisionError:当除数为0的时候引发的异常。

NameError:访问一个未声明的变量时引发的异常。

SyntaxError:解释器语法错误时引发的异常。

IndentationError:代码缩进错误。

IndexError:索引超出序列的范围时引发的异常。

KeyError:当字典中查找一个不存在的关键字所引发的异常。

ValueError:传参类型不正确引发的异常。

FileNotFoundError:打开一个不存在的文件时所引发的异常。

AttributeError:当尝试访问未知的对象时引发的异常。

driver.get("https://www.baidu.com")
try:
    ele = WebDriverWait(driver,10).until(EC.title_is("百度一下,你就知道")) #判断title
    print(type(ele)) #输出返回值类型
    driver.find_element(By.LINK_TEXT,'地图').click() #如果匹配,就执行该语句
# 程序捕捉到异常的时候直接抛出异常,不会执行下面的程序
except Exception as e: 
    raise e #将异常信息存储到e中并抛出异常
except ZeroDivisionError:
    print("除数不能为0!")
except ValueError:
    print("类型异常")
except(ZeroDivisionError,ValueError) as r: #对多个异常统一进行处理
    print("捕获到异常!")
    print("")
except: #捕获所有的异常
    print("出错啦!")
finally:
    driver.get() #无论是否引发异常都会继续执行的代码块
    
try:
    #可能引发异常的代码块
except Exception:
    #出现异常后执行的代码块
else:
    #如果try中没有引发异常,就会执行该代码
finally:
    #无论是否引发异常都会继续执行的代码块

#assert断言
#assert 逻辑表达式,'参数'
#用户自定义异常类
class SexException(Exception):
    def __init__(self,msg,value):
        self.msg = msg
        self.value = value
    def f(self):
        sex = input("请输入:")
        if sex != '男' and sex != '女':
            raise  SexException("性别异常")
    try:
        f()
    except Exception as r:
        print("错误信息是:%s,输入的性别是:%s"%(r.msg,r.value))

自动化框架的搭建

1.总体框架如图:https://csdnimg.cn/release/blogv2/dist/pc/themesSkin/skin-whitemove/images/bg.gif

2.主要设计思想采用采用PO模式、数据驱动的思想(对象,数据,业务,三者分离的模型)

3.PO(Page Object)设计模式:UI自动化测试采用的一种设计模式,用于解决开发频繁修改UI页面而导致的自动化脚本维护困难的问题。

PO模式中心思想:

  • 每一个页面为一个对象;
  • 每一个对象维护着页面中各元素和操作方法;
  • 用例测试脚本只需要聚集业务逻辑和测试数据;
  • UI页面页面的变更,只需要修改对应的PO对象,无需修改测试脚本(很难做到,因为UI的变更更多的时候意味着业务逻辑的变更)

DDT(Data Driven Testing)数据驱动测试模式,用来解决部分自动化用例逻辑完全相同,只有数据和预期结果不同的问题。实际就是同一测试脚本使用不同的测试数据来反复执行,测试数据和测试行为完全分离。

DDT中心思想:

  • 将测试数据分离出来
  • 减少重复自动化用例的数量

将各个步骤抽象出一个方法,每次使用的时候都进行调用

unitest和pytest的:

  • pytest中的编写测试用例格式比较简单,兼容性高,可同时兼容unittest,插件非常丰富,使用第三方插件allure,方便标记测试用例和重跑用例
  • unitest的数据驱动要比pytest要好

unitest框架

unittest框架

  • 测试固件(test fixture):SetUp,tearDown,setUpClass,tearDownClass
  • 测试用例(test case):以test开头,并且执行顺序依照的是方法名的ASCII码值排序。
  • 测试套件(test suite):一组测试用例,执行一个测试套件就可以将这些测试用例全部执行
  • 测试运行器(test runner):执行测试用例,也可以用图形,表格等展示出来。

unittest中的测试固件:

  • setUp:执行测试前的初始化工作
  • tearDown:负责测试后的清理工作
  • setUpClass:在所有测试方法执行前执行,负责单元测试的前期准备。必须使用@classmethod装饰器进行修饰

如合理的组织测试用例以及添加合适的断言非常的关键:

  • 多个测试用例文件尽量不要存在依赖关系,否则一旦依赖的测试用例执行失败,后续有以来关系的测试用例也会执行失败。
  • 一个测试用例文件只包含一个class类,一个class对应一个业务场景
  • 一个class类可以包含多个def定义的测试用例
  • 一个def用例下可以添加多个断言,类似于你在做功能测试的时候一个步骤可能需要检查多个点。

执行测试用例:

third = 20
class TestFirst(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        print('setUpClass')

    def setUp(self) -> None:
        print("setup")

    def test_first(self):
        print(1)
        self.assertEqual('first', 'first')

    def test_second(self):
        print('second')
        self.assertEqual('second', 'second')

    def test_third(self):
        fouth = third + 5
        self.assertEqual(fouth, 25)
        print(fouth)

    def tearDown(self) -> None:
        print('tearDown')

    @classmethod
    def tearDownClass(cls) -> None:
        print('tearDownClass')
##################################################################
if __name__ == '__main__':
    unittest.main() # unittest.main()是对上方脚本进行自测,不影响其他文件的调用
##################################################################
if __name__ == '__main__':
    testSuite = unittest.TestLoader().discover(".")  # 在指定目录中寻找符合条件的测试用例
    unittest.TextTestRunner(verbosity=2).run(testSuite)
##################################################################
if __name__ == '__main__':
    suite = unittest.TestSuite()  # 测试套件
    suite.addTest(unit_demo1.TestFirst("test_first"))
    #suite.addTest(unit_demo2.TestSecond("test_third"))	
    suite.addTest(unit_demo2.TestSecond("test_fourth"))  # 通过addTest将某个class下面的测试用例添加到集合,然后执行测试用例
    unittest.TextTestRunner().run(suite)  # 测试运行器

用例的执行次序:

测试用例执行的顺序是依照方法和函数名的ASCII值进行的排序。因为在测试的过程中,部分的测试用例存在着前后的以来关系,这时就需要对他们进行顺序执行。

内置装饰器:
unittest提供的装饰器:

  • 无条件跳过装饰器 @unittest.skip(‘skip info’)
  • 满足条件跳过装饰器 @unittest.skipIf(third > 10, ‘info’)
  • 不满足条件跳过 @unittest.skipUnless(third == 10, ‘info’)
class TestFirst(unittest.TestCase):
    @classmethod  # 该装饰器必须有
    def setUpClass(cls) -> None:
        print('setUpClass')

    def setUp(self) -> None:
        print("setup")

    @unittest.skipUnless(third == 10, 'info')  # 不满足条件跳过,因为third不满足 == 10 的条件,所以跳过
    def test_001_first(self):
        print(1)
        self.assertEqual('first', 'first')

    @unittest.skip('skip info')  # 无条件跳过的装饰器
    def test_002_second(self):
        print('second')
        self.assertEqual('second', 'second')

    @unittest.skipIf(third > 10, 'info')  # 如果满足条件则跳过
    def test_003_third(self):
        fouth = third + 5
        self.assertEqual(fouth, 25)
        print(fouth)

    def tearDown(self) -> None:
        print('tearDown')

    @classmethod
    def tearDownClass(cls) -> None:
        print('tearDownClass')

批量执行测试文件:

  • 以“test”开头的测试文件名为符合条件的测试用例
  • “.”代表当前目录,也可以构造,传递其他目录
if __name__ == '__main__':
    testSuite = unittest.TestLoader().discover(".")  # 在指定目录中寻找符合条件的测试用例
    unittest.TextTestRunner(verbosity=2).run(testSuite)

测试断言:

方法检查
assertEqual(a,b)a == b
assertNotEqual(a,b)a != b
assertTrue(x)Bool(X) is True
assertFalse(x)Bool(X) is False
assertIs(a,b)a is b
assertNotIs(a,b)a is not b
assertNone(X)X is None
assertNotNone(X)X is Not None
assertIn(a,b)a in b
assertNotIn(a,b)a not in b
assertIsInstance(a,b)Instance(a,b)
assertNotIsInstance(a,b)not instance(a,b)
  • Instance:判断a变量是否是b类型;

assertEqual和assertIs的区别:前者只判断值是否相等,后者则需要数值类型相同。

unittest参数化:

DDT数据驱动主要通过装饰器的方式来实现数据驱动

  • @ddt类装饰器,标记这个类使用的ddt数据驱动的框架
  • @data函数装饰器,主要是给函数传值
  • @unpack函数装饰器,主要是给数据解包,一般应用元组和列表
  • @file_data函数装饰器,主要是直接取json/yaml文件
  1. @ddt.data,括号中可以传递列表或元组
  2. 下面这个测试用例包含3个参数:
    (1)第一个是用户名参数(2)第二个是密码的取值(3)第三个是登录成功与否:一般0代表失败,1代表成功。
  3. 测试类前先声明@ddt.ddt
  4. 测试方法前加上@ddt.data和@ddt.unpack,前者定义的数据顺序必须和测试方法的形参保持一致,后者是对测试数据进行解包

​ @ddt.data([‘admin’,‘error’,‘0’],[‘admin’,‘rootroot’,‘1’])

unittest+parameterized参数化

@parameterized.expand括号中传递列表

  • 列表中传递元组,每一个元组代表一个测试用例
  • from parameterized import parameterized,param需要导入连个包
  • 测试类不需要进行装饰
  • 只需要在方法中进行装饰

@parameterized.expand([(),()])

Pytest框架

实例变量要放在构造函数中,但是pytest中没有构造函数。

运行原理:用例的前置和后置setup/teardown

类的前置中一般是用于存放加载驱动等信息,后置一般是quit()进行关闭

  • pytest规则:默认为“test_”开头或结尾,但是unittest以test开头或结尾
  • 测试类(class)命名:默认以“Test”开头
  • 测试方法(函数)命名:默认以“_test”开头
Pytest测试固件
测试固件解释
setup_module/teardown_module用于模块的始末,只执行一次 ,全局方法
setup_function/teardown_function只对函数生效,不在类中
steup_class/teardown_class在类中开始结束时执行
setup_method/teardown_method在类中的方法开始和结束处执行
setup/teardown在调用方法的前后执行
import pytest


class TestStorm(object):
    def setup_module(self):  # 在整个文件的开始和结束执行一次
        print('setup_module')

    def teardown_module(self):
        print('teardown_module')

    def setup_function(self):  # 在每个函数前后执行一次
        print('setup_function')

    def teardown_function(self):
        print('teardown_function')

    def setup_class(self):  # 在每个class的开始和结束各执行一次
        print('setup_class')

    def teardown_class(self):
        print('teardown_class')

    def setup_method(self):  # 在每个方法前后执行一次
        print('setup_method')

    def teardown_method(self):
        print('teardown_method')

    def test_a(self):
        print('a')
        assert 'a' == 'a'

    def test_b(self):
        print('b')
        assert 'b' == 'b'


if __name__ == '__main__':
    pytest.main(["-s", "Pytest01.py"])

建议在一个项目中约定好是定义class来组织测试用例,还是直接定义函数来组织用例。

Pytest测试用例和断言
  • 直接通过函数定义测试用例的话,def后面的括号中没有self
  • 通过class中的方法定义测试用例的话,def后面的括号中有self
import pytest


def test_c():
    print('ccc')
    assert 'c' == 'c'


class Test01(object):
    def test_a(self):
        print('aaa')
        assert 'a' == 'a'

    def test_b(self):
        print('bbb')
        assert 'b' == 'b'


if __name__ == '__main__':
    pytest.main(["-s", "Pytest02.py"])

pytest使用的是python中自带的asser关键字进行断言,使得代码更加灵活、

import pytest


class Test03(object):
    def setup_class(self):  # 在每个class的开始和结束各执行一次
        print('setup_class')

    def teardown_class(self):
        print('teardown_class')

    def setup_method(self):  # 在每个方法前后执行一次
        print('setup_method')

    def teardown_method(self):
        print('teardown_method')

    def test_a(self):
        print('aaa')
        assert 5 > 3 # 算数运算符

    def test_b(self):
        print('bbb')
        assert 'Strom' in 'Hello Strom' # 成员运算符


if __name__ == '__main__':
    pytest.main(["-s", "Pytest03.py"])

Pytest框架的执行方式

pytest.main([“-s”]) 执行当前文件所在目录下所有符合条件的测试用例

注意

  • unittest中直接使用的unittest.main(),括号中不需要提供参数,默认执行当前文件中符合条件的测试用例。
  • Pytest中直接使用的Pytest.main(),括号中不需要提供参数,默认执行当前文件和下级目录中的符合条件的测试用例。
  • -s,关闭捕捉,输出信息。-v参数则不会在控制台输出信息
  • main()括号中传递的参数必须放在列表中。

Pytest中调试的时候还可以通过在文件名后加两个冒号和class名来指定执行某个class

import pytest


class Test03(object):
    def setup_class(self):  # 在每个class的开始和结束各执行一次
        print('setup_class')

    def teardown_class(self):
        print('teardown_class')

    def setup_method(self):  # 在每个方法前后执行一次
        print('setup_method')

    def teardown_method(self):
        print('teardown_method')

    def test_a(self):
        print('aaa')
        assert 5 > 3

    def test_b(self):
        print('bbb')
        assert 'Strom' in 'Hello Strom'


class Test03_02(object):
    def setup_class(self):  # 在每个class的开始和结束各执行一次
        print('setup_class')

    def teardown_class(self):
        print('teardown_class')

    def setup_method(self):  # 在每个方法前后执行一次
        print('setup_method')

    def teardown_method(self):
        print('teardown_method')

    def test_c(self):
        print('ccc')

    def test_d(self):
        print('ddd')


if __name__ == '__main__':
    # pytest.main(["-s", "Pytest03.py"])
    pytest.main(["-s", "Pytest03.py::Test03_02"])

在命令行窗口执行Pytest脚本

  • 进入文件所在目录:pytest +文件名
  • 进入文件所在目录:pytest+文件名::类名

输出参数:

pytest框架用例执行失败重试

在dos中执行,pytest +文件名 --reruns +执行次数

也别注意的是这种方式在使用的时候要谨慎,有可能会跳过某些应该会出现的问题

标记机制(对测试用例进行分级):

import pytest


class Test03(object):
    def setup_class(self):  # 在每个class的开始和结束各执行一次
        pass

    def teardown_class(self):
        pass

    def setup_method(self):  # 在每个方法前后执行一次
        pass

    def teardown_method(self):
        pass

    @pytest.mark.L1
    @pytest.mark.L2
    def test_a(self):
        print('aaa')
        assert 5 > 3

    @pytest.mark.L1
    def test_b(self):
        print('bbb')
        assert 'Strom' in 'Hello Strom'


class Test03_02(object):
    def setup_class(self):  # 在每个class的开始和结束各执行一次
        pass

    def teardown_class(self):
        pass

    def setup_method(self):  # 在每个方法前后执行一次
        pass

    def teardown_method(self):
        pass

    @pytest.mark.L1
    def test_c(self):
        print('ccc')
        assert 'c' == 'c'

    @pytest.mark.L3
    def test_d(self):
        print('ddd')
        assert 'd' == 'd'


if __name__ == '__main__':
    pytest.main(["-s", "Pytest03.py"])
    # pytest.main(["-s", "Pytest03.py::Test03_02"])

跳过某些用例:

import pytest


class Test_04(object):
    def setup_class(self):  # 在每个class的开始和结束各执行一次
        pass

    def teardown_class(self):
        pass

    def setup_method(self):  # 在每个方法前后执行一次
        pass

    def teardown_method(self):
        pass

    @pytest.mark.skip(reason='原因')  # 实现无条件跳过
    def test_a(self):
        print('aaa')
        assert 'a' == 'a'

    @pytest.mark.skipif(2 > 1, reason='原因')  # 当满足条件的时候跳过该用例
    def test_b(self):
        print("bbb")
        assert 'b' == 'b'

    def test_c(self):
        print('ccc')
        assert 'c' == 'c'


if __name__ == '__main__':
    pytest.main(["-s", "Pytest04.py"])

全局设置:

  • 文件名必须是pytest.ini
  • 文件的内容必须以“[pytest]”开头
  • 文件内容不能包含中文

(1)命令行参数

​ 通过关键字“addopts”来设置命令行参数。如“-s”或者“-v”监控,失败重试的次数,重试的时间间隔,按标签来执行,多个参数之间用空格进行隔离。

[pytest]
#命令行参数,用空格进行分隔
addopts = -v -s --reruns 1 --reruns-delay 2 -m "L1 or L2" --html=./report.html

#自定义标签
marks =
    smoke: Run the smoke test functions for tasks project	#冒烟用例
    get: Run the test functions that test tasks.get()	#get请求用例
    post: Run the test functions that test tasks.get()	#post请求用例
    L1:Level 1 case
    L2:Level 2 case

#执行测试用例的路径
testpaths = test_cases
#查找文件名以“test_”开头的文件
python_files = test_*.py
#查找类以“Test”开头的类
python_classes = Test*
#查找函数以“test_”开头的函数
python_functions = test_*

–html=./report.html:在当前文件夹生成一个测试报告,采用的是pytest-html。

!(python_auto.assets/image-20221022004727266.png)

参数化:@pytest.mark.parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)

  • 参数名称,字符串格式,多个参数用逗号隔开,如:“arg1,arg2,arg3”
  • argvalues:参数值,需要与argnames对应,元组或列表,如:[ val1,val2,val3 ],如果有多个参数例,元组列表一个元组对应一组参数的值,如:@pytest.mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”), (“yy3”, “123”)])
  • ndirect:默认为False,代表传入的是参数。如果设置成True,则把传进来的参数当函数执行,而不是一个参数
  • ids:自定义测试id,字符串列表,ids的长度需要与测试数据列表的长度一致,标识每一个测试用例,自定义测试数据结果的显示,增加程序的可读性
    #静态参数化
    @pytest.mark.parametrize('user,passwd',
                             [pytest.param('name1', '123', id='User:name1'),
                              pytest.param('name2', '123456789', id='User:name2'),
                              pytest.param('name2', '123', id='User:name2')
                              ])
    def test_passwd_length(self,user,passwd):
        # assert user == 'name1'
        assert len(passwd) >= 2
        assert len(passwd) <= 9
    
    #便利多种用例
    @pytest.mark.parametrize('user',['11','22','33','44'])
    @pytest.mark.parametrize('passwd',['1234','23456','3','4'])
    def test_passwd_length(self, user, passwd):
        # assert user == 'name1'
        assert len(passwd) >= 2 and len(passwd) <= 9
        # print(f'name:{user} pwd:{passwd}')

	#动态参数化
PO设计模式

整体来说是将系统代码定位器放在一个文件下:

  • 第一层:将所有元素对象定位器放到一个文件夹下
  • 第二层:将所有元素操作放在一个文件夹下
  • 第三层:将公共的业务场景封装到一个文件夹下

(1)封装定位器:
mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”), (“yy3”, “123”)])

  • ndirect:默认为False,代表传入的是参数。如果设置成True,则把传进来的参数当函数执行,而不是一个参数
  • ids:自定义测试id,字符串列表,ids的长度需要与测试数据列表的长度一致,标识每一个测试用例,自定义测试数据结果的显示,增加程序的可读性
    #静态参数化
    @pytest.mark.parametrize('user,passwd',
                             [pytest.param('name1', '123', id='User:name1'),
                              pytest.param('name2', '123456789', id='User:name2'),
                              pytest.param('name2', '123', id='User:name2')
                              ])
    def test_passwd_length(self,user,passwd):
        # assert user == 'name1'
        assert len(passwd) >= 2
        assert len(passwd) <= 9
    
    #便利多种用例
    @pytest.mark.parametrize('user',['11','22','33','44'])
    @pytest.mark.parametrize('passwd',['1234','23456','3','4'])
    def test_passwd_length(self, user, passwd):
        # assert user == 'name1'
        assert len(passwd) >= 2 and len(passwd) <= 9
        # print(f'name:{user} pwd:{passwd}')

	#动态参数化
PO设计模式

整体来说是将系统代码定位器放在一个文件下:

  • 第一层:将所有元素对象定位器放到一个文件夹下
  • 第二层:将所有元素操作放在一个文件夹下
  • 第三层:将公共的业务场景封装到一个文件夹下

(1)封装定位器:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值