目录
WebDriverWait、implicitly_wait、sleep
UnicodeEncodeError: 'latin-1' codec can't encode character '\u7b2c'
说明
本文是平时进行自动化测试时学习与工作时的知识整理。如有不对之处请帮忙指正。
python、selenium、pycharm基础知识
-
python
先谈谈对象、变量、常量、名字
通俗地说,对象它就是一个东西,它装的是数据。它有类型(比如bool型、整型int、浮点型float、字符串、列表、元组、字典,还有复数型),而它的类型决定了它装的数据是变量还是常量。变量是可变的,常量是不可变的。目前我们要用到的是变量。
我们可以改变已存在对象的数据,但是我们不能改变这个对象的类型。
变量也只是一个称呼,是在程序中为了方便引用内存中的值而为它取的名称。
变量名只能包含以下字符:a-z,A-Z,0-9,_
[例]
a=1
b=2
c=a
c=b
用type()方法可以知道一个对象的类型。例如type(a),结果会是<class 'int'>,表明这是一个int类型。
(class是对象的定义,在python中类和类型一般不加区分)
-
-
数据结构
-
数据结构及相关基本方法
(1)字符串。用''创建
基本方法 | 释义 | 举例 |
str() | 可将其他类型转换为字符串 | 例如str(1),print(type(str1)) 结果是<class 'str'> |
\ | 转义。有一些常用的转义符:\n,\t,\',\" | \n表示换行、\t表示tab制表符 |
r | 表示非转义的原生字符串 | |
+ | 拼接 | letters=‘abc’+'def',结果为abcdef |
[] | 提取。从左到右,第一个字符的偏移量为0,以此类推; 从右到左,最后一个字符的偏移量为-1,以此类推。 | letters[0]的结果是字符a, letters[-1]的结果是字符f |
[start:end:step] | 分片。偏移量从0开始。 [:]从头到尾 [start:]从start到结尾 [:end]从开头到end-1 [start:end:step]从start到end-1,每step个字符提取一个 | number='0123456789' number[0:10:2] 输出结果为: 02468 |
len() | 获取长度 | len(number)的结果是10 |
split() | 分割。分割后的结果是一个列表。 | s1='I have a new computer' sp=s1.split(' ') sp的结果为: ['I', 'have', 'a', 'new', 'computer'] 类型为:<class 'list'> |
join() | x.join(list)将列表list以字符串x进行合并 | nowDate='2020-11-23' nowDateSp=nowDate.split('-') nowDateJ='-'.join(nowDateSp) nowDateJ结果为:2020-11-23 |
列表list。用[]或list()创建
基本方法 | 释义 | 举例 |
list() | 可将其他类型转换为列表 | li=list('test') 结果为['t','e','s','t'] |
[] | 可通过[偏移量]提取或修改元素。 从左到右,第一个字符的偏移量为0,以此类推; 从右到左,最后一个字符的偏移量为-1,以此类推。 | letters=['I\'ll be waiting you','though I know you will never come back'] print(letters[1]) letters[1]='untill you come back' print(letters) 输出结果为: though I know you will never come back ["I'll be waiting you", 'untill you come back'] |
[start:end:step] | 分片。偏移量从0开始。 [:]从头到尾 [start:]从start到结尾 [:end]从开头到end-1 [start:end:step]从start到end-1,每step个字符提取一个 | |
append() | 添加元素至尾部 | letters=['I\'ll be waiting you,','untill you come back.'] letters.append('Please don\'t leave me alone') print(letters) 输出结果为:["I'll be waiting you,", 'untill you come back.', "Please don't leave me alone"] |
extend()或+= | 合并列表。 | |
insert(location,string) | 在指定位置插入元素 | |
del | 删除指定位置的元素 | |
remove() | 删除具有指定值的元素 | |
pop() | 获取并删除指定位置的元素 | |
index() | 查询具有指定值的元素位置 | |
count() | 记录指定值出现的次数 | |
sort(),sorted() | 重新排列元素。 可添加参数reverse.reverse=True改变为降序排列。 listA.sort()会改变原listA的内容 sorted(listA)是创建了一个副本,不会改变原listA的内容 | sort()对列表,sorted()对所有可迭代对象 |
=,copy() | =赋值;copy()复制 |
字典dict。用{}创建字典
用{}将一系列键值对(key:value)以逗号分隔包裹起来,格式为{键1:值1,键2:值2}。
字典是无序的。
基本方法 | 释义 | 举例 |
dict() | 可将包含双值子列表的列表转换为字典 | |
[key] | 可通过[key]获取、添加或修改元素。 从左到右,第一个字符的偏移量为0,以此类推; 从右到左,最后一个字符的偏移量为-1,以此类推。 | |
update() | 合并字典。 | |
del | 删除指定键的元素 | |
clear() | 删除所有元素 | |
in | 判断是否存在。存在返回True,不存在则返回False。 | |
keys() | 获取所有键 | |
values() | 获取所有值 | |
items() | 获取所有键值对 | |
=,copy() | =赋值;copy()复制 |
-
集合set。用set()创建
集合是无序的、且内容不可重复的。
可使用{a,b,c}创建集合,但是只能用set()创建空集合。
基本方法 | 释义 | 举例 |
set() | 可将其他类型转换为集合。 重复的值会被丢弃。 | |
in | 判断是否存在。存在返回True,不存在则返回False。 | |
&,intersection() | 取交集 | |
|,union() | 取并集 | |
-,difference() | 取差集 | |
^,symmetric_difference() | 取异或集 | |
<=,issubset() | 取子集 | |
>=,issuperset() | 取超集 | |
> | 取真超集 |
-
关于None
None表示空值,它是一个特殊 Python 对象, None的类型是NoneType。但是None不等于0、空字符串、空列表、空字典等。特别注意不要搞错None和空字符串''。
可以认为None是内存中不同于其他的一块内存空间,字符串、空列表等各自指向其他内存块,而且None是唯一的。
可以将None赋值给任何变量,也可以给None值变量赋值。
判断一个变量是否为None,用if 变量 is None。
-
关于=,copy和deepcopy
copy和deepcopy要用到copy模块。from copy import copy,deepcopy
a=b赋值,是将a、b两个名字指向同一个对象
a=copy(b)或a=b.copy(),是会再创建一个对象,且是a指向新的对象,b依然指向旧的对象。但是copy只是浅层复制,只会复制一层,a对象中引用的内存地址不会改变。
a=deepcopy(b),是会创建新的对象,且引用的内存中数据会再复制一份。a对象中引用的内存地址是新的。
当碰到大型数据结构时,用copy就容易出现问题,如果用copy出现问题则改用deepcopy。
详细对比分析请看:https://blog.csdn.net/fflush_stdin/article/details/126919208?spm=1001.2014.3001.5501
【练习】
1.获取到了3个字段的提示信息:info1='"文本1"未填写',info2='"单人力1"未填写',info3='"编号"重复',info4='"登录名"重复'。(其中“”中的是字段名,未填写是该字段的提示内容)
-
分别用[]和split()获取字段名,并存入一个列表listName中
-
获取字段名和提示内容,以字段名为键、提示内容为字典创建一个字典dictInfo。
-
-
代码结构
-
-
注释、连接
基本方法 | 释义 | 举例 |
# | 注释 | |
\ | 放在末尾,表示连接下一行 | A='lis'+\ 't' print(A)的结果为list |
-
if、elif、else;True、False
除了以下的假值以为,都会被认为是真值True
假值(False) | |
类型 | 值 |
布尔 | False |
null类型 | None |
整形 | 0 |
浮点型 | 0.0 |
空字符串 | '' |
空列表 | [] |
空字典 | {} |
空集合 | set() |
-
while、for循环,in,range(),break,continue
range()生成自然数序列
while | 循环 | i=1 while i<4: print('我还在循环') i+=1 |
for | 循环 | for i in range(1,4): print(i) |
break | 跳出循环 | for i in range(1, 5): if i==3: break print(i) i += 1 |
continue | 跳出本次循环,进入下一次迭代开始 | for i in range(1, 5): if i==3: continue print(i) i += 1 |
-
函数
如果有些代码要被复用,可用函数组织这些代码。
-
定义函数
def functionname(arg1,arg2,arg3=True,*args):
pass
args3=True是给这个参数赋一个默认值,如果调用函数时不传入该参数的值,那该参数就采用该默认值
*args将一组可变数量的位置参数集合成参数值的元组
-
调用函数
使用位置参数:functionname(arg1_value,arg2_value)
关键字参数:functionname(arg1=arg1_value,arg2=arg2_value)
-
try、except
sts=['Something']
try:
sts.append('like')
print(sts)
except AttributeError as atr:
print(atr)
-
模块调用、对象、类、继承、覆盖
1)调用。需先导入,才能调用
import A:导入模块A
from A import function1:调用模块A中的方法
2)类、对象
class className1():
def functionName1(self):#self参数指向了正在被创建对象的本身
print("这是一个方法")
print('这是一个类')
className_1=className1()
className_1.functionName1()
class className1():
def __init__(self,name):#__init__根据类的定义创建实例对象,self参数指向了正在被创建对象的本身
self.name=name
def functionName1(self):#self参数指向了正在被创建对象的本身
print("这是一个方法")
print(self.name)
print('这是一个类')
className_1=className1('pfl')
className_1.functionName1()
-
继承
B类继承了A类,那么B类中就有了A类中的属性。可以通过B类的实例化对象直接调用A类中定义的属性。
class className1():
def __init__(self,name):#__init__根据类的定义创建实例对象,self参数指向了正在被创建对象的本身
self.name=name
def functionName1(self):#self参数指向了正在被创建对象的本身
print("这是一个方法")
print(self.name)
print('这是一个类')
class class_empty(className1):
pass
class_empty_1=class_empty('pfl')
class_empty_1.functionName1()
-
覆盖(override)
class className1():
def __init__(self,name):#__init__根据类的定义创建实例对象,self参数指向了正在被创建对象的本身
self.name=name
def functionName1(self):#self参数指向了正在被创建对象的本身
print("这是一个方法")
print(self.name)
print('这是一个类')
class class_empty(className1):
def functionName1(self):
print('这是子类覆盖的方法')
class_empty_1=class_empty('pfl')
class_empty_1.functionName1()
className_1=className1('pfl')
className_1.functionName1()
-
格式化
%s:字符串
%d:十进制整数
%f:十进制浮点数
{}和format():‘{顺序值}’.format(arg),如果有多个参数,可以根据顺序值决定插入的位置。
a='you'
b='me'
c='him'
print('%s and %s talk about %s'%(a,b,c))
print('{2} and {1} talk about {0}'.format(c,b,a))
-
正则表达式匹配(后续讲断言的时候再细讲)
需要用到re模块。
1)匹配
match(pattern,source):在source中匹配pattern,需要source以pattern为开始
compile(pattern):对于复杂的匹配,可用compile先对pattern进行编译以加快匹配速度
search():匹配source中任意一位置的pattern,返回第一次成功的匹配
findall():如果source中存在pattern,会返回所有不重叠的pattern,返回结果是一个包含所有匹配的元组的列表
sub(pattern,replacement,source):将source中的pattern替换为replacement。会新创建一个对象,不改变原source。
group(),groups():使用match()和search()匹配时,会以group()形式返回。用group()获取第一个匹配值,使用groups()获取所有匹配值。group(0)是所有,group(1)是拿到第一组。
str1='you\'re a pretty girl,so that I miss you everday.'
str2='Would you like to travel around the world with me? '
m1=re.match('you',str1)
m2=re.match('you',str2)
print(m1.group())
if m2:
print(m2.group())
else:
print('str2中未match到you')
m3=re.search('you',str2)
print(m3.group())
#compile
youPattern=re.compile('you')
m4=youPattern.search(str2)
print(m4.group())
#findall
m5=youPattern.findall(str1+str2)
print(m5)
#sub
m6=re.sub('I','he',str1+str2)
print(m6)
2)模式
特殊字符 | |
模式 | 匹配 |
\d | 一个数字字符 |
\D | 一个非数字字符 |
\w | 一个字母或数字字符、下划线 |
\W | 一个非字母非数字字符、非下划线 |
\s | 空白符 |
\S | 非空白符 |
模式标识符 | |
模式 | 匹配 |
abc | 文本值abc |
expr1|expr2 | expr1或expr2 |
. | 除\n外的任何字符 |
^ | 源字符串的开头 |
$ | 源字符串的结尾 |
prev? | 0个或多个prev |
prev* | 0个或多个prev,尽可能多地匹配 |
prev*? | 0个或多个prev,尽可能少地匹配 |
prev+ | 1个或多个prev,尽可能多地匹配 |
prev+? | 1个或多个prev,尽可能少地匹配 |
prev{m} | m个连续的prev |
prev{m,n} | m到n个连续的prev |
[abc] | a或b或c |
[^abc] | 非a且非b且非c |
[例]
str3='我在一个叫"Madrid"的地方,来了1天了'
pattern=re.compile('[^0-9a-z]+')
m=pattern.findall(str3)
print(m)
【练习】
1.获取到了3个字段的提示信息:info1='"文本1"未填写',info2='"单人力1"未填写'。(其中“”中的是字段名,字段名除数字外的是字段类型,未填写是该字段的提示内容)
-
获取字段类型和提示内容,以字段类型为键、提示内容为字典创建一个字典dictInfo。
参考:
匹配以分组的形式:p=re.compile(r'"(.*?)(\d+)"(.*)')
p=re.compile(r'"(\D+)')
匹配不以分组的形式:p=re.compile('[^"0-9]+')
1.3 获取当前时间
from datetime import datetime
datetime.now()
#得到的数据格式为:'2021-05-26 17:22:000000'
1.4 时间、时间戳转换
时间格式化为字符串(以datetime.now()为例):
time_str=datetime.now().strftime('%Y-%m-%d% H:%M:%S')
字符串转换为时间戳:time_s=int(time.mktime(time.strptime(startData, "%Y-%m-%d %H:%M:%S"))) #单位是秒
time_ms=time_s*1000 #单位是毫秒
时间戳转换为时间:time.localtime(float(time_ms/1000)))
日期时间直接转为时间戳:datetime.now().timestamp() * 1000
1.4.1 时间比较
#方式一:datetime类型直接相减
#获取当前年份总天数,以及总秒数
today=datetime.datetime.today()#获取当前时间
sum=0
for i in range(1,13):
day1, ndays=calendar.monthrange(today.year,i)
sum+=ndays
year_seconds =sum*24*60*60
#计算时间差
res_entryTime=1620316800000#从接口获取到的时间戳
entry_time = datetime.datetime.fromtimestamp(float(res_entryTime / 1000))# 将时间戳转成datetime类型
now_time = datetime.datetime.today()
long=now_time-entry_time#datetime类型,两个时间相减后的数据(timedelta类型)
long_times=long.days*24*60*60+long.seconds#相差的秒数
#方式二:时间戳直接相减
res_entryTime = 1620316800000#从接口获取到的时间戳
today = datetime.datetime.today()#获取当前时间
now_int=int(time.mktime(time.strptime(now_time.strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")))*1000
star_int=int(time.mktime(time.strptime(str(today.year)+'-01-01 00:00:00', "%Y-%m-%d %H:%M:%S")))*1000
end_int=int(time.mktime(time.strptime(str(today.year+1)+'-01-01 00:00:00', "%Y-%m-%d %H:%M:%S")))*1000
year_int=end_int-star_int#当前年份一整年相差的时长(毫秒级)
long_int=now_int-res_entryTime#当前时间与res_entryTime相差的时长(毫秒级)
1.5 中文编码处理
xxx.encode('utf-8').decode('latin1')
1.6 字典&字符串转换
dictinfo = json.loads(json_str)输出dict类型
jsoninfo = json.dumps(dict)输出str类型
urlencode(dict) 字典转url表单键值对编码格式
1.7 数据反射
setattr(对象,属性名,属性值),设置属性值
应用场景:
用例1:新建日报,会产生一个唯一的blogid
用例2:编辑日报时需要编辑 用例1新建日报,可以在用例1 中进行参数blogid反射到类属性中
p = Person("laowang")
setattr(p,"talk",abc) # 将abc函数添加到对象中p中,并命名为talk
p.talk(p) # 调用talk方法,因为这是额外添加的方法,需手动传入对象p.talk(p)
1.9 数值区间
field1='gt'
field2='lgeq'
start='1'
end='3'
level=[]
li_1=['gt','gteq','lg','lgeq']
li_2=[False,True,False,True]
level.append(li_2[li_1.index(field1)])
level.append(float(start))
level.append(float(end))
level.append(li_2[li_1.index(field2)])
data=1
s=Interval(level[1],level[2],lower_closed=level[0],upper_closed=level[3])#创建数值区间
bl=data in s
1.11 可变参数
注意看下各项数据是否为同一个值,如果是,参数只要设置一个即可
接口方法参数不宜过多,如果数量过多则考虑命名可变参数(**kwargs)
*args:可称非键值可变参数、未命名可变参数
-
传递一个非键值变长参数列表
-
关键是*,不是args,args可替换成其他名称。
-
解包赋值给位置参数。如果可变参数(args)之前有位置参数,解包参数会优先赋值给位置参数,剩余的变量赋值给可变参数
**kwargs
-
传递关键字变长参数
-
关键是*,不是args,args可替换成其他名称。
-
解包赋值给关键字参数
-
传递方式:
参数名=参数值(attrKey=value)
或**字典(**{attrKey:value})
1.12 继承、组合
-
出现的原因:为了代码复用
-
继承:类与类之间有共同的属性,那就可以把这些属性提取出来作为基类的属性,基类即成为父类,原来的类(亦可称派生类)继承该基类。其他特有属性可在子类中声明。子类中可重写父类方法。
电脑分为台式机和笔记本。台式机和笔记本都有主板、CPU、显卡、内存条、硬盘、风扇。
电脑成为父类,电脑的属性就有主板、CPU、显卡、内存条、硬盘、风扇
台式机为子类,继承电脑,也就继承了电脑的属性,也有主板、CPU、显卡、内存条、硬盘、风扇
笔记本为子类,继承电脑,也就继承了电脑的属性,也有主板、CPU、显卡、内存条、硬盘、风扇
但笔记本属性有差异。比如CPU的架构、接口、引脚有差异,故在笔记本类中可重写CPU。台式机同理。
-
组合:在一个类中将另一个类的对象作为属性。
电脑由主板、CPU、显卡、内存条、硬盘、风扇组成。这些配件又分别有自己的元件,那么这些配件可分别成类。
主板类的属性有:BIOS芯片、I/O控制芯片、直流电源供电插件、指示灯插件、控制开关接口、扩充卡槽等。其他配件同理。
电脑类和主板类则是组合关系。
1.13关于执行速度
-
方法参数不宜过多
-
for循环执行相对慢,尽量不要多层嵌套,如果需要多层嵌套,一层嵌套中每次迭代不互相影响的情况下可考虑加线程并行运行(速度会大大提高)
-
字典、列表更新,尽量用内置函数。比如update、extend
-
可了解下:update’, ‘字典构造器-关键字参数’, ‘chain’, ‘元素拼接’, ‘推导式’,'for循环', ‘ChainMap’
-
可了解下:time.perf_counter()
-
selenium
-
xpath选择器---基础知识点,简单应用,布置任务
-
-
绝对路径选择、相对路径选择、指定标签名,通配符
绝对路径(/):从根节点开始的,到某个节点,每层都依次写下来,每层之间用 / 分隔的表达式
相对路径(//):不管在什么位置
指定标签名:所果要查找所有的div,可用//div
通配符(*):如果要查找所有的节点,不管什么类型,可用//*
-
根据属性选择、属性值包含字符串
[@属性名="属性值“]
[contains(@属性名,"属性值")]
*其中text()特殊
[text()="属性值“]
-
(By.XPATH,'//input[@weid="f56o40_jom1so_i8bhs1_9w0hkp_ymnnrt_m2qt0r_sdj4yn_ay5iow_7lhf0u_98yaqr@0_htfv0m@0_nf78ld@0_zggflt_ud12pe@typeName"]')按次序选择
//ul/li[2]:所有的ul下层第2个li
(//ul/li)[2]:所有的ul下层li的第2个
-
组选择、父节点、兄弟节点
组选择(|):A|B,既有A的结果,又有B的结果
父节点(/..):A/..,A结果的上一层
后兄弟节点或者说弟弟节点(/following-sibling::),A/following-sibling::div,表示A的后兄弟结点且是div类型
前兄弟节点或者说哥哥节点(/preceding-sibling::)
-
-
WebDriverWait、implicitly_wait、sleep
-
-
sleep(睡眠)
from time import sleep
sleep(秒数)
直接睡眠秒数时长
-
implicitly_wait(隐式等待)
driver.implicitly_wait(秒数)---》由于我们写代码时的框架,需写成:self.driver.implicitly_wait
若接下来要查找的元素未找到则等待,若已找到则程序往下继续执行,最大等待时长为设置的秒数
-
WebDriverWait(显示等待)
wait=WebDriverWait(driver,秒数)---》由于我们写代码时的框架,需写成:wait=WebDriverWait(self.driver,秒数)
wait.until(expected_conditions.presence_of_element_located((By.XPATH,'具体的xpath')))
直到条件满足时停止等待,最长等待时长不超过设置的秒数。
-
-
元素操作
-
查找元素
By类定位元素方式
点击
element.click()
输入
element.send_keys('')
移动
action_chains.move_by_offset(x, y).click().perform()
-
-
其他
-
冻结窗口:setTimeout(function(){debugger},times)
times的单位为毫秒。执行该语句,在times时间后会冻结该窗口。
直接执行javascript:driver.execute_script(js)
-
pycharm
-
部分快捷键、安装快捷键提示插件
-
复制一行代码到下一行:ctrl+D
排列代码格式:ctrl+alt+L
运行:shift+F10
调试:shift+F9
查找某个classes:ctrl+N
当前文件查的:ctrl+F
当前文件替换:ctrl+R
快速打开任何类、字段或函数:Ctrl+Alt+Shift+N
将两行合并为一行并删除不必要的空格:Ctrl+Shift+J
行注释或取消行注释:Ctrl + /
单步调试(一行一行走):F8
调试进入内部:F7
退出:Shift + F8
在当前行加上断点/断点开关:Ctrl + F8
查看所有断点:Ctrl + Shift + F8
-
-
调试
-
如何调试
异常名 | 释义 | 举例 |
NoSuchElementException | 没有找到元素 | |
NoSuchAttributeException | 属性错误 | |
lementNotVisibleException | 元素不可见 | |
NoSuchFrameException | 没有找到iframe | |
NoSuchWindowException | 没找到窗口句柄handle | |
NoAlertPresentException | 没找到alert弹出框 | |
ElementNotSelectableException | 元素没有被选中 | |
TimeoutException | 查找元素超时 | |
NameError | 属性名错误,该属性名未定义 | |
StaleElementRefrenceException | 元素不存在或元素已过期 |
3.3 pycharm使用方法
3.3.1正则
选中表达式,按ctrl+enter
-
unittest&pytest
4.1 用例名称命名
-
用例名称需以test_开头
单个运行:右键点击
(当用例名称以test_开头,才会识别为用例,右键菜单中会显示以unittest方式运行。非test_开头的则以纯python文件运行)
按规则匹配:运行runpytest.py
-
类名当以Test_开头时,不可设置__init__方法。
4.2 setUp和tearDown
setUpClass(cls)
类创建时执行,第一个要执行的用例执行之前执行。
方法前需加@classmethod以声明这是一个类方法。
tearDownClass(cls)
最后一个要执行的用例执行之后执行。
方法前需加@classmethod以声明这是一个类方法。
setUp(self)
每一个用例执行前执行。
tearDown(self)
每一个用例结束后执行。
5. yaml文件
读取文件:
with open('yaml文件路径', encoding='utf-8') as fp_stream:
'变量名' = yaml.load(fp_stream, Loader=yaml.FullLoader)
6.http常用请求方式
6.1 GET
用于使用给定的URI从给定服务器中检索信息,即从指定资源中请求数据。使用GET方法的请求应该只是检索数据,并且不应对数据产生其他影响。
6.2 HEAD
与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。
6.3 POST
用于将数据发送到服务器以创建或更新资源,它要求服务器确认请求中包含的内容作为由URI区分的Web资源的另一个下属。
POST请求永远不会被缓存,且对数据长度没有限制;我们无法从浏览器历史记录中查找到POST请求。
6.4 PUT
用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源
6.5 PATCH
是对PUT方法的补充,用来对已知资源进行局部更新
6.6 DELETE
用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容
6.7 OPTIONS
用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略。
6.8 TRACE
用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量。
6.9 CONNECT
用来建立到给定URI标识的服务器的隧道;它通过简单的TCP/IP隧道更改请求连接,通常使用解码的HTTP代理来进行SSL编码的通信(HTTPS)。
7.pyautogui
安装:pip install pyautogui
官方文档:https://pypi.org/project/PyAutoGUI/
我们目前常用:鼠标控制。坐标原点是屏幕最左上角的那个点。(selenium的坐标原点是网页最左上角的点)
position():获取当前鼠标位置。currentMouseX, currentMouseY = pyautogui.position()
moveTo(x,y,duration):移动到某个绝对位置。duration为执行此次动作设置时间,单位为秒。
moveRel(xOffset,yOffset,duration):相对于鼠标当前位置移动对应距离。
dragTo(x,y,button='left'):按下鼠标左键移动到某个绝对位置(也就是拖拽)。
dragRel (xOffset,yOffset,button='left'):按下鼠标左键相对于鼠标当前位置移动对应距离。
click(x,y,button):模拟点击(默认是左键)
实例:base.py中ele_dragRell()
8.RSA加密
关于密钥
对称加密:加密和解密时使用同一个密钥。
AES 、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6 、
非对称加密:加密和解密时使用不同的密钥(公钥、私钥)。
RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
公钥:公开的密钥
私钥:私有的密钥
加解密
.pem:PEM格式的文件
load_pkcs1_openssl_pem:加载符合PKCS标准的文件(.pem)
load_pkcs1_openssl_der:加载公钥原始二进制字节流
encrypt(arg1,arg2):arg1是待加密的数据,arg2是公钥
decrypt(arg1,arg2):arg1是待解密的数据,arg2是私钥
其他:
encode():编码。将str类型转为bytes类型
decode():解码。将 bytes 类型的二进制数据转换为 str 类型
9.cmd执行命令
格式:命令名 参数 参数
例如 cmd中执行命令 python runtest.py test_fna_AA_findAmountDigits test_fna_AB_saveAmountDigits
除了命令python外,其他都是参数,参数是以空格分隔的
sys.argv得到的是参数list。
sys.argv[0]是文件名runtest.py
sys.argv[1]是用例名test_fna_AA_findAmountDigits
-
框架相关知识
unittest
简介
单元测试框架,一个测试用例是一个独立的测试单元。
测试与报告框架互相独立。
部分:
test fixture(测试固定配置):本测试模块的配置,setUp_class,tearDown_class,setup,tearDown
test case(测试用例): unittest 提供一个基类: TestCase ,用于新建测试用例
test suite(测试套件):多个测试用例的集合。
test runner(测试运行器):一个用于执行和输出测试结果的组件。
本模块直接运行测试
class TestA(unittest.TestCase):
def test_A(self):
print('这是第一个测试用例')
def test_B(self):
print('这是第二个测试用例')
if __name__ == '__main__':
unittest.main()
unittest.main(): 执行用例。实际走的是testRunner.run()
自定义测试套件之addTest
class TestA(unittest.TestCase):
def test_A(self):
print('这是第三个测试用例')
def test_B(self):
print('这是第四个测试用例')
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(TestA('test_A'))
suite.addTest(TestA('test_B'))
runner = unittest.TextTestRunner()
runner.run(suite)
unittest.TestSuite(): 生成一个测试套件
addTest(类名(方法名)):往测试套件里添加测试用例
自定义测试套件之discover
class TestA(unittest.TestCase):
def test_A(self):
print('这是test_suite_discover第一个测试用例')
def test_B(self):
print('这是test_suite_discover第二个测试用例')
if __name__ == '__main__':
path=os.path.join(os.getcwd())
all=unittest.defaultTestLoader.discover(path,pattern='test_suite_discover.py')
# all=unittest.defaultTestLoader.discover('./',pattern='test_suite_discover.py')
runner = unittest.TextTestRunner()
runner.run(all)
unittest.defaultTestLoader.discover(path,pattern): 自动发现并加载用例为一个测试套件
path:要运行的用例所在目录。可用绝对路径或相对路径
pattern:匹配模式
Unittest vs Pytest
pytest官方中文版:Pytest权威教程(官方教程翻译) - 韩志超 - 博客园
unittest | pytest | |
用例编写规则 | 1)测试文件必须先import unittest 2)测试类必须继承unittest.TestCase 3)测试方法必须以“test_”开头 4)测试类必须要有unittest.main()方法 | 1)测试文件名必须以“test_”开头或者"_test"结尾(如:test_ab.py) 2)测试方法必须以“test_”开头 3)测试类命名以"Test"开头 |
用例分类执行 | 默认执行全部用例,也可以通过加载testsuit,执行部分用例 | 可以通过@pytest.mark来标记类和方法,pytest.main加入参数("-m")可以只运行标记的类和方法 |
用例前置和后置 | 提供了setUp/tearDown,只能针对所有用例 | pytest中的fixture显然更加灵活。可以任意自定义方法函数,只要加上@pytest.fixture()这个装饰器,那么被装饰的方法就可以被使用 |
参数化 | 需依赖ddt库 | 使用@pytest.mark.parametrize装饰器 |
断言 | 很多断言格式(assertEqual、assertIn、assertTrue、assertFalse) | 只有assert一个表达式,用起来比较方便 |
报告 | 使用HTMLTestRunnerNew库 | 有pytest-HTML、allure插件 |
失败重跑 | 无此功能 | pytest支持用例执行失败重跑,pytest-rerunfailures插件 |
2、Pytest全局用例共用之conftest.py详解
一、conftest特点:
1、可以跨.py文件调用,有多个.py文件调用时,可让conftest.py只调用了一次fixture,或调用多次fixture
2、conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
3、不需要import导入 conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了,如果放到某个package下,那就在改package内有效,可有多个conftest.py
4、conftest.py配置脚本名称是固定的,不能改名称
5、conftest.py文件不能被其他文件导入
6、所有同目录测试文件运行前都会执行conftest.py文件
二、conftest用法:
conftest文件实际应用需要结合fixture来使用,fixture中参数scope也适用conftest中fixture的特性,这里再说明一下
1、fixture
fixture(scope='function',params=None,autouse=False,ids=None,name=None):
fixture里面有个scope参数可以控制fixture的作用范围,scope:有四个级别参数"function"(默认),"class","module","session
params:一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。
autouse:如果True,则为所有测试激活fixture func可以看到它。如果为False则显示需要参考来激活fixture
ids:每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成
name:fixture的名称。这默认为装饰函数的名称。如果fixture在定义它的统一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽,解决这个问题的一种方法时将装饰函数命令"fixture_<fixturename>"然后使用"@pytest.fixture(name='<fixturename>')"。
2、fixture的作用范围
fixture里面有个scope参数可以控制fixture的作用范围:session>module>class>function
-function:每一个函数或方法都会调用
-class:每一个类调用一次,一个类中可以有多个方法
-module:每一个.py文件调用一次,该文件内又有多个function和class
-session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
function默认模式@pytest.fixture(scope='function')或 @pytest.fixture()
3、conftest结合fixture的使用
conftest中fixture的scope参数为session,所有测试.py文件执行前执行一次
conftest中fixture的scope参数为module,每一个测试.py文件执行前都会执行一次conftest文件中的fixture
conftest中fixture的scope参数为class,每一个测试文件中的测试类执行前都会执行一次conftest文件中的fixture
conftest中fixture的scope参数为function,所有文件的测试用例执行前都会执行一次conftest文件中的fixture
三、conftest应用场景
1、每个接口需共用到的token
2、每个接口需共用到的测试用例数据
3、每个接口需共用到的配置信息
四、安装依赖包问题
1.无法安装上依赖包
重新安装pip:easy_install.exe pip
再重新安装依赖包
2.出现权限问题,检查path,用管理员运行
五、常见问题
-
JSONDecodeError
原因:接口返回信息response有问题
处理:
-
debug查看response返回数据 ,确认是否有'text'字段。
-
排查为什么response有问题
-
检查url(post请求时,注意要有eteamsid)
-
检查headers中的请求格式是否正确(Content-Type)
-
检查data(即payload)数据是否正确(格式、内容,引号嵌套是否正确)
-
UnicodeEncodeError: 'latin-1' codec can't encode character '\u7b2c'
原因:请求时,数据存在中文
处理:payload.encode('utf-8').decode('latin1')
-
用例不运行
原因:用例名称未以test_开关
-
用例顺序不对
原因:用例运行根据用例文件名及用例名称,逐个字符判断大小,从小的开始运行
-
字典键错误、类型错误、list取值错误
排查:(1)检查response数据格式,与自己取值的格式是否一致