5 Python 编程语言与测试框架
| 名称 | 相关知识点 |
|---|---|
| 5.1 Python环境搭建、多版本安装 | Mac/Windows环境搭建、IDE日常使用 |
| 5.2 基本数据类型与操作 | python 的数字、字符串、列表的使用 |
| 5.3 控制流语法 | 条件、循环等表达式与流程控制 |
| 5.4 常用数据结构 | 列表、元组、集合、词典与常用便捷表达式 |
| 5.5 模块 | 项目目录结构、模块定义、文件引用 |
| 5.6 输入与输出 | 字面量打印与格式化、文件读取、json 格式转换 |
| 5.7 错误与异常 | 语法错误与定位、异常捕获、异常处理、自定义异常 |
| 5.8 面向对象编程 | 类定义、方法定义、类变量、实例引用、实例变量 |
| 5.9 标准库 | os 与文件处理、科学计算、网络访问、日期与时间等处理 |
| 5.10 多线程处理 | 进程与多线程处理,log 处理 |
| 5.11 第三方库 | pytest、requests |
| 5.12 pip 依赖管理与虚拟环境 | 第三方的依赖管理与项目管理 |
| 5.13 unittest | python 自带单元测试框架 |
| 5.14 pytest | python 最流行的全能型测试框架 |
5.2基本数据类型
Python有五个标准的数据类型:
- Numbers(数字)
- String(字符串)
- List(列表)
- Tuple(元组)
- Dictionary(字典)
不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组);
可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。
Python 数字
数字数据类型用于存储数值。不可改变的数据类型
Python支持四种不同的数字类型:
- int(有符号整型)
- long(长整型,也可以代表八进制和十六进制)
- float(浮点型)
- complex(复数)
注意: long 类型只存在于 Python2.X 版本中,在 2.2 以后的版本中,int 类型数据溢出后会自动转为long类型。在 Python3.X 版本中 long 类型被移除,使用 int 替代。
Python字符串
字符串或串(String)是由数字、字母、下划线组成的一串字符。
s = "a1a2···an" # n>=0
它是编程语言中表示文本的数据类型。
[头下标:尾下标] 获取的子字符串包含头下标的字符,但不包含尾下标的字符。
>>> s = 'abcdef'
>>> s[1:5]
'bcde'
Python列表
List(列表) 用 [ ] 标识,是 python 最通用的复合数据类型。
- List 写在方括号之间,元素用逗号隔开,可以是不同类型的元素,通过索引获取元素
- 和字符串一样,List 可以被索引和切片。
- List 可以使用 + 操作符进行拼接。
- List 中的元素是可以改变的。
- 列表可以作为一个堆栈来使用,后进先出,append() 方法可以把一个元素添加到堆栈顶, pop() 方法可以把一个元素从堆栈顶释放出来
list = [] ## 空列表
list.append('Google') ## 使用 append()整体追加,insert插入指定位置,extend列表添加到列表中
list1 = ['physics', 'chemistry', 1997, 2000]
del list1[2] #pop删除元素并返回该元素,remove根据元素值删除,如果值不存在就报错
Python包含以下函数:
| 序号 | 函数 |
|---|---|
| 1 | cmp(list1, list2) 比较两个列表的元素 |
| 2 | len(list) 列表元素个数 |
| 3 | max(list) 返回列表元素最大值 |
| 4 | min(list) 返回列表元素最小值 |
| 5 | list(seq) 将元组转换为列表 |
Python包含以下方法:
| 序号 | 方法 |
|---|---|
| 1 | list.append(obj) 在列表末尾添加新的对象 |
| 2 | list.count(obj) 统计某个元素在列表中出现的次数 |
| 3 | list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表) |
| 4 | list.index(obj) 从列表中找出某个值第一个匹配项的索引位置 |
| 5 | list.insert(index, obj) 将对象插入列表 |
| 6 | list.pop([index=-1]) 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 |
| 7 | list.remove(obj) 移除列表中某个值的第一个匹配项 |
| 8 | list.reverse() 反向列表中元素 |
| 9 | list.sort(cmp=None, key=None, reverse=False) 对原列表进行排序 |
Python 元组
元组用 () 标识。内部元素用逗号隔开。但是元组不能二次赋值,相当于只读列表。
- 与字符串一样,元组的元素不能修改。
- 元组也可以被索引和切片,方法都是一样的。
- 注意构造包含 0 或 1 个元素的元组的特殊语法规则。
- 元组也可以使用 + 操作符进行拼接。
- 元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组
- 对元组进行连接组合,如下实例:
tup1 = (12, 34.56)
tup2 = ('abc', 'xyz')
tup3 = tup1 + tup2
print tup3
删除元组
元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组
del tup
Python 字典
列表是有序的对象集合,字典是无序的对象集合。两者之间的区别在于:字典当中的元素是通过键来存取的,而不是通过偏移存取。字典用"{ }"标识。字典由索引(key)和它对应的值value组成。
- 键必须是唯一的,但值则不必,通过键获取元素
- 不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住
tinydict.clear() # 清空字典所有条目
del tinydict # 删除字典
Python数据类型转换
有时候,我们需要对数据内置的类型进行转换,数据类型的转换,你只需要将数据类型作为函数名即可。
以下几个内置的函数可以执行数据类型之间的转换。这些函数返回一个新的对象,表示转换的值。
| 函数 | 描述 |
|---|---|
| int(x [,base]) | 将x转换为一个整数 |
| float(x) | 将x转换到一个浮点数 |
| complex(real [,imag]) | 创建一个复数 |
| str(x) | 将对象 x 转换为字符串 |
| tuple(s) | 将序列 s 转换为一个元组 |
| list(s) | 将序列 s 转换为一个列表 |
| set(s) | 转换为可变集合 |
| dict(d) | 创建一个字典。d 必须是一个序列 (key,value)元组。 |
split 函数
split函数可以将字符串按照一定规则进行切割成列表,默认按照空格进行切割,如果字符串无空格则直接将这个字符串变为列表中的一个元素,还可以传入切割次数,默认-1无限制
str_01 = 'abc'
# 默认按照空格切割,无空格则整个转换为列表中的一个元素
print(str_01.split())
str_02 = 'a b c'
# 默认按照空格切割
print(str_02.split())
str_03 = 'a#b#c'
# 指定按照#切割
print(str_03.split('#'))
str_04 = 'stark0peter0strange0banner'
# 切割1次
print(str_04.split('0', 1))
str_05 = 'pc12138'
print(str_05.split(''))
复制代码
5.5模块
项目目录结构、模块定义
模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。
__name__属性
可以用__name__属性来使该程序块仅在该模块自身运行时执行。
#!/usr/bin/python3
# Filename: using_name.py
if __name__ == '__main__':
print('程序自身在运行')
else:
print('我来自另一模块')
运行输出如下:
$ python using_name.py
程序自身在运行
$ python
>>> import using_name
我来自另一模块
>>>
说明: 每个模块都有一个__name__属性,当其值是'main'时,表明该模块自身在运行,否则是被引入。
说明: name 与 main 底下是双下划线, _ _ 是这样去掉中间的那个空格。
文件引用
open() 方法
Python open() 方法用于打开一个文件,并返回文件对象。
注意: 使用 open() 方法一定要保证关闭文件对象,即调用 close() 方法。
open() 函数常用形式是接收两个参数:文件名(file)和模式(mode)。
完整的语法格式为:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
参数说明:
- file: 必需,文件路径(相对或者绝对路径)。
- mode: 可选,文件打开模式
- buffering: 设置缓冲
- encoding: 一般使用utf8
- errors: 报错级别
- newline: 区分换行符
- closefd: 传入的file参数类型
- opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。
mode 参数有:
| 模式 | 描述 |
|---|---|
| t | 文本模式 (默认)。 |
| x | 写模式,新建一个文件,如果该文件已存在则会报错。 |
| b | 二进制模式。 |
| + | 打开一个文件进行更新(可读可写)。 |
| U | 通用换行模式(Python 3 不支持)。 |
| r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
| rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
| r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
| rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
| w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
| wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
| w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
| wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
| a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
| ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
默认为文本模式,如果要以二进制模式打开,加上 b 。
file 对象
file 对象使用 open 函数来创建,下表列出了 file 对象常用的函数:
| 序号 | 方法及描述 |
|---|---|
| 1 | file.close()关闭文件。关闭后文件不能再进行读写操作。 |
| 2 | file.flush()刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入。 |
| 3 | file.fileno()返回一个整型的文件描述符(file descriptor FD 整型), 可以用在如os模块的read方法等一些底层操作上。 |
| 4 | file.isatty()如果文件连接到一个终端设备返回 True,否则返回 False。 |
| 5 | file.next()**Python 3 中的 File 对象不支持 next() 方法。**返回文件下一行。 |
| 6 | file.read([size])从文件读取指定的字节数,如果未给定或为负则读取所有。 |
| 7 | file.readline([size])读取整行,包括 "\n" 字符。 |
| 8 | file.readlines([sizeint])读取所有行并返回列表,若给定sizeint>0,返回总和大约为sizeint字节的行, 实际读取值可能比 sizeint 较大, 因为需要填充缓冲区。 |
| 9 | file.seek(offset[, whence])移动文件读取指针到指定位置 |
| 10 | file.tell()返回文件当前位置。 |
| 11 | file.truncate([size])从文件的首行首字符开始截断,截断文件为 size 个字符,无 size 表示从当前位置截断;截断之后后面的所有字符被删除,其中 windows 系统下的换行代表2个字符大小。 |
| 12 | file.write(str)将字符串写入文件,返回的是写入的字符长度。 |
| 13 | file.writelines(sequence)向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符。 |
5.6Json字符串转换
Python3 中可以使用 json 模块来对 JSON 数据进行编解码,它包含了两个函数:
- json.dumps(): 对数据进行编码。
- json.loads(): 对数据进行解码。
Python 编码为 JSON 类型转换对应表:
| Python | JSON |
|---|---|
| dict | object |
| list, tuple | array |
| str | string |
| int, float, int- & float-derived Enums | number |
| True | true |
| False | false |
| None | null |
JSON 解码为 Python 类型转换对应表:
| JSON | Python |
|---|---|
| object | dict |
| array | list |
| string | str |
| number (int) | int |
| number (real) | float |
| true | True |
| false | False |
| null | None |
执行以上代码输出结果为:
#!/usr/bin/python3
import json # Python 字典类型转换为 JSON 对象
data = {
'no' : 1,
'name' : 'Runoob',
'url' : 'http://www.runoob.com'
}
json_str = json.dumps(data)
print ("Python 原始数据:", repr(data))
print ("JSON 对象:", json_str)
Python 原始数据: {'url': 'http://www.runoob.com', 'no': 1, 'name': 'Runoob'}
JSON 对象: {"url": "http://www.runoob.com", "no": 1, "name": "Runoob"}
#!/usr/bin/python3
import json # Python 字典类型转换为 JSON 对象
data1 = {
'no' : 1,
'name' : 'Runoob',
'url' : 'http://www.runoob.com'
}
json_str = json.dumps(data1)
print ("Python 原始数据:", repr(data1))
print ("JSON 对象:", json_str)
# 将 JSON 对象转换为 Python 字典
data2 = json.loads(json_str)
print ("data2['name']: ", data2['name'])
print ("data2['url']: ", data2['url'])
Python 原始数据: {'name': 'Runoob', 'no': 1, 'url': 'http://www.runoob.com'}
JSON 对象: {"name": "Runoob", "no": 1, "url": "http://www.runoob.com"}
data2['name']: Runoob
data2['url']: http://www.runoob.com
5.7 错误与异常
-
首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)
-
如果没有异常发生,忽略 except 子句,try 子句执行后结束。
-
如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。
-
如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。最后执行 try 语句之后的代码。
-
如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。
-
一个 try 语句可能包含多个 except 子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。
-
可选的 else 子句,必须放在所有的 except 子句之后,这个子句将在 try 子句没有发生任何异常的时候执行
-
一个 except 子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组
except (RuntimeError, TypeError, NameError): pass -
try-finally 语句无论是否发生异常都将执行最后的代码。
常见异常
AttributeError,对象不含指定属性的异常
class Student:
name = None
age = 7
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return 'Student[name={}, age={}]'.format(self.name, self.age)
stu = Student("stark", 44)
print(stu)
print(stu.address)
KeyError,没有指定的键出现的异常
dict_01 = {
'name': 'stark',
'age': 44
}
print(dict_01['address'])
IndexError,数组下标异常
list_01 = [1, 3, 4, 9, 10]
list_01[10]
ValueError,参数值异常
name = 'stark'
print(int(name))
TypeError,参数类型异常
def add(x, y):
return x + y
add()
5.8面向对象
- 类:用来描述具有相同属性和方法的对象的集合,定义了结合中每个对象所共有的属性和方法,对象是类的实例
- 方法:类中定义的函数
- 实例变量:也叫对象变量,讲一个对象的改变,不改变其他对象的值
- 类变量:也叫静态变量,static修饰,类变量定义在类中且在函数体之外,其中一个对象将值改变,其他对象得到都是改变后的
class GirlFriend:
# 女朋友类
gender = "女"
# 对象方法(实例方法):
# 实例方法的定义:直接定义在类里面的函数
# 第一个参数是self,self代表的是对象本身
# 实例方法的调用:对象.方法名
def __init__(self, name, face, height, leg):# 初始化方法(对象方法)
self.name = name # 实列属性
self.face = face
self.height = height
self.leg = leg
# 对象.属性名 = 属性值
def skill1(self):
# print(self)
print("{} 在买东西".format(self.name))
def skill2(self):
print("看电视")
# 类方法:
# 类方法定义:要通过classmethod装饰器来声明一个类方法
# 第一个参数是cls,cls代表的是类本身
# 类方法的调用:类名.方法名
# 对象.方法名
@classmethod # 通过classmethod装饰器,声明一个类方法
def cls_func(cls):
#print(cls)
print("这个是类方法")
obj1 = GirlFriend('小花', "好看", 168, "一米")
obj2 = GirlFriend('小红', "很好看", 168, "一米一")
print("----------------------------")
# 类方法的调用
print(GirlFriend)
GirlFriend.cls_func()
obj1.cls_func()
print("----------------------------")
# 实例方法的调用
obj1.skill1()
# 类不能够去调用实例方法的
# GirlFriend.skill1()
- 方法重写:如果父类的功能无法满足需求,可以在子类中重写父类的方法
Class Parent:
def mythod(self):
print('调用父类方法')
Class Child(Parent):
def mythod(self):
print('调用子类方法')
c = Child()
c.mythod()
super(Child,c).mythod()
输出:
调用子类方法
调用父类方法
高级函数
str对象描述信息的定义函数
class Student():
def __init__(self, name):
self.name = name
# 定义实例化对象的描述信息
def __str__(self):
return 'Student[name={}]'.format(self.name)
def breath(self):
print('Student can breath')
if __name__ == '__main__':
stu = Student('子渊')
print(stu)
stu.breath()
getattr当调用属性或方法不存在时,返回函数中定义的信息
class Student():
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student[name={}]'.format(self.name)
def __getattr__(self, property):
print('{}不存在'.format(property))
def breath(self):
print('Student can breath')
if __name__ == '__main__':
stu = Student('子渊')
print(stu)
stu.breath()
stu.age
setattr拦截当前类中不存在的属性和值,给不存在的属性设置值
def __setattr__(self, key, value):
if key not in self.__dict__:
self.__dict__[key] = value
print('key={}, value={}'.format(key, value))
在main函数下增加代码
stu.like = 'lilith'
stu.like
5.9标准库
OS
| 序号 | 方法及描述 | |
|---|---|---|
| 1 | os.access(path, mode) 检验权限模式 | |
| 2 | os.chdir(path) 改变当前工作目录 | |
| 3 | os.chflags(path, flags) 设置路径的标记为数字标记。 | |
| 4 | os.chmod(path, mode) 更改权限 | |
| 5 | os.chown(path, uid, gid) 更改文件所有者 | |
| 6 | os.chroot(path) 改变当前进程的根目录 | |
| 7 | os.close(fd) 关闭文件描述符 fd | |
| 9 | os.dup(fd) 复制文件描述符 fd | |
| 10 | os.dup2(fd, fd2) 将一个文件描述符 fd 复制到另一个 fd2 | |
| 11 | os.fchdir(fd) 通过文件描述符改变当前工作目录 | |
| 12 | os.fchmod(fd, mode) 改变一个文件的访问权限,该文件由参数fd指定,参数mode是Unix下的文件访问权限。 | |
| 21 | os.getcwd() 返回当前工作目录 | |
| 33 | os.makedirs(path[, mode]) 递归文件夹创建函数。像mkdir(), 但创建的所有intermediate-level文件夹需要包含子文件夹。 | |
| 35 | os.mkdir(path[, mode]) 以数字mode的mode创建一个名为path的文件夹.默认的 mode 是 0777 (八进制)。 | |
| 36 | os.mkfifo(path[, mode]) 创建命名管道,mode 为数字,默认为 0666 (八进制) | |
| 38 | os.open(file, flags[, mode]) 打开一个文件,并且设置需要的打开选项,mode参数是可选的 | |
| 40 | os.pathconf(path, name) 返回相关文件的系统配置信息。 | |
| 41 | os.pipe() 创建一个管道. 返回一对文件描述符(r, w) 分别为读和写 | |
| 44 | os.readlink(path) 返回软链接所指向的文件 | |
| 45 | os.remove(path) 删除路径为path的文件。如果path 是一个文件夹,将抛出OSError; 查看下面的rmdir()删除一个 directory。 | |
| 46 | os.removedirs(path) 递归删除目录。 | |
| 47 | os.rename(src, dst) 重命名文件或目录,从 src 到 dst | |
| 48 | os.renames(old, new) 递归地对目录进行更名,也可以对文件进行更名。 | |
| 49 | os.rmdir(path) 删除path指定的空目录,如果目录非空,则抛出一个OSError异常。 |
标准库概述
文件通配符
glob模块提供了一个函数用于从目录通配符搜索中生成文件列表
import glob
glob.glob('*.py')
输出:['primes.py','random.py','quote.py']
命令行参数
import sys
print(sys.argv)
输出:['demo.py','one','two']
正则匹配
import re
re.findall(r'\bf[a-z]*','which foot or hand fell faster')
输出:['foot','fell','faster']
数学
import math
math.cos(math.pi/4)
import random
random.randrange(6)
日期和时间
from datetime import date
now = date.today()
birthday = data(1964,7,31)
age = now -birthday
age.days
测试模块
import doctest
doctest.testmod() #自动验证嵌入测试
import unittest
class TestStaticalFunctions(unittest.TestCase):
def test_average(self):
self.assertEqual(average([20,30,70]),40.0)
self.assertEqual(round(average([20,30,70]),1),4.3)
self.assertRaises(ZeroDivisionError,average,[])
self.assertRaises(TypeError,average,20,30,70)
unittest.main()
5.10多线程处理
优点
- 使用线程可以把占据长时间的程序任务放到后台处理
- 用户界面更加吸引人,可以弹出进度条来显示处理的进度
- 程序运行速度可能加快
- 在一些等待的任务实现上,可以释放一些珍贵的资源如内存占用等等
python线程
_thread.start_new_thread ( function ,args[,kwargs])
function:线程函数
args:传递给线程函数的参数,必须是tuple
kwargs:可选参数
线程同步
5.11第三方库
requests
响应信息
| 属性或方法 | 说明 |
|---|---|
| apparent_encoding | 编码方式 |
| close() | 关闭与服务器的连接 |
| content | 返回响应的内容,以字节为单位 |
| cookies | 返回一个 CookieJar 对象,包含了从服务器发回的 cookie |
| encoding | 解码 r.text 的编码方式 |
| headers | 返回响应头,字典格式 |
| history | 返回包含请求历史的响应对象列表(url) |
| is_permanent_redirect | 如果响应是永久重定向的 url,则返回 True,否则返回 False |
| is_redirect | 如果响应被重定向,则返回 True,否则返回 False |
| iter_content() | 迭代响应 |
| json() | 返回结果的 JSON 对象 (结果需要以 JSON 格式编写的,否则会引发错误) |
| links | 返回响应的解析头链接 |
| next | 返回重定向链中下一个请求的 PreparedRequest 对象 |
| ok | 检查 "status_code" 的值,如果小于400,则返回 True,如果不小于 400,则返回 False |
| raise_for_status() | 如果发生错误,方法返回一个 HTTPError 对象 |
| reason | 响应状态的描述,比如 "Not Found" 或 "OK" |
| request | 返回请求此响应的请求对象 |
| status_code | 返回 http 的状态码,比如 404 和 200(200 是 OK,404 是 Not Found) |
| text | 返回响应的内容,unicode 类型数据 |
| url | 返回响应的 URL |
requests方法
requests 方法如下表:
| 方法 | 描述 |
|---|---|
| delete(url, args) | 发送 DELETE 请求到指定 url |
| get(url, params, args) | 发送 GET 请求到指定 url |
| head(url, args) | 发送 HEAD 请求到指定 url |
| patch(url, data, args) | 发送 PATCH 请求到指定 url |
| post(url, data, json, args) | 发送 POST 请求到指定 url |
| put(url, data, args) | 发送 PUT 请求到指定 url |
| request(method, url, args) | 向指定的 url 发送指定的请求方法 |
①使用 requests.request() 发送 get 请求:
# 导入 requests 包
import requests
# 发送请求
x = requests.request('get', 'https://www.runoob.com/')
# 返回网页内容
print(x.status_code)
输出结果如下:
200
②设置请求头:
import requests
kw = {'s':'python 教程'}
# 设置请求头
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
# params 接收一个字典或者字符串的查询参数,字典类型自动转换为url编码,不需要urlencode()
response = requests.get("https://www.runoob.com/", params = kw, headers = headers)
# 查看响应状态码
print (response.status_code)
# 查看响应头部字符编码
print (response.encoding)
# 查看完整url地址
print (response.url)
# 查看响应内容,response.text 返回的是Unicode格式的数据
print(response.text)
输出结果如下:
200
UTF-8
https://www.runoob.com/?s=python+%E6%95%99%E7%A8%8B
... 其他内容...
③post() 方法可以发送 POST 请求到指定 url,一般格式如下:
requests.post(url, data={key: value}, json={key: value}, args)
- url 请求 url。
- data 参数为要发送到指定 url 的字典、元组列表、字节或文件对象。
- json 参数为要发送到指定 url 的 JSON 对象。
- args 为其他参数,比如 cookies、headers、verify等。
# 导入 requests 包
import requests
# 发送请求
x = requests.post('https://www.runoob.com/try/ajax/demo_post.php')\
# 返回网页内容
print(x.text)
输出结果如下:
<p style='color:red;'>本内容是使用 POST 方法请求的。</p>
<p style='color:red;'>请求时间:
2022-05-26 17:30:47</p>
④post 请求带参数:
import requests
# 表单参数,参数名为 fname 和 lname
myobj = {'fname': 'RUNOOB','lname': 'Boy'}
# 发送请求
x = requests.post('https://www.runoob.com/try/ajax/demo_post2.php', data = myobj)
# 返回网页内容
print(x.text)
输出结果如下:
<p style='color:red;'>你好,RUNOOB Boy,今天过得怎么样?</p>
5.11unittest
unittest四个概念
- test case:就是我们的测试用例,unittest中提供了一个基本类TestCase,可以用来创建新的测试用例,一个TestCase的实例就是一个测试用例;unittest中测试用例方法都是以test开头的,且执行顺序会按照方法名的ASCII值排序。
- test fixure:测试夹具,用于测试用例环境的搭建和销毁。即用例测试前准备环境的搭建(SetUp前置条件),测试后环境的还原(TearDown后置条件),比如测试前需要登录获取token等就是测试用例需要的环境,运行完后执行下一个用例前需要还原环境,以免影响下一条用例的测试结果。
- test suite:测试套件,用来把需要一起执行的测试用例集中放到一块执行,相当于一个篮子。我们可以使用TestLoader来加载测试用例到测试套件中。
- test runner:用来执行测试用例的,并返回测试用例的执行结果。它还可以用图形或者文本接口,把返回的测试结果更形象的展现出来,如:HTMLTestRunner。
unittest断言
| 方法 | 检查 |
|---|---|
| assertEqual(a, b,msg=None) | 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 |
| assertIsNot(a, b) | a is not b |
| assertIsNone(x) | x is None |
| assertIsNotNone(x) | x is not None |
| assertIn(a, b) | a in b |
| assertNotIn(a, b) | a not in b |
| assertIsInstance(a, b) | isinstance(a,b) |
| assertNotIsInstance(a, b) | not isinstance(a,b) |
如果断言失败即不通过就会抛出一个AssertionError断言错误,成功则标识为通过,以上几种方式都有一个共同点,就是都有一个msg参数(表中只列了一个,其实都有),默认是None,即msg = None,如果指定msg参数的值,则将该信息作为失败的错误信息返回。
TestCase测试用例
在编写测试用例之前,需要新建一个测试类继承unittest的TestCase类
步骤
- 导入unittest模块
- 创建一个测试类,并继承
unittest.TestCase() - 定义测试方法,方法名必须以test_开头
- 调用
unittest.main()方法来运行测试用例,unittest.main()方法会搜索该模块下所有以test开头的测试用例方法,并自动执行
# register.py
users = [{'username': 'test', 'password': '123456'}]
def register(username, password1, password2):
if not all([username, password1, password2]):
return {"code": 0, "msg": "所有参数不能为空"}
# 注册功能
for user in users:
if username == user['username']:
return {"code": 0, "msg": "该用户名已存在!"}
else:
if password1 != password2:
return {"code": 0, "msg": "两次密码输入不一致!"}
else:
if 6 <= len(username) >= 6 and 6 <= len(password1) <= 18:
users.append({'username': username, 'password': password2})
return {"code": 1, "msg": "注册成功"}
else:
return {"code": 0, "msg": "用户名和密码必须在6-18位之间"}
# test_register.py
import unittest
from register import register # 导入被测试的代码
class TestRegister(unittest.TestCase):
"""注册接口测试用例类"""
def test_register_success(self):
"""注册成功"""
data = ("mikitest", "miki123", "miki123") # 测试数据
expected = {"code": 1, "msg": "注册成功"} # 预期结果
result = register(*data) # 把测试数据传到被测的代码,接收实际结果
self.assertEqual(expected, result) # 断言,预期和实际是否一致,一致即用例通过
def test_username_isnull(self):
"""注册失败-用户名为空"""
data = ("", "miki123", "miki123")
expected = {"code": 0, "msg": "所有参数不能为空"}
result = register(*data)
self.assertEqual(expected, result)
def test_username_lt6(self):
"""注册失败-用户名大于18位"""
data = ("mikitestmikitestmikitest", "miki123", "miki123")
expected = {"code": 0, "msg": "用户名和密码必须在6-18位之间!"}
result = register(*data)
self.assertEqual(expected, result) # 这条用例应该是不通过的,注册代码bug
def test_pwd1_not_pwd2(self):
"""注册失败-两次密码不一致"""
data = ("miki123", "test123", "test321")
expected = {"code": 0, "msg": "两次密码输入不一致!"}
result = register(*data)
self.assertEqual(expected, result)
# 如果直接运行这个文件,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':
unittest.main()
TestFixure测试夹具
class TestRegister(unittest.TestCase):
"""注册接口测试用例类"""
def setUp(self): # 每条用例执行之前都会执行
print("用例{}开始执行--".format(self))
def tearDown(self): # 每条用例执行之后都会执行
print("用例{}执行结束--".format(self))
@classmethod # 指明这是个类方法以类为维度去执行的
def setUpClass(cls): # 整个测试用例类中的用例执行之前,会先执行此方法
print("-----setup---class-----")
@classmethod
def tearDownClass(cls): # 整个测试用例类中的用例执行完之后,会执行此方法
print("-----teardown---class-----")
TestSuite测试套件
-
unittest.TestSuite()
addTest():添加单个测试用例方法addTest([..]):添加多个测试用例方法,方法名存在一个列表
-
unittest.TestLoader()
loadTestsFromTestCase(测试类名):添加一个测试类loadTestsFromModule(模块名):添加一个模块discover(测试用例的所在目录):指定目录去加载,会自动寻找这个目录下所有符合命名规则的测试用例
import unittest
import test_register
# 第一步,创建一个测试套件
suite = unittest.TestSuite()
# 第二步:将测试用例,加载到测试套件中
# 方式1,添加单条测试用例
case = test_register.TestRegister("test_register_success") # 创建一个用例对象
suite.addTest(case) # 添加用例到测试套件中
# 方式2,添加多条测试用例
case1 = test_register.TestRegister("test_register_success")
case2 = test_register.TestRegister("test_username_isnull")
# suite.addTest([case1, case2]) # 添加用例到测试套件中
# 方式3,添加一个测试用例类
loader = unittest.TestLoader() # 创建一个加载对象
suite.addTest(loader.loadTestsFromTestCase(test_register.TestRegister))
# 方式4,添加一个模块
loader = unittest.TestLoader() # 创建一个加载对象
suite.addTest(loader.loadTestsFromModule(test_register))
# 方式5,指定测试用例的所在的目录路径,进行加载
loader = unittest.TestLoader()
suite.addTest(loader.discover(r"d:\learn\python"))
TestRunner执行用例
test runner执行测试用例的,并且可以生成相应的测试报告。测试报告有text文本和html。html格式的是HTMLTestRunner了,HTMLTestRunner是 Python 标准库的 unittest 框架的一个扩展,它可以生成一个直观清晰的 HTML 测试报告。
import unittest
import test_register from HTMLTestRunner import HTMLTestRunner
# 创建测试套件
suite = unittest.TestSuite()
# 通过模块加载测试用例
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromModule(test_register))
# 创建测试运行程序启动器
runner = HTMLTestRunner(stream=open("report.html", "wb"), # 打开一个报告文件,将句柄传给stream
tester="miki", # 报告中显示的测试人员
description="注册接口测试报告", # 报告中显示的描述信息
title="自动化测试报告") # 报告的标题
# 使用启动器去执行测试套件里的用例
runner.run(suite)
相关参数说明:
-
stream:指定输出的方式 -
tester:报告中要显示的测试人员的名字 -
description:报告中要显示的面熟信息 -
title:测试报告的标题 -
verbosity:表示测试报告信息的详细程度,一共三个值,默认是2- 0 (静默模式):你只能获得总的测试用例数和总的结果,如:总共100个 失败10 成功90
- 1 (默认模式):类似静默模式,只是在每个成功的用例前面有个. 每个失败的用例前面有个F
- 2 (详细模式):测试结果会显示每个测试用例的所有相关的信息
python垃圾回收机制
引用计数器
环状双向链表 python中创建的任何对象都会放到refchain双向链表中
- 内部会创建一些数据【上一个对象,下一个对象,类型,引用个数】
name='lxq'
new=name 引用个数加一 - 内部会创建一些数据【上一个对象,下一个对象,类型,引用个数,value=18】
age=18 - 内部会创建一些数据【上一个对象,下一个对象,类型,引用个数,items=元素,元素个数】
hobby=['swim','sing']
C源码中使用PyObject结构体来体现每个对象都有相同的值{4个值} 多个元素组成的对象:PyObject结构体+ob_size(元素个数)
类型封装结构体
- data = 3.14
内部创建: _ob_next _ob_prev ob_refcnt=1 ob_type=float ob_fval=3.14
引用计数器 python运行时会根据数据类型的不同找到对应的结构体,根据结构体的字段创建相关数据,然后将对象添加到双向链表中
关键结构体PyObject PyVarObject
ob_refcnt就是引用计数器 默认为一,当有变量引用对象,计数器变化
a=100
b=a #引用
del b # b变量删除,b对应对象的引用计数器减一
del a # a变量删除,a对应对象的引用计数器减一
#当一个对象引用计数器为0,进行垃圾回收
# 1.对象从双向链表删除2.对象销毁,内存归还
循环引用
v1=[1,2,3] #v1引用计数机加一
v2=[4,5,6] #v2引用计数机加一
v1.append(v2) #v2引用计数机加一
v2.append(v1) #v1引用计数机加一
del v1 #v1引用计数机减一
del v2 #v2引用计数减一
标记清除
- 解决引用计数器循环引用不足,在python底层再维护一个存放可能存在循环引用的对象(list,tuple,dict,set)的链表
- 某个时刻,扫描可能存在循环引用的链表中的每个元素,检查是否有循环引用,如果有,让双方引用计数器-1,为零则回收
分代回收
- 什么时候扫描
- 可能存在循环引用的链表扫描代价大,耗时久
将可能存在循环引用的链表维护成三个链表:
- 0代:对象个数达到700个扫描一次
- 1代:0代扫描十次扫描一次
- 2代:一代扫描十次扫描一次
小结
- 在python中维护了一个refchain的双向环状链表,存储程序中创建的所有对象,每种类型的对象都有一个ob_refcnt引用计数器的值,引用计数器变为零会进行垃圾回收(对象销毁,从refchain中移除)
- 但是在python中有多个元素组成的对象可能存在循环引用的问题,为了解决这个问题,引入标记清除和分代回收,在其内部维护四个链表(refchain,0代,1代,2代)在源码内部,当达到预值,就会扫描链表,发现循环引用,各自减一
缓存机制
池避免重复创建和销毁一些常见对象,维护池
#启动解释器python内部会帮我们创建-5,-4.。。257
v1=7 #不开辟内存,直接去池中获取
v2=9 #不开辟内存,直接去池中获取
v3=9 #不开辟内存,直接去池中获取
free_list 计数为零,应该回收,但内部不会回收,添加到free_list链表中缓存,以后再去创建对象,不开辟内存,而是直接使用free_list
pyetst
6 Web 自动化测试
| 名称 | 相关知识点 |
|---|---|
| 6.1 selenium | selenium简介及实战 |
| 6.2 page object | page object 设计模式详解及实战 |
7 移动端 app 自动化测试
| 名称 | 相关知识点 |
|---|---|
| 7.1 appium | appium 基础知识及实战 |
| 7.2 appium使用技巧 | 元素定位、弹窗识别、webview测试等 |
8 常用开源测试平台
| 名称 | 相关知识点 |
|---|---|
| 8.1 monkey | android 健壮性与压力测试工具 monkey 的进阶使用 |
| 8.2 maxim | android 遍历工具 |
| 8.3 appcrawler | 多平台自动遍历测试工具 |
| 8.4 STF | 多设备管理平台 STF 打造自己的智能设备实验室管理上百台设备 |
| 8.5 Selenium Grid | 跨平台设备管理方案 Selenium Grid 构建支持 android、ios、web 的多架构自动化测试平台 |
9 客户端专项测试
| 名称 | 相关知识点 |
|---|---|
| 9.1 启动性能分析 | 冷启动、热启动、暖启动、首屏启动指标分析 |
| 9.2 接口性能分析 | dns、http/https 的接口性能分析 |
| 9.3 Webview性能分析 | hybrid app 的性能分析 |
| 9.4 H5性能分析 | 手机浏览器的性能数据获取与分析 |
| 9.5 卡顿分析 | 过度绘制、冰冻帧、卡顿数据 |
| 9.6 系统资源分析 | cpu 统计、mem 统计、网络流量分析 |
| 9.7 耗电量测试 | 使用 batterystats 与 battery historian 完成耗电量的基准分析 |
| 9.8 弱网测试 | 模拟弱网、丢包、延迟、不可访问等多种条件下的应用体验 |
| 9.9 健壮性测试 | 使用 monkey maxim 完成 app 的健壮性测试 |
| 9.10 兼容性测试 | 使用 appcrawler 完成遍历与兼容性分析 |
| 9.11 代码覆盖率 | jacoco 代码覆盖率 |
10 服务端接口测试
| 名称 | 相关知识点 |
|---|---|
| 10.1 常见接口协议 | tcp/udp/http/restful/dubbo |
| 10.2 抓包分析 tcp 协议 | 使用 tcpdump 与 wireshark 分析三次握手与四次挥手流程 |
| 10.3 postman/curl | postman及curl简介及使用 |
| 10.4 常用代理工具 | charles、burpsuite、mitmproxy、anyproxy |
| 10.5 http/https 抓包分析 | ssl 证书设置与 https 抓包 |
| 10.6 http 协议讲解 | 状态码、header、请求与响应的格式分析 |
| 10.7 get、post | get 与 post 的本质区别与具体抓包解读 |
| 10.8 session、cookie、token | 了解 session、cookie、token |
11 服务端接口自动化测试
| 名称 | 相关知识点 |
|---|---|
| 11.1 接口测试框架 | requests |
| 11.2 接口请求构造 | get/post/put/head 等 http 请求构造 |
| 11.3 接口测试断言 | 状态码、返回内容等断言 |
| 11.4 json/xml 请求 | 优雅的发送 json、xml 请求 |
| 11.5 json/xml 响应断言 | json path、xpath 进行断言 |
| 11.6 schema 断言 | 大量响应数据字段的格式断言 |
| 11.7 header cookie | header 自定义与 cookie 复用 |
| 11.8 认证体系 | http basic、oauth2 等认证体系的测试 |
12 服务端性能测试
| 名称 | 相关知识点 |
|---|---|
| 12.1 JMeter | JMeter 实战 |
| 12.2 性能监控系统 | influxdb、grafana、prometheus 实战 |
13 接口安全测试
| 名称 | 相关知识点 |
|---|---|
| 13.1 服务端安全测试体系 | 详解 OWASP 的 top10 安全漏洞与安全防护体系 |
| 13.2 安全测试演练环境 | 搭建安全测试演练环境实操常见安全漏洞 |
| 13.3 常见接口安全测试工具 | zap、burpsuite、sqlmap 等知名安全测试工具介绍 |
| 13.4 BurpSuite | 黑客与白帽子最常用的安全测试工具详解 |
| 13.5 命令注入漏洞 | 命令注入漏洞原理与实操 |
| 13.6 sql 注入漏洞 | sql 注入、sql 盲注等漏洞的原理介绍与实操 |
| 13.7 xss 漏洞 | xss 多种漏洞的原理介绍与实操 |
| 13.8 csrf 漏洞 | csrf 漏洞原理介绍与实操 |