一、python介绍
Python是当前测试工程师较常用的一门编程语言,相对于Java等其他语言,它更加的灵活易学,没有Java那样复杂固定的书写规范,也不像C一样考验智商。除此以外,Python庞大的第三方库让我们可以快速拿别人已经封装好的东西来用,比如比较出名的pytest测试框架、unittest测试框架、selenium、各种加解密方法等,个人觉得是比较适合测试工程师的一门语言。
1.1优缺点
优点:
- 简单易学、能够快速地编写一些脚本来满足使用需求
- 第三方库丰富,直接当个调包仔也能写出好用的代码
- 在人工智能和数据分析方面好用,如Tensorflow等训练模型、pandas库,都极大的简便了数据分析与机器学习的开发(虽然我从来不用)
缺点:
- python是解释性语言,性能相较其他语言比较差
- 虽然变量不用强制定义数据类型,但是有时也会因为这个问题而出bug
- Python存在GIL锁,使得他在同一时间只会有一个线程拿到控制权,这样虽然避免了多线程的数据争夺问题,但是也使得python的多线程并不是真正的多线程,在IO密集型任务时,由于IO存在时间开销,多线程还能够起到节省时间的作用,但是在面对CPU密集型任务时,由于同一时间只会有一个线程拿到控制权,并不能起到节省时间的作用,反而还增加了线程切换的时间开销
1.2安装
参考以下链接:
安装python详细步骤(超详细,保姆级,一步一图)_python安装-CSDN博客
IDE有很多:pycharm、vscode、jupyter……选择自己喜欢的就好了
1.3能做什么
对于测试来说,python还是一项很有用的技能,能够做以下很多事情
- 已经被人说烂了的UI自动化、接口自动化框架
- 当面对涉及加解密的需求时,可以使用python编写脚本进行测试
- 爬虫,快速爬取网站数据(爬取数据不要商用,容易进去喝茶)
- 各种提效用的小工具(例如我以前写过自动生成日报、webhook推送等脚本)
- 更高级的有测试平台,不过大多是好看不好用
二、常用基础
2.1数据类型
一般数据类型
int:整形,例如1、2、-1等数字
float:浮点数,也叫小数,1.5、2.55等数字
str:字符串,用双引号或单引号括起来,如’abc123’的字符,要注意’123’这种也算字符串,还可以使用’’’123abc’’’的方式表示字符串,这种方式和单个引号的区别在于,三个引号可以把多行的内容都表示为字符串,单个引号只能表示单行
bool:布尔值,对应值为True和False,用于判断的时候用的比较多,也可以用0或者负数代表False,正数代表True
None:一般代表空
complex:复数,一般用不到,我也没有怎么用过
序列数据类型
序列数据类型有几个特点:
1、他们是可以互相嵌套的,也就是说一个列表里可能放了几个字典、这几个字典里又嵌套几个集合,集合里又放着几个列表
2、序列可以存放任意长度、任意类型的数据
3、可以通过循环的方式按顺序读取序列内的数据
list:列表,用[1,2,3]的方式表示一个列表
dict:字典,很好用的一种数据类型,用{“key”:”value”,”key2”:”value2”}的方式来表示一个字典,要取对应value数据时,通过对应的key名进行访问就可以了,例如value = dict[“key”],虽然看起来和json一样,但是从根本上来说他们是两种数据类型,是不同的
set:集合,用var = set(obj)的方式定义一个集合,集合的特点是自动去重,会把原有obj内相同的元素去除,obj一般传列表或元组等序列,以{1,2}的方式显示
tuple:元组,用(1,2,3)的方式表示一个元组,元组的特点在于元组内的元素不允许更改
range:表示一个数字范围,如range(0,10),代表0到9,常用于数字迭代,本质上是一个迭代器
2.2基础语法
1、python对于缩进有严格的校验,通过缩进来控制代码执行域,可以参考以下gpt生成的代码
global scope (全局作用域)
│
├── if statement (条件语句)
│ ├── indented block (缩进块)
│ │ ├── code 1
│ │ └── code 2
│ └── else block (else 块)
│ ├── code 3
│ └── code 4
│
├── for loop (循环语句)
│ ├── indented block (缩进块)
│ │ ├── code 5
│ │ └── code 6
│ └── else block (else 块)
│ ├── code 7
│ └── code 8
│
└── function definition (函数定义)
├── indented block (缩进块)
│ ├── code 9
│ └── code 10
└── return statement (返回语句)
└── code 11
2、全局变量与局部变量以及执行顺序
如下图所示,在全局作用域定义的变量是可以被直接使用的,因此func函数中直接print(var)可以执行,但是局部变量只能作用于局部的作用域内,因此在全局作用域内print(var2)或者在错误的局部作用域内print(var4)都会报错,而在正确的作用域内pirnt(var2)可以执行,而var3由于定义的顺序在func函数后,因此是找不到var3变量的,所以print(var3)时会报错
var = 'a' #全局变量
def fun():
var4 = 'd'
def func():
var2 = 'b' #局部变量
print(var) #可以访问到外面的var
print(var2) #可以访问到局部变量
print(var3) #var3此时还没定义,会报错
print(var4) #访问不到fun函数内部的var4,会报错
var3 = 'c'
print(var2) #var2是个局部变量,访问不到var2,会报错
关于全局变量和私有变量还有一个内容要讲,可以看下面的代码
可以看到,一开始定义了x=20,然后在函数内部先print了x,这个时候输出的x,访问的是全局的变量,因此输出20
紧接着又在下一句声明了x=10,这时候声明的x,由于是在函数内部声明的,因此是一个局部变量,所以在下一个print中,访问的是在函数内部定义的局部变量x,输出了10
最后的一个print,由于访问不到函数内部的局部变量,因此只能访问全局变量,输出20
x = 20 #全局变量
def func():
print(x) #输出=20,访问的是全局变量
x = 10 #局部变量
print(x) #输出=10,访问的是私有变量
print(x) #输出=20
但是我们可以换一种写法,让函数内部也能跟全局作用域共同操作一个变量。可以看到,我们在声明x=30前,先写了一行代码:global x,这行代码的意思是声明接下来在这段作用域里,名字为x的变量操作的是全局变量,而不是局部变量,因此当声明x=30的时候,实际上是把全局作用域的x声明值=30,所以两个print都输出了30
x = 20 #全局变量
def func():
global x #声明x是全局变量
x = 30 #声明x的值=30
print(x) #输出30,访问的是全局变量
print(x) #输出30,访问的是全局变量
3、关键字与变量命名规则
变量命名的规则:变量名称只能包含字母(大小写均可)、数字和下划线(_)、不能以数字开头、不能包含空格或其他特殊字符、变量名称区分大小写、不能使用关键字或者内置函数作为变量名。通常命名用以下三种格式:
1、下划线分割,如test_student_name
2、驼峰,如TestStudentName
3、小驼峰,如testStudentName
以下是python的一些关键字
布尔值 True -- 真 False -- 假 | 空值 None | 逻辑运算符 and -- 并 or -- 或 not -- 非 | 条件语句 if -- 条件语句中的条件判断 elif -- 条件语句中的多个条件判断 else -- 条件语句中的其他情况 | 循环语句 for -- 循环一个可迭代对象 while -- 用于循环执行一段代码,直到判断条件变为False break -- 强制结束循环 continue -- 跳过当前循环,继续下一次循环 |
异常处理 try -- 没遇到异常时,执行try作用域的代码 expect -- 当捕获指定的异常类型时,执行expect作用域的代码 finally -- 不管有没有异常,最后都执行finally作用域的代码块 raise -- 主动抛出一个异常 | 函数、类 def -- 定义函数 class -- 定义类 return -- 用于在函数中返回一个值 | 模块导入 import -- 导入某个包 from -- 从xx处导入 | 异步 async -- 声明一个异步函数 await -- 等待某个异步函数执行完毕 | 其他 global -- 声明一个全局变量 pass -- 什么也不做,可以理解为占位符 yield -- 用于生成器中,代表从生成器函数中生成一个值,暂停并保存当前状态。 lambda -- 声明一个匿名函数 with -- 不知道怎么解释比较好,可以理解为一个上下文管理器,当with中的代码执行完毕会自动关闭/释放资源,防止内存泄露,常见用法 with open(file) |
2.4运算符
算术运算符 + 加号 - 减号 * 乘号 / 除号 % 求余,例如11%10=1,11%3=2 ** 幂运算 ,例如2**3=8,3**3=27 // 除数取整,向下取整,例如13//2=6,14//5=2 | 比较运算符 > 大于 < 小于 == 等于 != 不等于 >= 大于等于 <= 小于等于 | 逻辑运算符 and 并且,如果and两边连接的两个条件都为真,返回True,否则返回False or 或者,如果or两边连接的其中一个条件为真就返回True,都为假返回False not 非,如果not连接的条件为假,返回True,否则返回False | 赋值运算符 赋值(=):将右侧的值赋给左侧的变量。 加法赋值(+=):将左侧变量的值加上右侧的值,并将结果赋给左侧变量。 减法赋值(-=):将左侧变量的值减去右侧的值,并将结果赋给左侧变量。 乘法赋值(*=):将左侧变量的值乘以右侧的值,并将结果赋给左侧变量。 除法赋值(/=):将左侧变量的值除以右侧的值,并将结果赋给左侧变量。 取余赋值(%=):将左侧变量的值除以右侧的值的余数,并将结果赋给左侧变量。 幂运算赋值(=)**:将左侧变量的值提升到右侧变量的次幂,并将结果赋给左侧变量。 取整除赋值(//=):将左侧变量的值除以右侧的值的整数部分,并将结果赋给左侧变量 |
2.5控制结构
1、条件
如下图代码,当if声明的条件满足if/elif时,就会执行对应作用域的代码,如果所有的if和elif都不满足就会执行else作用域的代码。需要注意的一点是,if中也可以继续嵌套if - elif - else,只需要注意缩进即可。
#声明if时,elif和else不是必须声明的,可以只声明一个if条件
var = 20
if var > 10:
print('我是if条件')
elif var > 5 and var <= 10:
print('我是elif条件1')
elif var == 4:
print('我是elif条件2')
else:
print('我是else条件')
if var >= 0:
print('我是else条件中嵌套的if')
else:
print('我是else条件中嵌套的else')
多个if和elif的区别:举个简单的例子,下面这段代码中,func1和func2在执行判断时其实起到了同样的效果,不过if-elif跟多个if判断的区别在于,func1中当判断到x>10的时候,就不会再去判断elif和else了,但是在func2中,第一次判断了x>10,还会再判断一次x是否大于5且小于10,这就造成了一次没有意义的性能开销,因此能用elif解决的尽量不要用多个if
x = 20
def func1():
global x
if x > 10:
pass
elif x < 10 and x > 5:
pass
else:
pass
def func2():
global x
if x > 10:
pass
if x < 10 and x > 5:
pass
2、循环
for循环:for循环会循环一个可迭代对象,直到遍历完对象中的所有元素。例如下面这段代码会按顺序输出0-99
#for循环对象必须是一个可迭代对象,否则会报错
for i in range(0,100):
print(i)
循环不同的对象时,取到的值是不同的
string = "abcdefg"
obj = {'key1':1,'key2':2,'key3':3}
li = [1,2,3]
for i in string:
print(i) #循环字符串时,会按顺序打印字符串的每一个字符
for i in obj:
print(i) #循环字典时,会按顺序拿到字典中的每一个key值
print(obj[i]) #可以用这种方式按顺序拿到每一个value
for i in li:
print(i) #循环列表/集合/元组时,会按顺序拿到每一个元素
while循环:while循环会在满足指定条件的情况下,一直重复执行作用域中的代码,例如以下这段代码,在变量x<10时,会循环将x+1并输出,直到x=10的时候,就不会再继续执行了
#要注意不要把条件写成永真造成死循环
x = 0
while x < 10:
x += 1
print(x)
结束/跳过循环
如下图代码,第一个循环会输出10-14,第二个循环会从10开始一直不停的加一输出,除了在x=15的时候会跳过一次循环不输出
break是强制结束循环,当x加到15 的时候,不管任何情况,直接结束循环,即使while是个永真循环也一样结束
continue是跳过本次循环,当x加到15的时候,不再执行本次循环剩下的代码,直接开始下次循环
x = 10
while 1:
x += 1
if x == 15:
break #x循环加到15的时候,break结束while循环
print(x)
while 1:
x += 1
if x == 15:
continue #x循环加到15的时候,跳过这次循环
print(x)
2.6导入
import
导入对象可以是一个文件,也可以是文件中的一个指定对象 例:import test_file / import test_file.test_obj
from
用 from 文件 import 对象的格式来书写,例:from test_file import test_obj
较常用的包:
requests -- 处理http请求
random -- 生成随机数
json -- 处理json格式数据
time -- 处理跟时间相关的数据
re -- 正则表达式
jsonpath -- 定位json中的数据
os -- 跟操作系统相关
yaml -- 处理yaml文件
openpyxl --处理excel文件
下载第三方包可以用下面这段代码,例如下载requests
pip install requests
在默认没有换源的情况下,很容易下载超时,建议换源后再下载,具体查看下面这个连接,有很多关于pip的操作
https://www.cnblogs.com/chenhuabin/p/10448116.html
2.7异常处理
只要try模块代码没有报错,那么except模块中的代码是不会执行的,但是如果发生了报错,会判断错误类型,去执行对应except模块中的代码。例如下图代码,try中先print了一段语句,然后主动抛出了一个AssertionError类型的异常,这个异常会被对应except捕获到然后执行对应代码,在代码中会再抛出一个异常,由于在except已经没有代码能捕获了,所以会报错。但是如果没有报错的话,就会去执行finally了
try:
print('我是try模块中的内容,正常执行的话只会执行try')
raise AssertionError
print('由于上面抛出了异常,所以不会执行这个print了')
except AssertionError as e:
print('发生了AssertionError类型异常,轮到我执行了')
print('我是报错信息:',e)
raise AssertionError #再发生AssertionError异常,不会再捕获了,所以会直接报错
except NameError:
print('抛出的是AssertionError异常,轮不到我执行')
finally:
print('正常来说不管有没有异常,我都要执行,如果上面的except不抛异常我就能执行了')
2.8文件操作
如下图代码所示,打开文件有两种方式,第一种是通过open函数赋值给一个变量,第二种是通过with open()的方式。两种方法的区别在于,with open中的代码执行完后,文件就自动关闭无法再使用了,但是如果通过赋值的方式,只要不主动关闭文件,就可以一直使用。
f = open('file_path',mode='r')
content = f.read()
f.close()
with open('file_path',mode='r') as f:
content = f.read()
文件模式
可以看到上面的代码中,有一个mode的参数,这代表打开文件后的操作模式
r:只读模式,以字符串的形式读取,常用于文件读取,如果文件不存在,报错
rb:只读模式,以二进制的形式读取,常用于图片读取,如果文件不存在,报错
w:只写模式,以字符串的形式写入,如果文件存在,清空文件内容。如果文件不存在,创建新文件
wb:只写模式,以二进制的形式写入,如果文件存在,清空文件内容。如果文件不存在,创建新文件
a:追加模式,以字符串的形式写入,如果文件存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件。不能读取文件
ab:追加模式,以二进制的形式写入,如果文件存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件。不能读取文件
r+:读写模式,以字符串的形式读写文件,文件指针将会放在文件的开头。如果文件不存在,报错
rb+:读写模式,以二进制的形式读写文件,文件指针将会放在文件的开头。如果文件不存在,报错
w+:写读模式,以字符串的形式读写文件,如果文件存在,清空文件内容。如果文件不存在,创建新文件
wb+:写读模式,以二进制的形式读写文件,如果文件存在,清空文件内容。如果文件不存在,创建新文件
a+:追加读模式,以字符串的形式写入,如果文件存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件。可以读取文件
ab+:追加读模式,以二进制的形式写入,如果文件存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件。可以读取文件
f = open('file_path',mode='a+')
content = f.read() #读取文件的全部内容
line_content = f.readline() #读取当前的内容,读取后指针会走向下一行
all_line_content = f.readlines() #读取所有行的内容,用列表返回
f.write('abc') #向文件写入内容
line = f.tell() #返回文件当前指针所在行
f.seek(10) #将文件指针移动到指定行
f.close() #关闭文件
还有一些关于文件的常用函数
2.9路径
在python中,有着绝对路径和相对路径两个概念
绝对路径是从文件系统的根目录(通常是硬盘的根目录)开始指定文件或目录的路径。它包含完整的路径信息,从根目录一直到目标文件或目录。绝对路径对于文件系统来说是唯一的,无论当前的工作目录在哪里,都可以精确定位到文件或目录。
例如:C:\Users\Username\Documents\example.txt
相对路径是相对于当前工作目录的路径。它描述了目标文件或目录与当前工作目录之间的位置关系。相对路径通常更简洁,但需要参考当前的工作目录来确定目标文件或目录的位置。
例如:如果当前处在Username目录下,想定位上面绝对路径的文件,那么可以写/Documents/example.txt
那么哪个路径比较好用?我个人更推荐使用相对路径,虽然使用绝对路径可以定位到文件,但是这个路径只在当前电脑适用,换一台电脑,就很可能会报错
三、语法糖
语法糖是指能让代码变得更简洁易读却没有额外功能的代码写法,它使得代码更加美观、易于理解,但实际上只是一种语法上的变形,不会改变底层的语言特性
比较经典的便是for循环,for循环的背后本质是迭代器实现的
还有三目运算符:x = 1 if True else 0,这行代码拆开来就是
if True:
x = 1
else:
x = 0
他甚至可以写的更长,例如: x = 1 if a ==0 else 2 if a == 1 else 3 if a == 2 else .......
翻译过来就是如果a==0那么设置x=1否则(如果a==1那么x=2否则(如果a==2那么x=3否则......))是可以无限嵌套的表达式
列表生成式:用[x for x in 迭代对象]的形式书写,可以参考下面的代码
#要注意,列表生成式一定是用[]括起来的,如果用()括起来,生成的就不是一个列表,而是一个生成器
obj = {'key1':1,'key2':2,'key3':3}
key_list = [x for x in obj] #所有的key
value_list = [obj[x] for x in obj] #所有的value
#如果想在列表生成式里对循环迭代的x进行一些操作,比如求平方,也可以这么写
value_list2 = [obj[x]**2 for x in obj]
#把key_list的列表生成式拆开来就是下面这段代码
empty_list = []
for x in obj:
empty_list.append(x)
lambda表达式:格式大概是这样的。
number = [1,2,3]
test = lambda x:x*2
print(test(number)) #输出[1,2,3,1,2,3]
print([test(numbers) for numbers in number]) #输出[2,4,6]
关于lambda表达式我用的很少,不是很清楚其他的用法。不过看起来lambda表达式一般是想快速定义一个固定、简单的函数,却又不想用def的方法来写的时候用的。
四、建议
在编程语言里,python算是相对容易入门的语言,学习难度较低。在学习了基础的语法知识后,可以试着做一些练习题来巩固。可以找一些做题网站例如
Learn Python - Free Interactive Python Tutorial
也可以试着做鸡兔同笼的一些简单脚本。或者根据自己的需要,开发一些实用的脚本也是很好的一种方式。
个人觉得对于初学者来说有一些容易踩坑的地方,把这些地方给搞明白了对于接下来的学习是很有用的。
1、各种数据类型的应用以及数据类型之间的转换
2、全局变量/局部变量