面向对象
基本的介绍
面向对象是一个编程思想(写代码的套路)
编程思想:
1.面向过程
2.面向对象
以上两种都属于写代码的套路(方法),最终目的都是为了将代码书写出来,只不过过程和思考方法不太一样.
- 面向过程
- 关注的是具体步骤的实现,所有的功能都自己书写
- 亲力亲为
- 定义一个个函数,最终按照顺序调用函数
- 面向对象
- 关注的是结果,谁(对象)能帮我做这件事
- 偷懒
- 找一个对象(),让对象去做
类和对象
面向对象的核心思是 找一个对象去帮我们处理事情
在程序代码中 对象是由 类 创建的
类和对象,是面向对象编程思想中非常重要的两个概念
-
类
- 抽象的概念,对多个特征和行为相同或者相似事物的统称。
- 泛指的(指代多个,而不是具体的一个)
-
对象
-
具体存在的一个事物,看得见摸得着的
-
特指的,(指代一个)
-
苹果 --->类
红苹果 ---->类
张三嘴里正在吃的那个苹果 --->对象
类的组成
- 类名(给这多个事物起一个名字,在代码中满足大驼峰命名法(每个单词的首字母大写))
- 属性〈事物的特征,即有什么,一般文字中的名词)
- 方法(事物的行为,即做什么事,一般是动词)
类的抽象(类的设计)
-
类的抽象,其实就是找到类的 类名 属性 和方法
-
举例:
- 需求:
小明今年18岁,身高1.75,每天毕上跑完步,会去吃东西
小美今年17岁,身高1.65,小美不跑步,小美喜欢吃东西
类名: 人类(Person,People) 属性: 名字(name),年龄(age),身高(height) 方法: 跑步(run) 吃(eat)
- 需求:
一只黄颜色的狗狗叫大黄看见生人汪汪叫
看见家人摇尾巴
类名: 狗类(Dog) 属性: 颜色(color),名字(name) 方法: 汪汪叫(bark),摇尾巴(shake)
- 需求:
面向代码的步骤
- 定义类,在定义类之前先设计类
- 创建对象,使用第一步定义的类创建对象
- 通过对象调用方法
面向对象基本代码的书写
1. 定义类
-
先定义简单的类,不包含属性,在 python中定义类需要使用 关键字class
-
方法:方法的本质是在类中定义的函数,只不过,第一个参数是 self
class类名: #在缩进中书写的内容,都是类中的代码 def方法名(self): #就是一个方法 pass
2. 创建对象
-
创建对象是使用
类名()
进行创建,即类名() # 创建一个对象,这个对象在后续不能使用 # 创建的对象想要在后续的代码中继续使用,需要使用一个变量,将这个对象保存起来 变量=类名() # 这个变量中保存的是对象的地址,一般可以成为这个变量为对象 # 一个类可以创建多个对象,只要出现类名()就是创建一个对象,每个对象的地址是不一样的
3. 调用方法
对象.方法名()
列表.sort()
列表.append()
4. 案例实现
-
需求:小猫爱吃鱼,小猫要喝水
class Cat: # 类名 def eat(self): # 缩进中书写方法 print('小猫爱吃鱼') def drink(self): print('小猫爱喝水') # 创建对象1 blue_Cat = Cat() blue_Cat.eat() # 调用方法 blue_Cat.drink() # 创建对象2 black_Cat = Cat() black_Cat.eat() black_Cat.drink()
self 的说明
class Cat: # 类名
def eat(self): # 缩进中书写方法
print('小猫爱吃鱼')
black_Cat.eat()
1.从函数的语法上讲,self是形参,就可以是任意的变量名,只不过我们习惯性将这个形参写作self
2. self 是普通的形参,但是在调用的时候没有传递实参值,原因是,Python解释器在执行代码的时候,自动的将调用这个方法的对象传递给了self,即self的本质是对象
3.验证,只需要确定通过哪个对象调用,对象的引用和 self 的引用是一样的
4. self是函数中的局部变量,直接创建的对象是全局变量
class Cat:
#在缩进中书写方法
def eat(self): # self 会自动出现,暂不管
print(f'{id(self)}, self')
print('小猫爱吃鱼...')
# 2.创建对象
blue_ cat = Cat()
print(f'{id(blue_ cat)}, blue_ cat')
# 3.通过对象调用类中的方法
blue_ cat.eat() # blue_ cat对象调用eat 方法,解释器就会将blue_ cat对象传给self
print('*_' * 30)
#创建对象
black_ cat = Cat()
print(f"{id(black_ cat)}, black_ cat")
black_ cat.eat() # black_cat 对象调用eat 方法,解释器就会将black_cat 对象传给self
对象的属性操作
添加属性
对象.属性名=属性值
-
类内部添加
在内部方法中,self 是对象, self.属性名=属性值 #在类中添加属性一般写作__init_方法中
-
类外部添加
对象.属性名 = 属性值 # 一般不使用
获取属性
对象.属性名
-
类内部
在内部方法中,self是对象, self.属性名
-
类外部
对象.属性名 # 一般很少使用
class cat:
#在缩进中书写方法
def eat(self): # self会自动出现,暂不管
print(f' {id(self)},self')
print(f'小猫{self.name}爱吃鱼...")
# 2.创建对象
blue_cat = cat()
print(f"{id(blue_cat)}, blue_cat')
# 给蓝猫添加name属性
blue_cat.name ="蓝猫"
# 3.通过对象调用类中的方法
blue_cat.eat() # blue_cat对象调用eat方法,解释器就会将blue_cat对象传给self
print( '_*_' * 30)
##创建对象
black_cat = cat()
black_cat.name ='黑猫'
print(f"{id(black_cat)}, black_cat")
black_cat.eat() # black_cat对象调用eat方法,解释器就会将black_cat对象传给self
魔法方法
-
python中有一类方法,以两个下划线开头,两个下划线结尾,并且在满足某个条件的情况下,会自动调用,这类方法称为魔法方法
-
学习:
1.什么情况下自动调用2.有什么用,用在哪
3.书写的注意事项
__init__
方法 **
-
什么情况下自动调用
- 创建对象之后会自动调用
-
有什么用,用在哪
- 给对象添加属性的,(初始化方法/ 相当于其他语言的 构造方法)
- 某些代码,在每次创建对象之后,都要执行,就可以将这行代码写在
__init__
方法
-
书写的注意事项:
- 不要写错了
- 如果 init 方法中,存在出了 self 之外的参数,在创建对象的时候必须传参
class Cat:
# 定义添加属性的方法
def __init__(self, name, age): # 这个方法是创建对象之后调用
self.name = name # 给对象添加name属性
self.age = age # 给对象添加age属性
print('我被调用了')
# 输出属性信息
def show_info(self):
print(f'小猫的名字是:{self.name},年龄是:{self.age}')
# Cat('小猫', 2) # 创建对象,会输出
cute_Cat = Cat('蓝猫', 5) # 创建对象,会输出
blue = cute_Cat # 不是创建对象,不会输出
blue.show_info()
black_Cat = Cat('黑猫',8)
black_Cat.show_info()
__str__
方法 *
-
什么情况下自动调用
- 使用print(对象)打印对象的时候会自动调用
-
有什么用,用在哪
- 在这个方法中一般书写对象的属性信息的,即打印对象的时候想要查看什么信息,在这个方法中进行定义的
- 如果类中没有定义
__str__
方法,print(对象),默认输出对象的引用地址
-
书写的注意事项
- 这个方法必须 返回 一个字符串
class Cat:
# 定义添加属性的方法
def __init__(self, name, age): # 这个方法是创建对象之后调用
self.name = name # 给对象添加name属性
self.age = age # 给对象添加age属性
print('我被调用了')
def __str__(self):
# 方法必须返回一个字符串,只要是字符串就行
return f'小猫的名字是:{self.name},年龄是:{self.age}'
# Cat('小猫', 2) # 创建对象,会输出
cute_Cat = Cat('蓝猫', 5) # 创建对象,会输出
blue_Cat = cute_Cat # 不是创建对象,不会输出
print(blue_Cat)
black_Cat = Cat('黑猫', 8)
print(black_Cat)
__del__
方法 [了解]
__init_方法,创建对象之后,会自动调用 (构造方法)
__del__方法,对象被删除销毁时,自动调用的(遗言,处理后事) (析构方法)
1.调用场景,程序代码运行结束,所有对象都被销毁
2.调用场景,直接使用del删除对象(如果对象有多个名字(多个对象引用一个对象),需要把所有的对象都删除才行)
class Demo:
def __init__(self, name):
print('我是__init_,我被调用了')
self.name = name
def __del__(self):
print(f' {self.name}没了,给他处理后事... ')
# Demo('a')
a = Demo('a')
b = Demo('b')
del a # 删除销毁对象,
print('代码运行结束')
# 我是__init_,我被调用了
# 我是__init_,我被调用了
# a没了,给他处理后事...
# 代码运行结束
# b没了,给他处理后事...
案例
- 需求:
- 小明体重75.0 公斤
- 小明每次跑步会减肥0.5 公斤
- 小明每次吃东西体重增加1公斤
类名:人类Person
属性:姓名name,体重weight
方法:跑步run
吃东西eat
添加属性__init__
属性信息__str__
class Person:
def __init__(self,name,weight):
self.name = name
self.weight = weight
def __str__(self):
return f'姓名:{self.name},体重{self.weight}kg'
def run(self):
print('小明跑了一次步,减重了')
self.weight = self.weight-0.5
def eat(self):
print('小明吃了一顿大餐,重了')
self.weight = self.weight+1
xm = Person('小明',75.0)
print(xm)
xm.run()
print(xm)
xm.eat()
print(xm)
案例2 分析
-
需求:
-
房子(House)有户型、总面积和家具名称列表
- 新房子没有任何的家具
-
家具(Houseltem)有名字和占地面积,其中
-
席梦思(bed)占地
4
平米 -
衣柜(chest)占地:
2
平米。 -
餐桌(table)占地1.5平米
-
-
将以上三件 家具 添加到 房子 中
-
打印房子时,要求输出 : 户型、总面积、剩余面积、家具名称列表
-
-
剩余面积
- 在创建房子对象时,定义一个剩余面积的属性,初始值和总面积相等
- 当调用
add_item
方法,向房间添加家具时,让剩余面积 - = 家具面积
类名:房子类 House
属性:户型name,总面积total_area,剩余面积 free_area = total_area
家具名称列表 item_list =[]
方法:__init__, __str__
添加家具方法
def add_item(self, item): #item家具对象
先判断房子的剩余面积和总面积的关系
修改房子的剩余面积
修改房子的家具名称列表
类名:家具类 HouseItem
属性:名字name,占地面积area
方法:__init__, __str__
作业例题
-
题目一:
-
代码:
class Student: def __init__(self,name,age): self.name=name self.age = age def __str__(self): return f'姓名:{self.name},年龄:{self.age}岁' def eat(self): print(f'{self.name}要吃饭') def sleep(self): print(f'{self.name}要睡觉') def year(self): self.age = self.age+1 print(self.age) xm = Student('小明', 18) print(xm) xm.eat() xm.sleep() xm.year() xh = Student('小红', 17) print(xh) xh.eat() xh.sleep() xh.year()
-
题目二
-
代码:
class HouseItem: def __init__(self, name, area): self.name = name self.area = area def __str__(self): return f'{self.name}占地{self.area}平米' class House: def __init__(self, house_type, area): self.house_type = house_type self.total_area = area self.free_area = area self.item_list = [] def __str__(self): return f'户型为{self.house_type},总面积为{self.total_area},剩余面积{self.free_area},家具名称列表{self.item_list}' def add_item(self, item): if self.free_area > item.area: self.item_list.append(item.name) self.free_area = self.free_area - item.area print(f'{item.name} 添加成功') else: print('添加家具失败') bed = HouseItem('席梦思', 4) chest = HouseItem('衣柜', 2) table = HouseItem('餐桌', 1.5) print(bed) print(chest) print(table) house = House('三室一厅', 150) house.add_item(bed) print(house)
私有和公有
-
在Python中定义的方法和属性,可以添加访问控制权限(即在什么地方可以使用这个属性和方法)
-
访问控制权限分为两种,公有权限,私有权限
-
公有权限
-
直接书写的方法和属性,都是公有的
-
公有的方法和属性,可以在任意地方访问和使用
-
-
私有权限
- 在类内部,属性名或者方法名前边加上两个下划线,这个属性或者方法就变为私有的
- 私有的方法和属性,只能在当前类的内部使用
-
什么时候定义私有
- 某个属性或者方法,不想在类外部被访问和使用,就将其定义为私有即可
- 测试中,一般不怎么使用,直接公有即可
- 开发中,会根据需求文档,确定什么作为私有
-
如果想要在类外部操作私有属性,方法是,在类内部定义共有的方法,我们通过这个方法去操作
-
案例
定义一个Person类,属性name,age(私有)
class Person: def __init__(self, name, age): self.name = name # 姓名 # 私有的本质,是 Python解释器执行代码,发现属性名或者方法名前有两个_, # 会将这个名字重命名#会在这个名字的前边加上_类名前缀,即self._age ===> self._Person__age self.__age = age # 年龄,将其定义为私有属性,属性名前加上两个_ def __str__(self): # 在类内部可以访问私有属性的 return f'名字: {self.name},年龄: {self.__age}' xm = Person('小明', 18) print(xm) # 在类外部直接访问age属性 # print(xm._age)#会报错,在类外部不能直接使用私有属性#直接修改age属性 xm.__age = 20 # 这个不是修改私有属性,是添加了一个公有的属性_age print(xm) print(xm._Person__age) # 能用但是不要用 xm._Person__age = 19 print(xm)
继承
- 继承描述的是类与类之间的关系
- 继承的好处:减少代码的冗余(相同的代码不需要多次重复书写),可以直接使用
语法
#class A(object) :
class A: #没有写父类,但也有父类,object,object类是 Python中最顶级(原始)的类
pass
class B(A): #类B,继承类A
pass
-
术语
-
A类,称为是父类(基类)
-
B类,称为是子类(派生类)
单继承:一个类只继承一个父类,称为单继承
-
-
继承之后的特点:
- 子类 (B) 继承父类 (A) 之后,子类的对象可以直接使用父类中定义的公有属性和方法
-
案例:
1.定义一个动物类,吃
2.定义一个狗类,继承动物类,吃,叫
3.定义一个哮天犬类,继承狗类
-
代码
class Animal: def eat(self): print('吃东西') class Dog(Animal): def dark(self): print('嚎叫') class XTQ(Dog): print('我是哮天犬') xtq = XTQ() xtq.dark() xtq.eat()
-
结论
python中对象.方法()调用方法 1.现在自己的类中的去找有没有这个方法如果有,直接调用 2.如果没有去父类中查找,如果有,直接调用 3.如果没有,去父类的父类中查找,如果有直接调用 4 ... 5.如果object类中有,直接调用,如果没有,代码报错
重写
- 重写:在子类中定义了和父类中名字相同的方法,就是重写
- 重写的原因:父类中的方法,不能满足子类对象的需求,所以重写
- 重写之后的特点:调用子类字节的方法,不再调用父类中的方法重写的方式:
- 1.覆盖(父类中功能完全抛弃,不要,重写书写)
- 2.扩展(父类中功能还调用,只是添加一些新的功能)(使用较多)
覆盖
-
直接在子类中定义和父类中名字相同的方法
-
直接在方法中书写新的代码
class Dog:
def bark( self):
print( '汪汪汪叫..... ')
class xTQ(Dog):
# XTQ类bark方法不再是汪汪汪叫,改为嗷嗷嗷叫
def bark(self):
print( '嗷嗷嗷叫...')
xtq = xTQ()
xtq.bark()
扩展父类中的功能
- 直接在子类中 定义和父类中名字相同的方法
- 在合适的地方调用父类中方法 super.( ) . 方法( )
- 书写添加的新功能
多态
- 是一种写代码调用的一种技巧
- 同一个方法,传入不同的对象,执行得到不同的结果,这种现象称为是多态
- 多态可以增加代码的灵活度
- 哪个对象调用方法,就去自己的类中去查找这个方法,找不到去父类中找
属性和方法
Python中一切皆对象.
即使用class 定义的类也是一个对象
对象的划分
实例对象(实例)
- 通过类名()创建的对象,我们称为实例对象,简称实例
- 创建对象的过程称为是类的实例化
- 我们平时所说的对象就是指实例对象(实例)
- 每个实例对象,都有自己的内存空间,在自己的内存空间中保存自己的属性(实例属性)
类对象(类)
- 类对象就是类,或者可以认为是类名
- 类对象是Python解释器在执行代码的过程中创建的
- 类对象的作用:①使用类对象创建实例类名(),②类对象也有自己的内存空间,可以保存一些属性值信息(类属性)
- 在一个代码中,一个类只有一份内存空间
属性的划分
实例属性
-
概念:是实例对象具有的属性
-
定义和使用:
在init方法中,使用 self.属性名=属性值 定义 在方法中是使用 self.属性名 来获取(调用)
-
内存
- 实例属性,在每个实例中都存在一份
-
使用时机
1.基本上99%都是实例属性,即通过self 去定义 2.找多个对象,来判断这个值是不是都是一样的,如果都是一样的,同时变化,则一般定义为类属性,否则定义为实例属性
类属性
-
概念:是类对象具有的属性
-
定义和属性
在类内部,方法外部,直接定义的变量,就是类属性 使用:类对象.属性名=属性值 or 类名.属性名=属性值 类对象.属性名 or 类名.属性名
-
内存
- 只有类对象中存在一份
方法的划分
- 方法,使用def 关键字定义在类中的函数就是方法
实例方法(最常用)
-
定义
#在类中直接定义的方法就是实例方法 class Demo: def func(self): #参数一般写作self,表示的是实例对象 pass
-
定义时机(什么时候用)
如果在方法中需要使用 实例属性(即需要使用self),则这个方法 必须 定义为实例方法
-
调用
对象.方法名() #不需要给self 传参
类方法(会用)
-
定义
#在方法名字的上方书写@classmethod装饰器(使用@classmethod装饰的方法) class Demo: @classmethod def func(cls): #参数一般写作cls,表示的是类对象(即类名) class pass
-
定义时机(什么时候用)
1.前提,方法中不需要使用 实例属性(即self) 2.用到了 类属性,可以将这个方法定义为 类方法,(也可以定义为 实例方法)
-
调用
# 1.通过类对象调用 类名.方法名() #也不需要给cls传参,python解释器自动传递 # 2.通过实例对象调用 实例.方法名() #也不需要给cls传参,python解释器自动传递
静态方法
-
定义
#在方法名字的上方书写@staticmethod装饰器(使用@staticmethod装饰的方法) class Demo: @staticmethod def func(): #一般没有参数 pass
-
定义时机(什么时候用)
1.前提,方法中不需要使用实例属性(即self) 2.也不使用 类属性,可以将这个方法定义为 静态方法
-
调用
# 1.通过类对象调用 类名.方法名() # 2.通过实例对象调用 实例.方法名()
练习
-
题目1
定义一个游戏类
Game
,包含实例属性玩家名字(name
)-
要求记录游戏的最高分(
top_score
类属性), -
定义方法:
show_help
显示游戏的帮助信息输出这是游戏的帮助信息
-
定义方法:
show_top_score
,打印输出游戏的最高分 -
定义方法:
start_game
,开始游戏,规则如下- 使用随机数获取本次游戏得分范围
(10 - 100 )之间
- 判断本次得分和最高分之间的关系
- 如果本次得分比最高分高
- 修改最高分
- 如果分数小于等于最高分,则不操作
- 如果本次得分比最高分高
- 输出本次游戏得分
- 使用随机数获取本次游戏得分范围
-
主程序步骤:
1.创建一个Game对象, 小王 2.小王玩一次游戏 3.查看历史最高分 4.小王再玩一次游戏 5.查看历史最高分 6.查看游戏的帮助信息
-
-
代码
import random class Game: top_score = 0 def __init__(self, name): self.name = name def show_help(self): print('这是游戏的帮助信息') def show_top_score(self): print(f'游戏的最高分为{Game.top_score}') def start_game(self): print(f'{self.name}开始一局游戏', end=' ') score = random.randint(10, 100) if score > Game.top_score: Game.top_score = score print(f'{self.name}本次游戏得分为{score}') xw = Game('小王') xw.start_game() xw.show_top_score() xw.start_game() xw.show_top_score() xw.show_help()
- 优化(使用类方法和静态方法)
@staticmethod def show_help(): print('这是游戏的帮助信息') @classmethod def show_top_score(cls): print(f'游戏的最高分为{cls.top_score}')
补充
-
哈希(hash) :是一个算法,可以对数据产生一个唯一的值(指纹)
-
is 可以用来判断两个对象是不是同一个对象,即两个对象的引用是否相同
a is b === > id(a) == id(b) 面试中可能会问: is和 == 的区别? == 只判断数据值是否相同, is判断引用是否相同
文件介绍
-
计算机的文件,就是存储在某种长期储存设备上的一段数据
-
作用:将数据长期保存下来,在需要的时候使用
- 计算机只认识二进制
(0 1)
- 文件中存储的数据都是以二进制
(0 1)
的形式去存储的
- 计算机只认识二进制
-
可以根据文件中的二进制内容,能否使用记事本软件将其转换为文字,将文件分为两种:文本文件和二进制文件
-
文本文件:
-
能够使用记事本软件打开(能够使用记事本转换为文字)
txt,md, py html, css, js , json
-
-
二进制文件
-
不能使用记事本软件打开的
exe,mp3,mp4, jpg,png
-
文件操作
文件操作的步骤
1.打开文件
2.读或者写文件
3.关闭文件
1. 打开文件
打开文件:将文件从磁盘(硬盘)中读取到内存中
语法:
open(file, mode= 'r ', encoding=None)
- 参数file:是要打开的文件,类型是字符串,文件的路径可以是相对路径,也可以是绝对路径(从根目录开始书写的路径),建议使用相对路径(相对于当前代码文件所在的路径,./ ../ )
- 参数mode:默认参数(缺省参数),表示的是打开文件的方式
- r: read 只读打开
- w : write 只写打开
- a: append 追加打开,在文件的末尾写入内容
- 参数encoding:编码方式,(文字和二进制如何进行转换的)
- gbk:将一个汉字转换为2个字节二进制
- utf-8:常用,将一个汉字转换为3个字节的二进制
- 返回值:返回的是文件对象,后续对文件的操作,都需要这个对象
2. 读或者写文件
写文件
-
向文件中写入指定的内容
前提:文件的打开方式是 w 或者 a
文件对象.write(' 写入文件的内容 ')
# 返回值:写入文件的字符数,一般不关注
#注意w方式打开文件:
1,文件不存在,会直接创建文件
2.文件存在,会覆盖原文件(将原文件中的内容清空)
-
代码
f = open('a.txt', 'w', encoding= 'utf-8') f.write('天天向上\n') f.write('好好学习') f.close()
读文件
-
将文件中的内容读取出来
前提:文件的打开方式需要是 r
文件对象.read(n)
#参数n表示读取多少个字符,一般不写,表示读取全部内容
#返回值:读取到的文件内容,类型字符串
#1,打开文件
f = open( 'a.txt ', 'r', encoding='utf-8')
#2,读文件
buf = f.read()
print(buf) #目前只是打印读取的内容,可以做其它的事
#3.关闭文件
f.close()
# r方式打开文件,如果文件不存在,代码会报错
3. 关闭文件
-
关闭文件:将文件占用的资源进行清理,同时会保存文件,文件关闭之后,这个文件对象就不能使用了
文件对象.close()
使用with open 打开文件
with open()打开文件的好处:不用自己去书写关闭文件的代码,会自动进行关闭
with open(file, mode, encoding='utf-8') as 变量:
#在缩进中去读取或者写入文件
#缩进中的代码执行结束,出缩进之后,文件会自动关闭
-
代码
with open('a.txt', 'a', encoding= 'utf-8') as f: f.write('good good study') # a方式打开文件,文件不存在会创建文件,文件存在,在文件的末尾写入内容
-
代码(读文件的三种方式)
with open('b.txt', encoding='utf-8') as f: buf = f.readline() print(buf) print(f.readline())
with open('b.txt', encoding='utf-8') as f: for i in f: # 按行读取,读到文件末尾结束 print(i, end='')
with open('b.txt', encoding='utf-8') as f: # read()和readline()读到文件末尾,返回一个空字符串,即长度为0 while True: buf = f.readline() if len(buf) == 0: break else: print(buf, end='')
#在容器中,容器为空,即容器中的数据的个数为0 ,表示 False,其余情况都是True with open('b.txt', encoding='utf-8 ') as f: while True: buf = f.readline() if buf: # if Len( buf) != 0 print(buf) else: break
json 文件的处理
- json文件也是一个文本文件,就可以直接使用
read()
和write()
方法去操作文件,只是使用这两个方法不方便,所以对json文件有自己独特的读取和写入的方法 - 常用在做测试的时候,将测试数据定义为json文件格式,使用代码读取json文件,即读取测试数据,进行传参(参数化)
json的介绍
- json基于文本,独立于语言的轻量级的数据交换格式
- 基于文本,是一个文本文件,不能包含图片,音视频等
- 独立于语言,不是某个语言特有的,每种编程语言都可以使用的
- 轻量级,相同的数据,和其他格式相比,占用的大小比较小
- 数据交换格式,后端程序员给前端的数据(json、html、xml)
json文件的语法
- json文件的后缀是.json
- json中主要数据类型为对象(
{ }
类似 Python中字典)和 数组 ([ ]
,类似 Python中的列表 ),对象和数组可以互相嵌套 - 一个json文件是一个对象或者数组(即json文件的最外层要么是一个
{ }
,要么是一个数组[ ]
) - json中的对象是由键值对组成的,每个数据之间使用 逗号 隔开,但是最后一个数据后边不要写逗号
- json中的字符串必须使用 双引号
- json中的其他数据类型(右边是python中的)
- 数字类型 ----> int float
- string 字符串 ---> str
- 布尔类型 true, false -----> True,False
- null ----> None
json文件的书写
我叫小明,我今年18岁,性别男,爱好听歌,游戏,购物,吃饭,睡觉,打豆豆T我的居住地址为国家中国,城市上海
{
"name": "小明",
"age": 18,
"isMen": true,
"like": [
"听歌",
"游戏",
"购物",
"吃饭",
"睡觉",
"打豆豆"
],
"address": {
"country": "中国",
"city": "上海"
}
}
读取 json 文件
1.导包 import json
2.读打开文件
3.读文件
json.load(文件对象)
# 返回的是 字典 (文件中是对象) 或者 列表 (文件中是数组)
import json
with open('info.json', encoding='utf-8') as f:
# buf = f.read() # <class 'str'>
result = json.load(f)
print(type(result)) # <class 'dict'> --> 更好取值
print(result.get('name'))
print(result.get('age'))
print(result.get('address').get('city'))
练习
题目1
我叫小明,我今年18岁,性别男,爱好听歌,游戏,吃饭,睡觉,打豆豆,我的居住地址为国家中国,城市上海.
我叫小红,我今年17岁,性别女,爱好听歌,学习,购物我的居住地址为国家中国,城市北京.
-
info2.json
[ { "name": "小明", "age": 18, "isMen": true, "like": [ "听歌", "游戏", "购物", "吃饭", "睡觉", "打豆豆" ], "address": { "country": "中国", "city": "上海" } }, { "name": "小红", "age": 17, "isMen": false, "like": [ "听歌", "学习", "购物" ], "address": { "country": "中国", "city": "北京" } } ]
-
代码
import json with open('info2.json', encoding='utf-8') as f: info_list = json.load(f) for info in info_list: print(info.get('name'), info.get('age'), info.get('address').get('city'))
题目2
某网站的测试数据如下data.json,需求,提取json文件中的用户名,密码和预期结果,组成如下格式:[(),(),()] (自动化参数化需要的数据格式)
[
{
"desc":"正确的用户名密码",
"username": "admin",
"password" : "123456",
"expect":"登录成功"
},
{
"desc":"错误的用户名",
"username": "root",
"password" : "123456",
"expect":"登录失败"
},
{
"desc":"错误的密码",
"username" : "admin",
"password" : "123123",
"expect":"登录失败"
}
]
-
代码(!!重要!常用)
import json def read_data(): new_list = [] with open('data.json', encoding='utf-8') as f: info = json.load(f) # 列表 for i in info: # i 字典 new_list.append((i.get('username'), i.get('password'), i.get('expect'))) return new_list
json 的写入
文件对象.write(字符串)不能直接将Python的 列表 和 字典 作为参数传递
想要将Python中的数据类型存为json文件,需要使用json提供的方法,不再使用write
步骤:
1.导包import json
2.写(w)方式打开文件
3.写入
json.dump(Python中的数据类型,文件对象)
-
代码
import json my_list = [('admin', '123456', '登录成功'), ('root', '123456', '登录失败'), ('admin', '123123', '登录失败')] with open('info_4.json', 'w', encoding='utf-8') as f: # json.dump(my_list,f) # json.dump(my_list, f, ensure_ascii=False) # 直接显示中文,不以ASCII的方式显示 # 显示缩进 json.dump(my_list, f ,ensure_ascii=False, indent=4)
-
注意
异常
- 程序在运行时,如果Python解释器遇到到一个错误,会停止程序的执行,并且提示一些错误信息,这就是异常
- 程序停止执行并且提示错误信息这个动作,抛出异常(raise关键字)
- 捕获异常:程序遇到异常,默认动作是终止代码程序的执行,遇见异常之后,可以使用异常捕获,让程序代码继续运行,不会终止运行(重点)
异常捕获 [ 重点 ]
基本语法
try:
书写可能发生异常的代码
except: #任何类型的异常都能捕获
发生了异常执行的代码
try:
书写可能发生异常的代码
except 异常类型: #只能捕获指定类型的异常,如果不是这个异常,还是会报错
发生了异常执行的代码
-
举例1
try: # 1、获取用户从键盘输入的数据 num = input('请输入数字:') # 2.转换数据类型为整数 num = int(num) # 3.输出转换之后的数据内容 print(num) except: print('请输入正确的数字') print('后续其他的代码,可以继续执行')
-
举例2
try: num = input('请输入数字') num = int(num) print(num) a = 10/num # num 不能等于0,但是num=0的情况不属于ValueError print(f'a:{a}') except ValueError: print('发生了异常,请输入正确的数字...') print('后面的代码依旧会执行')
捕获多个指定类型的异常
-
好处:可以针对不同的异常错误,进行单独的代码处理
try: 书写可能发生异常的代码 except 异常类型1: #只能捕获指定类型的异常,如果不是这个异常,还是会报错 发生了异常1执行的代码 except 异常类型2: 发生了异常2执行的代码 except 异常类型...: 发生了异常...执行的代码
try: num = input('请输入数字') num = int(num) print(num) a = 10/num # num 不能等于0,但是num=0的情况不属于ValueError print(f'a:{a}') except ValueError: print('发生了异常,请输入正确的数字...') except ZeroDivisionError: print('输入数字为除数,不能为0') print('后面的代码依旧会执行')
异常捕获的完整版本
- 完整版本中的内容,不是说每一次都要全部书写,根据自己的需要,去选择其中的进行使用
try:
可能发生异常的代码
except 异常类型1:
发生异常类型1执行的代码
# Exception是常见异常类的父类,这里书写 Exception,可以捕获常见的所有异常,as变量,这个变量是一个异常类的对象,print(变量)可以打印异常信息
except Exception as 变量:
发生其他类型的异常,执行的代码
else:
没有发生异常会执行的代码
finally:
不管有没有发生异常,都会执行的代码
-
常用:
try: 可能发生异常的代码 except Exception as e: 发生异常执行的代码
总结
pytest ui自动化课程中进行学习
在windows中绝对路径路径可能存在的问题:
c :/xx/ ddd
c : \xxx\xxx 可能会出现\和后边的字符结合组成转义字符
c : \xxx\Xx
r"c : \xx\xx"
练习
题目5
按照如下要求完成代码:
1 使用随机数产生10个1-20之间的数字
2 将这些数字保存到文件data.txt中
3 读取data.txt中的内容,按照数字大小进行降序排序
4 将最大的五个数字存入文件data1.txt中
-
另一种写法:
先将10个数字,转为字符串,存入列表 ',' .join(列表)
-
使用json文件:
import random import json my_list = [] for i in range(10): num = random.randint(1, 20) my_list.append(num) with open('data.json', 'w', encoding='utf-8') as f: json.dump(my_list, f) with open('data.json', 'r', encoding='utf-8') as f: data_list = json.load(f) # 排序 data_list.sort(reverse=True) print(data_list[:5]) with open('data1.txt', 'w', encoding='utf-8') as f: for i in range(5): f.write(str(data_list[i]))
题目6
两种方式实现以下需求(1.异常捕获︰2.)
1.获取用户输入的数字
2.判断获取的数字是否整数
3.如果不是整数,提示输入错误
4.如果是整数,则进一步判断是奇数还是偶数
5.最终提示:程序运行结束
-
代码:(两种方法)
def func1(): num = input('请输入数字') try: num = int(num) except Exception as e: print('输入错误,', e) else: if num % 2 == 0: print('偶数') else: print('奇数') finally: print('程序运行结束') def func2(): num = input('请输入数字') if num.isdigit(): num = int(num) if num % 2 == 0: print('偶数') else: print('奇数') else: print('输入错误') print('程序运行结束')
异常传递 [ 了解 ]
异常传递是Python中已经实现好了,我们不需要操作,我们知道异常会进行传递.
异常传递:在函数嵌套调用的过程中,被调用的函数,发生了异常,如果没有捕获,会将这个异常向外层传递.....如果传到最外层还没有捕获,才报错
模块和包
1. Python源代码文件就是一个模块
2.模块中定义的变量函数类,都可以让别人使用,同样,可以使用别人定义的(好处:别人定义好的不需要我们再次书写,直接使用即可)
3.想要使用别人的模块中的内容工具(变量,类,函数),必须先导入模块才可以
4.我们自己写的代码,想要作为模块使用,代码的名字需要满足标识符的规则(由数字,字母下划线组成,不能以数字开头)
导入模块的语法
方式一 [ 重点记忆 ]
import 模块名
# 使用模块中的内容
模块名.工具名
# 举例
import random
import json
random.randint(a, b)
json.load()
json.dump()
方式二 [ 重要 - 快捷方式导包 ]
from 模块名 import 工具名
# 使用
工具名 # 如果是函数和类,需要加括号
# 举例
from random import randint
from json import load,dump
randint(a,b)
load()
dump()
快捷导入:Alt + Enter
方式三 [了解] 基本不用
from 模块名 import * #将模块中所有的内容都导入
from random import *
from json import *
randint(a,b)
load()
dump()
- 问题:可能存在多个模块中有相同的名字的工具,会产生冲突
as : 起别名
- 对于导入的模块和工具可以使用 as 关键字给其起别名
- 注意:如果起别名,原来的名字就不能用了,只能使用别名
模块的查找顺序
-
在导入模块的时候会先在当前目录中找模块,
-
如果找到,就直接使用
-
如果没有找到回去系统的目录中进行查找找到,直接使用
-
没有找到,报错
-
-
注意点:
定义代码文件的时候,你的代码名字不能和你要导入的模块名字相同
__name__
的作用
-
每个代码文件都是一个模块
-
在导入模块的时候,会执行模块中的代码(三种方法都会)
-
__name__
变量3.1
__name__
变量,是python解释器自动维护的变量3.2
__name__
变量,如果代码是直接运行,值是"__main__
"3.3
__name__
变量,如果代码是被导入执行,值是 模块名(即代码文件名) -
在代码文件中,在被导入时不想被执行的代码,可以写在
if __name__=="__main__"
: 代码 的缩进中
- 左边导入运行,不执行右边的print
- 右边直接运行,会执行全部的print
代码练习
1.定义一个模块tools.py
2.在模块中定义一个函数,func,输出'我是tools模块中的 funn函数'
3.在模块中定义一个类,Dog,具有属性name,age,方法 play,输出'xx在快乐的玩耍'
4.新建一个代码文件,调用tools模块中的 func函数并创建一个 Dog类的对象,调用play方法
def func():
print('我是tools模块中的func函数')
class Dog:
def __init__(self, name ,age):
self.name = name
self.age = age
def play(self):
print(f'{self.name}在快乐的玩耍')
import tools
tools.func()
dog = tools.Dog('小白',5)
dog.play()
包(package)
-
在Python中,包是一个目录,只不过在这个目录存在一个文件
__init__.py
(可以是空的)- 将功能相近或者相似的代码放在一起的
-
在Python中使用的时候,不需要可以是区分是包还是模块,使用方式是一样的
-
random -- 模块(单个代码文件)
-
json -- 包(目录)
-
unittest -- 包(目录)
- import 包名
- alt 回车 快捷导入
UnitTest框架
介绍
-
框架
- 说明
- 框架英文单词framework 2.为解决一类事情的功能集合
- 需要按照框架的 规定 (套路) 去书写代码
-
什么是UnitTest框架?
- 概念:UnitTest是Python自带的一个单元测试框架,用它来做单元测试。
- 自带的框架(官方):不需要单外安装,只要安装了Python,就可以使用
- random,json,os,time
- 第三方框架:想要使用需要先安装后使用(pytest)
- selenium , appium,requests
- 自带的框架(官方):不需要单外安装,只要安装了Python,就可以使用
- 单元测试框架:主要用来做单元测试,一般单元测试是开发做的.
- 对于测试来说,unittest 框架的作用是 自动化脚本(用例代码) 执行框架(使用 unittest框架 来 管理 运行 多个测试用例的)
- 概念:UnitTest是Python自带的一个单元测试框架,用它来做单元测试。
-
为什么使用UnitTest框架?
- 能够组织多个用例去执行
- 提供丰富的断言方法(让程序代码代替人工自动的判断预期结果和实际结果是否相符)
- 能够生成测试报告
-
UnitTest 核心要素 (unitest的组成)
-
TestCase(最核心的模块)
Testcase(测试用例),注意这个测试用例是unittest框架的组成部分,不是手工和自动化中我们所说的用例(Test case) 主要作用:每个Testcase(测试用例)都是一个代码文件,在这个代码文件中 来书写 真正的用例代码
-
TestSuite
TestSuite(测试套件),用来 管理 组装(打包)多个 Testcase (测试用例)的
-
TestRunner
TestRunner(测试执行,测试运行),用来执行TestSuite(测试套件)的
-
TestLoader
TestLoader(测试加载), 功能是对 Testsuite (测试套件) 功能的补充,管理 组装 (打包) 多个 Testcase (测试用例)的
-
Fixture
Fixture(测试夹具),书写在 Testcase(测试用例)代码中,是一个代码结构,可以在每个方法执行前后都会执行的内容 举例: 登录的测试用例,每个用例中重复的代码就可以写在 Fixture代码结构中,只写一遍,但每次用例方法的执行,都会执行Fixture中的代码 1.打开浏览器 2.输入网址
-
TestCase(测试用例)
- 是一个代码文件,在代码文件中来书写真正的用例代码
- 代码文件的名字必须按照标识符的规则来书写(可以将代码的作用在文件的开头使用注释说明)
-
步骤
- 导包(unittest)
- 自定义测试类
- 在测试类中书写测试方法
- 执行用例
-
代码
""" 代码的目的:学习Testcase(测试用例)模块的书写方法 """ # 1,导包 import unittest # 2,自定义测试类, 需要继承unittest模块中的Testcase类即可 class TestDemo(unittest.TestCase): # 3,书写测试方法 即用例代码.目前没有真正的用例代码,使用print 代替 # 书写要求,测试方法必须以test_开头(本质是以test开头) def test_method1(self): print('测试方法1') def test_method2(self): print('测试方法2') # 4,执行用例(方法) # 4.1 将光标放在类名的后边运行,会执行类中的所有的测试方法 # 4.2将光标放在方法名的后边运行,只执行当前的方法
问题一 代码文件的命名不规范
1.代码文件的名字以数字开头
2.代码文件名字中有空格
3.代码文件名字有中文
4.其他的特殊符号
(数字,字母,下划线组成,不能以数字开头)
问题二 代码运行没有结果
右键运行没有 unittests for 的提示,出现的问题
解决方案:
方案1.重新新建一个代码文件,将写好的代码复制进去
方案2.删除已有的运行方式(如下图步骤)
问题3 没有找到用例
测试方法中不是以test_开头的,或者单词写错了
TestSuite & TestRunner
Testsuite(测试套件):管理打包组装Testcase(测试用例)文件的TestRunner(测试执行):执行TestSuite(套件)
-
步骤
- 导包(unittest)
- 实例化(创建对象)套件对象
- 使用套件对象添加用例方法
- 实例化运行对象
- 使用运行对象去执行套件对象
-
代码
""" 学习 Testsuite 和 Testrunner 的使用 """ # 1.导包(unittest) import unittest from Test_lzy.testcase1 import TestDemo1 from Test_lzy.testcase2 import TestDemo2 # 2.实例化(创建对象)套件对象 suite = unittest.TestSuite() # 3.使用套件对象添加用例方法 # 方法一:套件对象.addTest(测试类名.('方法名')) # 建议测试类名和方法名直接去复制,不要手写 suite.addTest(TestDemo1('test_method1')) suite.addTest(TestDemo1('test_method2')) suite.addTest(TestDemo2('test_method1')) suite.addTest(TestDemo2('test_method2')) # 4.实例化运行对象 runner = unittest.TextTestRunner() # 注意有Text # 5.使用运行对象去套执行套件对象 # 运行对象.run(套件对象) runner.run(suite)
添加测试方法的另一种书写
-
将—个测试类中的所有方法进行添加
-
套件对象.addTest(unittest.makeSuite(测试类名))
代码:
-
""" 学习 Testsuite 和 Testrunner 的使用 """ # 1.导包(unittest) import unittest from Test_lzy.testcase1 import TestDemo1 from Test_lzy.testcase2 import TestDemo2 # 2.实例化(创建对象)套件对象 suite = unittest.TestSuite() # 3.使用套件对象添加用例方法 # 方法二:将—个测试类中的所有方法进行添加 # 套件对象.addTest(unittest.makeSuite(测试类名)) suite.addTest(unittest.makeSuite(TestDemo1)) suite.addTest(unittest.makeSuite(TestDemo2)) # 4.实例化运行对象 runner = unittest.TextTestRunner() # 注意有Text # 5.使用运行对象去套执行套件对象 # 运行对象.run(套件对象) runner.run(suite)
查看测试结果(关注的内容)
练习
1.在tools模块中定义 add函数,对两个数字进行求和计算
2.书写Testcase 代码对 add()进行测试
用例1: 1,2,3
用例2: 10,20,30
用例3: 2,3,5
-
代码
def add(a, b): return a+b
# 用例代码 import unittest from Test_lzy.Tools import add class TestAdd(unittest.TestCase): def test_method1(self): # 1,2,3 if add(1, 2) == 3: print('测试通过') else: print('测试不通过') def test_method2(self): # 10,20,30 if add(10, 20) == 30: print('测试通过') else: print('测试不通过') def test_method3(self): # 2,3,5 if add(2, 3) == 5: print('测试通过') else: print('测试不通过')
# 套件和执行的代码 import unittest from Test_lzy.practice1 import TestAdd suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestAdd)) runner = unittest.TextTestRunner() runner.run(suite)
练习 - 对login函数的测试
-
题目
#假设对某网站的登录进行测试 不要写成登陆 def login(username,password) : if username == 'admin' and password == '123456' : return '登录成功‘ else: return '登录失败' # 1.这个是开发书写的代码功能,不要修改我的 login函数 # 2.可以认为这函数就是tpshop登录 设计测试数据: 正确的用户名和密码:admin,123456,登录成功 错误的用户名: root,123456,登录失败 错误的密码: admin,123123,登录失败 错误的用户名和错误的密码: aaa,123123,登录失败
-
代码
import unittest from Test_lzy.Tools import login class TestLogin(unittest.TestCase): def test_username_password_ok(self): """正确的用户名和密码 admin 123456""" if login('admin', '123456') == '登录成功': print('pass') else: print('fail') def test_username_error(self): """不正确的用户名 root 123456""" if login('root', '123456') == '登录失败': print('pass') else: print('fail') def test_password_error(self): """不正确的密码 admin 123123""" if login('admin', '123123') == '登录失败': print('pass') else: print('fail') def test_username_password_error(self): """不正确的用户名和密码 aaa 123123""" if login('aaa', '123123') == '登录失败': print('pass') else: print('fail')
TestLoader(测试加载)
TestLoader(测试加载),作用和TestSuite 的作用是一样的,对TestSuite 功能的补充,用来组装测试用例的
比如:如果Testcase 的代码文件有很多,(10,20,30 )
-
使用步骤
- 导包
- 实例化测试加载对象并添加用例 ---> 得到的是 suite 对象
- 实例化 运行对象
- 运行对象执行套件对象
-
代码实现
在一个项目中 Testcase(测试用例)的代码,一般放在一个单独的目录(case)
"""Testloader的使用""" # 1.导包 import unittest # 2.实例化对象 # unittest.TestLoader().discover('用例所在的路径','用例的代码文件名') # 用例所在的路径,建议使用相对路径,用例的代码文件名可以使用 * (任意多个任意字符)通配符 suite = unittest.TestLoader().discover('./case', '*case*.py') # 3.实例化运行对象 runner = unittest.TextTestRunner() # 4.执行 runner.run(suite) # 可以将 3 4 步合为一步 unittest.TextTestRunner().run(suite)
#1.导包 #2.使用默认的加载对象并加载用例 #3.实例化运行对象并运行 """TestLoader 的使用""" #1,导包 import unittest # 2,使用默认的加载对象并加载用例 suite = unittest.defaultTestLoader.discover( 'case ', '*case*.py ') # 可以将 3 4 步变为一步 unittest.TextTestRunner( ).run(suite)
Fixture(测试夹具)
Fixture(测试夹具)是一种代码结构
在某些特定的情况下会自动执行
方法级别 [ 掌握 ]
-
在每个测试方法(用例代码)执行前后都会自动调用的结构
# 方法执行之前 def setUp(self): 每个测试方法执行之前都会执行 pass # 方法执行之后 def tearDown(self) 每个测试方法执行之后都会执行 pass
类级别 [ 掌握 ]
-
在每个测试类中所有方法执行前后都会自动调用的结构 (在整个类中 执行之前 执行之后 各一次 )
# 类级别的Fixture方法,是一个 类方法 # 类中所有方法之前 @classmethod def setUpClass(cls): pass # 类中所有方法之后 @classmethod def tearDownClass(cls): pass
模块级别 [ 了解 ]
模块:代码文件
在每个代码文件执行前后执行的代码结构
# 模块级别的需要写在类的外边直接定义函数即可
# 代码文件之前
def setUpModule():
pass
# 代码文件之后
def tearDownModule():
pass
- 方法级别和类级别的前后的方法,不需要同时出现,根据用例代码的需要自行的选择使用
案例
1.打开浏览器(整个测试过程中就打开一次浏览器)类级别
2.输入网址(每个测试方法都需要一次)方法级别
3.输入用户名密码验证码点击登录(不同的测试数据)测试方法
4.关闭当前页面(每个测试方法都需要一次)方法级别
5.关闭浏览器(整个测试过程中就关闭一次浏览器)类级别
------
1.打开浏览器(整个测试过程中就打开一次浏览器)类级别
2.输入网址(每个测试方法都需要一次)方法级别
3.输入用户名密码验证码点击登录(不同的测试数据)测试方法
4.关闭当前页面(每个测试方法都需要一次)方法级别
2.输入网址(每个测试方法都需要一次)方法级别
3.输入用户名密码验证码点击登录(不同的测试数据)测试方法
4.关闭当前页面(每个测试方法都需要一次)方法级别
2.输入网址(每个测试方法都需要一次)方法级别
3.输入用户名密码验证码点击登录(不同的测试数据)测试方法
4.关闭当前页面(每个测试方法都需要一次)方法级别
5.关闭浏览器(整个测试过程中就关闭一次浏览器)类级别
-
代码
import unittest class TestLogin(unittest.TestCase): def setUp(self): """每个测试方法执行之前都会先调用的方法""" print('输入网址') def tearDown(self): """每个测试方法执行之后都会调用的方法""" print('关闭当前页面') @classmethod def setUpClass(cls) -> None: print('-----1.打开浏览器') @classmethod def tearDownClass(cls) -> None: print('-----5.关闭浏览器') def test_1(self): print('输入正确用户名密码验证码,点击登录 1') def test_2(self): print('输入错误用户名密码验证码,点击登录 2')
断言
-
让程序代替人工自动的判断预期结果和实际结果是否相符
让程序代替人工自动的判断预期结果和实际结果是否相符. 断言的结果有两种: >True,用例通过 > False,代码抛出异常,用例不通过 在unittest中使用断言,都需要通过self.断言方法来试验
assertEqual
self.assertEqual(预期结果,实际结果) #判断预期结果和实际结果是否相等
1.如果相等,用例通过
2.如果不相等,用例不通过,抛出异常
assertIn
self.assertIn(预期结果,实际结果)#判断预期结果是否包含在实际结果中
1.包含,用例通过
2.不包含,用例不通过,抛出异常
assertIn( ' admin' , 'admin' ) # 包含
assertIn( ' admin', 'adminnnnnnnn ') # 包含
assertIn( ' admin' , 'aaaaaadmin' ) # 包含
assertIn( ' admin', 'aaaaaadminnnnnnn') # 包含assertIn( 'admin' , 'addddddmin' ) #不是包含
参数化
参数化在测试方法中,使用 变量 来代替具体的测试数据,然后使用传参的方法将测试数据传递给方法的变量
好处:相似的代码不需要多次书写.
工作中场景:
1.测试数据一般放在json文件中
2.使用代码读取json文件,提取我们想要的数据--->[(),()] or [[],[]]
安装插件
-
unittest 框架本身是 不支持 参数化,想要使用参数化,需要安装插件来完成
- 联网安装(在 cmd 窗口安装 或者 在 pycharm 的终端窗口) pip install parameterized
-
pip
- pip 是 Python 中包 ( 插件)的管理工具,使用这个工具下载安装插件
-
验证
pip list # 查看到parameterized
参数化代码
-
1.导包unittest/ pa
2.定义测试类
3.书写测试方法(用到的测试数据使用变量代替)
4.组织测试数据并传参
-
代码
# 1.导包unittest/ pa import unittest from parameterized import parameterized from Test_lzy.Tools import login # 组织测试数据并传参 [(),(),()] or [[],[],[]] data = { ('admin', '123456', '登录成功'), ('root', '123456', '登录失败'), ('admin', '123123', '登录失败'), ('aaa', '123123', '登录失败') } # 2.定义测试类 class TestLogin(unittest.TestCase): # 3.书写测试方法(用到的测试数据使用变量代替) @parameterized.expand(data) def test_login(self, username, password, expect): self.assertEqual(expect, login(username, password)) # 4.组织测试数据并传参 (装饰器 @)
跳过
- 对于一些未完成的或者不满足测试条件的测试函数和测试类,不想执行,可以使用跳过使用方法,装饰器完成
- 代码书写在 TestCase 文件
# 直接将测试函数标记成跳过
@unittest.skip('跳过的原因')
# 根据条件判断测试函数是否跳过 , 判断条件成立,跳过
@unittest.skipIf(判断条件,'跳过原因')
-
代码
import unittest version = 30 class TestDemo(unittest.TestCase): @unittest.skip('不想执行') def test_1(self): print('测试方法 1') @unittest.skipIf(version >= 30, '版本大于等于30,不用测试') def test_2(self): print('测试方法 2') def test_3(self): print('测试方法 3')
测试报告
自带的测试报告
- 只有单独运行TestCase 的代码,才会生成测试报告
生成第三方的测试报告
- 获取第三方的 测试运行类模块,将其放在代码的目录中
- 导包unittest
- 使用套件对象,加载对象去添加用例方法
- 实例化第三方的运行对象并运行套件对象
-
代码
# 1. 获取第三方的 测试运行类模块,将其放在代码的目录中 # 2. 导包unittest import unittest from HTMLTestRunner import HTMLTestRunner # 3. 使用套件对象,加载对象去添加用例方法 suite = unittest.defaultTestLoader.discover('.', 'practice1.py') # 4. 实例化第三方的运行对象并运行套件对象 HTMLTestRunner() # stream=sys.stdout, 必填, 测试报告的文件对象(open ),注意点,要使用wb打开 # verbosity=1, 可选,报告的详细程度,默认1简略,2详细 # title=None, 可选,测试报告的标题 # description = None 可选,描述信息,Python 的版本,pycharm版本 file = 'report2.html' # 报告的后缀是html with open(file, 'wb')as f: runner = HTMLTestRunner(f, 2, '测试报告', 'python 3.6.8') # 运行对象 # 运行对象执行套件,要写在with的缩进中 runner.run(suite)
-
流程
1.组织用例文件(Testcase里边),书写参数化,书写断言(判断预期&实际),书写Fixture,书写跳过...如果单个测试测试文件,直接运行,得到测试报告; 如果有多个测试文件,需要组装运行生成测试报告 2.使用套件对象组装,或者使用加载对象组装 3.运行对象运行 runner.run 3.1 运行对象 = 第三方的运行类(文件对象(打开文件需要使用wb方式 -- 二进制写, 写了wb就不要再写encoding了)) 3.2 运行对象.run(套件对象)