Python学习(五)类,文件,异常,代码测试

编程入门 行业动态 更新时间:2024-10-23 05:01:58

Python学习(五)类,文件,<a href=https://www.elefans.com/category/jswz/34/1771210.html style=异常,代码测试"/>

Python学习(五)类,文件,异常,代码测试

文章目录

  • Python学习(五)
    • 类(用法和Java基本相同)
      • 创建类
      • 根据类来创建实例
      • 给属性指定默认值
      • 修改属性的值
        • 直接通过实例修改属性的值
        • 通过方法修改属性的值
        • 通过方法对属性的值进行递增
      • 继承
        • 给子类定义属性和方法
        • 重写父类方法
        • 将实例用作属性
      • 导入类
        • 导入单个类
        • 在一个模块中导入多个类
        • 导入整个模块
    • Python标准库
    • 类编码风格
    • 文件和异常
      • 从文件中读取数据
        • 读取整个文件
        • 修改文件路径
        • 逐行读取
        • 创建一个包含文件各行内容的列表
      • 写入文件
        • 写入空文件
        • 写入多行
        • 附加到文件
      • 异常
        • `ZeroDivisionError`异常
        • else代码块
        • `FileNotFoundError`异常
        • pass占位符
      • 存储数据
        • 使用`json.dump()`和`json.load()`
        • 重构
    • 代码测试
      • 单元测试和测试用例
      • 不能通过的测试
      • 测试类
        • 各种断言方法
        • 一个用于测试的类
        • 对上述类进行测试
        • `setUp()`方法

Python学习(五)

在本节我们将学习完毕Python的基础语法。

类(用法和Java基本相同)

面向对象编程是最有效的编程方法之一,在面向对象编程时,可以编写表示现实世界中的实物和情境的类,并给予这些类来创建对象。在编写类时,我们往往会定义一大类对象通有的行为。在基于类创建对象时,每个对象自动具备这种通用行为,然后再根据需要赋予每个对象独特的个性。

根据类来创建对象被称为实例化,这让我们能够使用类的实例。

创建类

首先我们来创建一个Dog类:

class Dog():"""依次模拟小狗的简单尝试"""def __init__(self, name, age):"""初始化姓名,年龄"""self.name = nameself.age = agedef sit(self):"""模拟小狗被命令时蹲下"""print(self.name.titile() + " is now sitting")def eat(self):"""模拟小狗吃东西"""print(self.name.titile() + " is now eating")

在第一行,我们定义了一个名为Dog的类,在Python中类的名字的首字母大写。后边跟上括号和冒号,冒号后边具有缩进的被称为类的内容。

类中的函数被称为方法,我们学过的有关函数的都适用于方法,目前唯一有区别的地方在于函数和方法的调用形式不同。方法__init__()是一个特殊的方法,每当我们创建Dog类的一个实例时,Python都会自动运行他它。这个方法名称开头和结尾各有两个下划线,意在和其他方法进行区分。

方法__init__()定义成了包含三个形参:self、name和age,在这个方法定义中self必不可少,且必须放在所有形参的前边,Python在调用方法__init__()来创建实例时,会自动传入参数self,每个与类相关联的方法在调用时也会自动传递实参self,它能够让实例访问类中的属性和方法。

就用法来说,感觉于Java的构造方法非常类似

第五句和第六句定义的两个变量都有前缀self,以self为前缀的变量可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。self.name = name获取到存储在形参中的值,然后将其保存在name中。

此外该类还定义了两个方法,sit()eat(),这些方法都不需要额外的信息,因为它只有一个形参self。我们后面将创建的实例能访问这些方法。

根据类来创建实例

我们使用上述的类来讲解如何创建实例:

"""
在下边的代码中我们创建了一个名字为willie,年龄为6的小狗,遇到这个代码,Python使用实参willie和6调用Dog类中的方法__init__(),这个方法创建了一个特定小狗的示例,并使用我们提供的属性值来设置name和age。
"""
my_dog = Dog("willie", 6)#我们可以通过类名.属性名的方式来访问实例中的属性、
print(my_dog.name)			#willie
print(my_dog.age)			#6#我们同样可以使用句点表示法来调用Dog类中定义的任何方法
my_dog.sit()				#Willie is now sitting
my_dog_eat()				#Willie is now eating#可以按照需求创建任意数量的实例,下边再创建一个your_dog的实例,这个实例能像my_dog一样使用属性方法。
your_dog = Dog("lucy", 5)

给属性指定默认值

下边先编写一个汽车类,其中包含了汽车的有关信息

class Car:"""一个汽车类"""def __init__(self, make, module, year):"""描述汽车的初始属性"""self.make = makeself.module = moduleself.year = yearself.odometer_reading = 0def get_descriptive_name(self):"""返回汽车的描述信息"""name = str(self.year) + " " + self.make + " " + self.modulereturn namedef read_odometer(self):print(self.odometer_reading)

类中的每个属性都必须由初始值,哪怕这个值是0或者是空字符串,如果想要给某个属性设置默认值,可以在__init__方法中设置,这个时候我们就不需要为这个属性提供为其提供初始值的形参了。如上属性odometer(里程),我们设置其属性为0,就没有在__init__方法中为其设置形参。

这时候我们可以先尝试创建一个汽车的实例,并且使用各个属性和方法:

my_car = Car("Audi", "A4", 2016)
print(my_car.get_descriptive_name())		#2016 Audi A4
my_car.read_odometer()						#0

修改属性的值

可以使用三种方法修改属性的值:直接通过实例修改;通过方法进行设置;通过方法增加特定的值。

直接通过实例修改属性的值

要修改属性的值,最好的办法是通过实例直接访问它,下边的代码将里程(odometer)属性的值设置为50:

my_car = Car("Audi", "A4", 2016)
my_car.odometer_reading = 50
my_car.read_odometer()				#50

通过方法修改属性的值

如果我们设置了修改属性的方法,那么我们就不需要通过实例俩访问属性,直接将值传递给一个方法,并在内部对方法进行更新,我们在上边的Car类中增加新的update_odometer函数来实现更新里程的值,同时我们对该方法进行扩展来禁止有人将里程表的数回调:

    def update_odometer(self, mileage):if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("你不能回调里程数")my_car = Car("Audi", "A4", 2016)
my_car.update_odometer(50)
my_car.read_odometer()				#50

通过方法对属性的值进行递增

有时候需要将属性值递增特定的量,比如我们买了一辆二手车,从购买到登记增加了100公里的里程,我们可以使用increase_odometer函数,来实现里程数的递增:

    def increase_odometer(self, miles):self.odometer_reading += milesmy_car = Car("Audi", "A4", 2016)
my_car.update_odometer(10000)
my_car.read_odometer()						#10000
my_car.increase_odometer(100)
my_car.read_odometer()						#10100

继承

编写类时,我们并非总要从空白开始,如果要编写的类于另一个已经有的类很相似,我们可以采用继承的方法。一个类继承另一个类时,他会获得另一个类的所有属性和方法,原有的类被称为父类,而新类被称为子类。子类继承其父类的所有属性和方法,同时还可以编写自己的属性和方法。

在上边的Car类的基础上,我们创建子类电动汽车Electric_Car,它具备Car类的所有属性和方法:

class Car:"""一个汽车类"""def __init__(self, make, module, year):"""描述汽车的初始属性"""self.make = makeself.module = moduleself.year = yearself.odometer_reading = 0def get_descriptive_name(self):"""返回汽车的描述信息"""name = str(self.year) + " " + self.make + " " + self.modulereturn namedef read_odometer(self):print(self.odometer_reading)def update_odometer(self, mileage):if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("你不能回调里程数")def increase_odometer(self, miles):self.odometer_reading += milesclass Electric_Car(Car):"""电动汽车类,继承了Car类"""def __init__(self, make, module, year):"""c初始化父类属性"""super().__init__(make, module, year)my_Electric_Car = Electric_Car("tesla", "s", "2018")
print(my_Electric_Car.get_descriptive_name())			#2018 tesla s

在创建子类时,父类必须包含在当前文件中,并且要位于子类的前边在第29句,我们定义子类时一定要在括号里边指明父类的名称,表示继承关系。

第33句的super()是一个特殊函数,帮助Python将子类和父类关联起来,这行代码让Electric_Car调用父类的__init__方法,能够保证Electric_Car的实例包含父类的全部属性。

在后边我们对继承结果进行了测试,可以发现,我们能够使用父类的属性,并且使用其中的方法。

给子类定义属性和方法

在一个类继承另一个类后,我们可以在子类中添加属性和方法以此来和父类进行区分,如下,我们在子类中创建了电动汽车的特有属性,battery(电量),并且增加了特有方法打印当前的电量。

class Electric_Car(Car):"""电动汽车类,继承了Car类"""def __init__(self, make, module, year):"""c初始化父类属性"""super().__init__(make, module, year)self.battery = 80def describe_battery(self):print("当前的电量是: " + self.battery)my_Electric_Car = Electric_Car("tesla", "s", "2018")
print(my_Electric_Car.get_descriptive_name())
my_Electric_Car.describe_battery()			#当前的电量是: 80

在创建电动汽车子类时,我们可以根据需要添加任意数量的属性和方法,但是如果有属性或者方法是父类或者子类共有的时候,我们就用该将其添加到父类中。

重写父类方法

对于父类的方法,只要不符合子类的行为,都可以对其进行重写。为此,可以在子类中定义一个方法,它要与重写的父类方法同名,这样在调用时,Python不会考虑这个父类方法,而只关注在子类中定义的相应的方法。比如在下边我们重写Car类的get_descriptive_name()方法:

    def get_descriptive_name(self):name="制造时间是:"+self.year+"\t制造商是:"+self.make+"\t型号是:"+self.modulereturn namemy_Electric_Car = Electric_Car("tesla", "s", "2018")
print(my_Electric_Car.get_descriptive_name())#制造时间是:2018	制造商是:tesla	型号是:s

如果有人对电动汽车调用get_descriptive_name()方法,Python会忽略Car类的get_descriptive_name()方法,转而使用Electric_Car类中的get_descriptive_name()方法。

将实例用作属性

在用代码模拟实物的时候,我们会发现自己给类添加的细节越来越多,属性和方法也越来越多,在这种情况下,可以将类的一部分作为一个独立的类提取出来。

例如在电动汽车类中,我们将描述电瓶的类提取出来,放到名为Battery的类中,并将其实例作为Electric_Car类的一个属性。

class Battery:"""模拟电动车电瓶"""def __init__(self, battery_size=70):self.battery_size = battery_sizedef describe_battery(self):"""打印电瓶的描述信息"""print("电瓶的容量是:" + str(self.battery_size))class Electric_Car(Car):"""电动汽车类,继承了Car类"""def __init__(self, make, module, year):"""c初始化父类属性"""super().__init__(make, module, year)self.battery = Battery()my_Electric_Car = Electric_Car("tesla", "s", "2018")
print(my_Electric_Car.get_descriptive_name())		#2018 tesla s
my_Electric_Car.battery.describe_battery()			#电瓶的容量是:70

在这里我们定义了一个名为Battery的新类,__init__方法包括了设置默认值的形参battery_size,如果没有给他提供值他就会使用默认值70。

在Electric_Car类中,我们定义了self.battery的属性,这个代码让Python创建一个新的Battery实例,并将实例存储在属性self.battery中。从最后一个输出语句我们可以看出,我们使用的是my_Electric_Car.battery.describe_battery()这个句点表示法语句进行输出,先使用Electric_Car的属性battery调用Battery类的实例,再通过实例调用Battery类的方法,这也证明了我们将实例用作属性。

导入类

Python允许将类存储在模块,然后再主程序中导入模块。

导入单个类

首先先创建一个只包含单个类的模块,将其命名为car.py,下边是该模块中的主要代码:

class Car:"""一个汽车类"""def __init__(self, make, module, year):"""描述汽车的初始属性"""self.make = makeself.module = moduleself.year = yearself.odometer_reading = 0def get_descriptive_name(self):"""返回汽车的描述信息"""name = str(self.year) + " " + self.make + " " + self.modulereturn namedef read_odometer(self):print(self.odometer_reading)def update_odometer(self, mileage):if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("你不能回调里程数")def increase_odometer(self, miles):self.odometer_reading += milesclass Electric_Car(Car):"""电动汽车类,继承了Car类"""def __init__(self, make, module, year):"""c初始化父类属性"""super().__init__(make, module, year)self.battery = 80def describe_battery(self):print("当前的电量是: " + self.battery)

之后我们创建一个新的文件,命名为my_car.py

from car import Carmy_new_car = Car("audi", "a4", 2016)
print(my_new_car.get_descriptive_name())			#2016 audi a4my_new_car.odometer_reading = 23
my_new_car.read_odometer()							#23

上述第一句话,我们从car.py中导入Car类,之后就可以使用Car类了,就像再这个文件中定义一样。

在一个模块中导入多个类

可以根据需要在程序文件中导入任意数量的类,我们在car.py中存储了两个类,一个是Car类,一个是Electric_Car类,现在我们可以将两个类导出:

from Variable import Car, Electric_Carmy_new_car = Car("audi", "a4", 2016)
print(my_new_car.get_descriptive_name())				#2016 audi a4my_new_car.odometer_reading = 23						#23
my_new_car.read_odometer()my_battery_car = Electric_Car("tesla", "s", "2017")	
print(my_battery_car.get_descriptive_name())			#2017 tesla s
my_battery_car.describe_battery()						#当前的电量是: 80	

导入整个模块

还可以直接导入整个模块,再使用句点表示法访问需要的类,这种导入方式很简单,代码也容易阅读。

import Variablemy_new_car = Variable.Car("Audi", "A4", "2016")
print(my_new_car.get_descriptive_name())			#2016 Audi A4my_new_car.odometer_reading = 23
my_new_car.read_odometer()							#23

导入模块中的所有类:

from car import *

不推荐使用上述代码导入,因为我们在导入时,往往希望知道程序具体使用了哪些类,这种方式不仅没有指出导入了哪些类,还可能引起类名称的困惑。

Python标准库

Python标准库是一组模块,在安装Python时都会包含。现在简单介绍模块collections中的一个类——OrderedDict,该类的实例与字典几乎相同,区别在于该类记录了键值对的添加顺序,确保输出时是按照键值对的添加顺序进行输出:

from collections import OrderedDictfavorite_language = OrderedDict()favorite_language["jane"] = "C"
favorite_language["jack"] = "Java"
favorite_language["TOM"] = "Python"
favorite_language["Maria"] = "C"for name, language in favorite_language.items():print(name.title() + " " + language)'''
Jane C
Jack Java
Tom Python
Maria C
'''

需要注意的是,这里没有使用花括号,而是使用OrderedDict()来创建一个空白字典。

类编码风格

类名应采用驼峰命名法,即类名中的每个单词的首字母大写,而不使用下划线。

对于每个类,都应该在后边加上文档注释,说明类的作用,每个模块也都应该包含一个文档字符串,对其中的类的作用进行描述。

在类中往往使用一个空行分隔方法,而在模块中用两个空行分隔类。

需要同时导入标准库和自己的模块时,先编写标准库的import语句,再添加一个空行,编写自己导入的import语句。

文件和异常

从文件中读取数据

文本文件可以存储大量的信息,我们可以通过读取文件来分析和修改存储在文件中的信息,也可以重新重新设置数据的格式并将其写入文件。

如果要使用文本文件中的信息,首先要将信息读取在内存中,为此我们既可以一次性读取文件的全部内容,也可以按照一次一行的方式读取。

读取整个文件

要读取文件,首先需要一个包含几行文本的文件。首先来创建一个文件,它包含精确到小数点后30位的圆周率,且在小数点后每10位出换行。

3.141592653589793238462643383279

接下来编写代码,打开并读取文件,将其内容显示到屏幕上。

with open("pi.txt") as file_object:contents = file_object.read()print(contents.rstrip())'''
3.141592653589793238462643383279
'''

如果想要以任何方式使用文件,都必须先打开文件。函数open()接收一个参数:要打开的文件的名称,Python会在当前执行的文件的的目录中查找指定文件(运行open函数的代码再哪个文件夹,就会在哪个文件夹搜索指定文件)。。函数open()会返回一个表示文件的对象,Python将这个对象存储在后边的变量中。

关键字with在我们不需要访问文件后将其关闭,在有了pi.txt文件的对象后,我们就可以使用方法read()读取文件的全部内容,并将其作为一个长长的字符串存在contents中。

上述代码在输出时使用了contents.rstrip(),这是因为read()函数在读到文件末尾时会返回一个空字符串,而将这个空字符串显示出来就是一个空格。

修改文件路径

上边我们使用ope()函数时,打开的是当前执行文件所在目录的文件,但是有时候,我们可能要打开不再当前目录中的文件,这个时候我们推荐使用绝对路径来传递文件的路径,在windows环境下,如下:

with open("E:\\PythonLearning\\pi.txt") as file_object:contents = file_object.read()print(contents)

这里推荐用两个反斜杠来分隔路径,因为一个反斜杠可能会和后边的路径或者文件名组成转义字符,导致程序出现错误。

逐行读取

读取文件时,常常需要检查其中一行,比如在其中查找特定的信息,或者修改其中的文本。要以按照每一行的方式进行检查,可以对文件对象使用for循环:

with open("E:\\PythonLearning\\pi.txt") as file_object:for line in file_object:print(line)'''
3.141592653589793238462643383279
''''''

我们可以看出每一行输出之后都会再多出来一个空行,这是因为每行的末尾都有一个看不见的换行符,而print语句会加上这个换行符,因此每行末尾都有两个换行符,一个来自print语句,一个来自文件本身,要消除空白行,再输出时可以使用rstrip()

with open("E:\\PythonLearning\\pi.txt") as file_object:for line in file_object:print(line.rstrip())'''
3.141592653589793238462643383279
'''

创建一个包含文件各行内容的列表

在使用关键字with时,open()返回的文件对象只能在with代码块中使用,如果要在with代码块外访问文件内容,可以将文件内容存储在列表中:

with open("E:\\PythonLearning\\pi.txt") as file_object:lines = file_object.readlines()for line in lines:print(line.rstrip())

方法readlines()从文件中读取每一行并将其存储在列表中,之后该列表被存储到变量lines中。

写入文件

保存数据最简单的方式之一是将其写入文件,通过将输出写入文件,即便关闭程序终端窗口,这些输出也依然存在。

写入空文件

要将文本写入文件,在调用open()时提供另一个实参,告诉Python你要打开的文件。

#注意修改文件路径with open("E:\\PythonLearning\\pi.txt", "w") as file_object:file_object.write("I love Python")

在这里调用open时提供了两个实参,第一个实参是需要写入的文件的名称;第二个实参"w"告诉Python我们要以写入的方式打开文件。打开文件时可以指定读取模式(“r”),写入模式(“w”),附加模式(“a”),以及能够读取和写入文件的模式(“r+”),如果我们省略了模式实参,Python将以只读模式打开文件。

如果要写入的文件不存在,函数open()将自动创建它,然而,以(“w”)模式打开文件时,如果指定的文件已经存在,Python将在返回文件对象前清空该文件。

Python只能将字符串写入文本文件,如果想要将数值数据存储到文本文件,需要先使用str()函数将其转换为字符串格式

写入多行

函数write()不会再写入的文本末尾添加换行符,因此如果写入多行时没有指定换行符,文件可能和我们预想的排版不太一样。如果要让字符串单独占一行,需要在write()语句中包含换行符。

#注意修改文件路径with open("E:\\PythonLearning\\pi.txt", "w") as file_object:file_object.write("I love Python\n")file_object.write("I also love Java\n")

我们还可以使用空格,制表符和空行来设置不同的输出样式。

附加到文件

如果要给文件添加内容,而不是覆盖原有的内容,可以使用附加模式打开文件,Python不会再返回对象前清空文件,而我们写入到文件的内容都会添加到文件末尾,如果指定的文件不存在,Python会为你创建一个空文件。

#注意修改文件路径with open("E:\\PythonLearning\\pi.txt", "a") as file_object:file_object.write("I love Php\n")file_object.write("I also love go\n")

异常

Python使用称为异常的特殊对象来管理程序执行期间发生的错误,每当程序出现错误时,它都会创建一个异常对象,如果我们编写了处理异常的代码,那么程序会继续运行,否则程序就会停止,返回一个traceback。

ZeroDivisionError异常

我们知道一个数字不能除以0,但是我们如果还要让Python这么做呢:

print(5 / 0)'''
Traceback (most recent call last):File "E:/PythonLearning/Test.py", line 1, in <module>print(5 / 0)
ZeroDivisionError: division by zero
'''

在上述的traceback中,第七行指出了错误是ZeroDivisionError是一个异常对象,Python无法按照我们的要求做时,就会创建这种对象,并指出发生了哪种异常,这时候我们就可以根据反馈修改代码。

使用try-except代码块

处理ZeroDivisionError异常的try-except代码类似于下边这样:

try:print(5 / 0)
except ZeroDivisionError:print("0不能作为除数")

我们将导致错误的代码放在一个try的代码块中,如果try中的代码运行起来没有问题,Python将会跳过except代码块,如果try中的代码块导致了错误,并且错误的类型和except声明的错误类型相同,那么就会运行except中的代码。

else代码块

依赖try代码成功执行的代码都应该放在else代码中:

print("请以此输入两个数,并对他们执行除法")
print("输入q来退出程序")while True:first_number = input("请输入被除数:")if first_number == "q":breaksecond_number = input("请输入除数:")if second_number == "q":breaktry:answer = int(first_number) / int(second_number)except ZeroDivisionError:print("你不能除以0")else:print(answer)"""
请以此输入两个数,并对他们执行除法
输入q来退出程序
请输入被除数:9
请输入除数:0
你不能除以0
请输入被除数:9
请输入除数:3
3.0
请输入被除数:q
"""

try-except-else代码块的工作原理大致如下:Python尝试执行try中的代码块,只有可能引发异常的语句才会放在try语句中,有一些仅在try代码块成功运行时才需要运行的代码应放在else代码块中。except代码块告诉Python,如果运行时出现了指定的异常应该怎么办。

FileNotFoundError异常

使用文件时,一种常见的问题就是找不到文件,你要使用或者查找的文件可能在其他地方,文件名不正确,或者文件根本不存在,这种情况下我们也可以使用和try-exception解决。

pass占位符

我们并非每次捕获到异常时都要告诉用户,有时候我们希望出现错误时,程序会像什么都没有发生一样继续运行。可以正常编写try代码块,但是在except代码块中明确的告诉Python什么都不要做。Python中有一个pass语句,可以在代码块中使用它告诉Python什么也不做。

try:answer = 5 / 2answer_1 = 5 / 0
except ZeroDivisionError:pass

如上,这时候程序在出现异常时不会有任何提示信息,在有时候pass会很有用。

pass语句还充当占位符的作用,它会提醒你在程序的某个地方什么没有做,并且以后需要在这里做些什么。

存储数据

我们在多数情况下都会需要用户输入某些信息,然后我们再将用户的信息存储在列表和字典等数据结构中,用户关闭程序时,我们几乎总是要保存他们提供的信息,一种简单的方式是通过使用模块json来保存信息:
模块json可以将简单的Python数据结构存储到文件中,并在程序再次运行时加载文件中的数据。

使用json.dump()json.load()

我们将编写一个存储一组数字的简短程序,并在程序再次运行时加载该文件中的数据。

import jsonnumbers = [2, 4, 6, 8, 10]file_name = "number.json"
with open(file_name, "w") as f_obj:json.dump(numbers, f_obj)

通常使用文件扩展名.json来指出文件存储的数据位json格式。接下来我们以写入模式打开文件,让json能够将数据写入其中,再第七行,我们使用函数json.dump()将数字列表存储到文件中。

这个程序没有输出,但是我们可以打开文件number.json来查看文件中的内容。

import jsonfile_name = "number.json"
with open(file_name) as f_obj:numbers = json.load(f_obj)print(numbers)			#[2, 4, 6, 8, 10]

这里我们需要确保我们读取的是之前写入的文件,我们使用函数json.load()加载存储在numbers.json中的信息,并将其存储在numbers,然后对numbers进行打印。

重构

有时候代码能够正确的运行,但是我们能够对代码做进一步的改进——将代码分为一些列完成具体工作的函数,这的过程被称为重构,重构可以让代码更加清晰,易于扩展。

具体来说可以将同时实现多个功能的程序分割为不同的函数,每个函数实现一个功能,最终将函数合并实现原有的功能。

代码测试

编写函数或者类时,还可以为其编写测试。通过测试,可以确定代码面对各种输入都能够按要求进行工作。

单元测试和测试用例

Python标准库中的模块unittest提供了代码测试的工具。单元测试用于核实函数的某个方面没有问题,测试用例是一组单元测试,用于核实函数在一系列下的行为都符合要求。全覆盖测试用例包括一整套单元测试,涵盖了各种可能的函数使用方式。

首先我们指定需要测试的函数,并且将该函数保存在name_function.py中:

def get_formatted_name(first, last):"""格式化的输出姓名"""full_name = first + " " + lastreturn full_name.title()

接下来编写测试函数:

import unittest
from name_function import get_formatted_nameclass NameTestCase(unittest.TestCase):"""测试name_function.py"""def test_first_last_name(self):"""能正确处理类似Talor Swift这样的名字吗"""formatted_name = get_formatted_name("talor", "Swift")self.assertEqual(formatted_name, " Talor Swift")unittest.main()'''
.
----------------------------------------------------------------------
Ran 1 test in 0.000sOK
'''

首先我们导入了模块unittest和要测试的函数,在第五行,我们创建了一个类,用于包含一系列针对get_formatted_name()的测试,这个类可以随意命名,但是最好让他看起来和要测试的函数有关,并包含Test字样,同时,该类必须继承unittest.TestCase,这样Python才知道如何运行编写的测试。

NameTestCase只有一个方法,用于测试get_formatted_name(),我们将这个方法命名为test_first_last_name(),在运行这个Python文件时,所有以test开头的方法都会自动运行,在这个方法中,我们调用了要测试的函数,并存储了要测试的返回值。在实例中,我们用 formatted_name的值和Talor Swift进行比较。

在第十一行,我们使用了unittest类最重要的功能之一,断言方法。断言方法是用来核实得到的结果与期望是否一致,并返回比较结果。

代码行unittest.main()让Python运行这个文件中的测试。

不能通过的测试

接下来我们修改get_formatted_name()使其能够处理中间名:

def get_formatted_name(first, middle, last):"""格式化的输出姓名"""full_name = first + " " + middle + " " + lastreturn full_name.title()'''
E
======================================================================
ERROR: test_first_last_name (__main__.NameTestCase)
能正确处理类似Talor Swift这样的名字吗
----------------------------------------------------------------------
Traceback (most recent call last):File "E:/PythonLearning/Test.py", line 10, in test_first_last_nameformatted_name = get_formatted_name("talor", "Swift")
TypeError: get_formatted_name() missing 1 required positional argument: 'last'
'''

但是修改完我们再对代码进行测试时,会发现代码出现错误了。

在上述的第8行,E提示测试用例中有一个单元测试出现了错误。

第10行看到NameTestCase中的test_first_last_name 导致了错误,测试用例包含众多测试单元时,知道哪个测试未通过至关重要。

接下来我们看到了标准的traceback,告诉我们调用get_formatted_name()有问题,因为缺少一个实参。

如果测试通过了,意味着函数的行为是正确的。但是当测试没有通过时,意味着我们编写的新的代码有问题,我们应该修复导致测试不通过的代码,而不是去修改测试。

测试类

各种断言方法

Python在unittest中提供了很多断言方法,断言方法用于检测你认为应该满足的条件是否满足。如果确实满足了,我们就可以认为程序没有错误,如果没有满足,程序就会发生异常。

下表给出了常用的6个断言方法,使用这些方法可以核实返回的值是否等于或者不等于预期的值,返回的值为True或False,返回的值是否在列表中:

方法用途
assertEqual(a, b)核实 a == b
assertNotEqual(a, b)核实 a != b
assertTrue(x)核实x为True
assertFalse(x)核实x为False
assertIn(item, list)核实item在list中
assertNotIn(item, list)核实item不再list中

一个用于测试的类

首先我们创建一个帮助管理匿名调查的类,并将类存储在模块survey中:

class AnonymousSurvey:"""收集匿名调查的问卷"""def __init__(self, question):"""存储一个问题,并未存储答案做准备"""self.question = questionself.responses = []def show_question(self):"""显示调查问卷"""print(self.question)def store_response(self, new_response):"""收集单份问卷"""self.responses.append(new_response)def show_results(self):"""显示收集到的所有问卷"""print("Survey results: ")for response in self.responses:print("- " + response)

接下来验证该类能正确运行:

from survey import AnonymousSurveyquestion = "你喜欢说什么语言"
my_survey = AnonymousSurvey(question)my_survey.show_question()
print("输入Q来退出")
while True:response = input("请输入喜欢的语言")if response == "Q":breakmy_survey.store_response(response)print("感谢调查")
my_survey.show_results()'''
你喜欢说什么语言
输入Q来退出
请输入喜欢的语言汉语
请输入喜欢的语言英语
请输入喜欢的语言Q
感谢调查
Survey results: 
- 汉语
- 英语
'''

对上述类进行测试

接下来白那些一个测试,对AnonymousSurvey类进行测试,首先我们验证只存储一个答案时,能正确进行输出。

import unittest
from Variable import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):"""针对AnonymousSurvey进行测试"""def test_store_single_response(self):"""测试单个答案会被妥善存储"""question = "你喜欢什么语言"my_survey = AnonymousSurvey(question)my_survey.store_response("汉语")self.assertIn("汉语", my_survey.responses)unittest.main()'''
.
----------------------------------------------------------------------
Ran 1 test in 0.000sOK
'''

从上可以看出,我们的测试通过了。此外,再提醒一下,测试的方法必须以test开头,否则就在测试时不会自动运行。

接下来我们验证存储多个答案时也能正确通过测试:

import unittest
from Variable import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):"""针对AnonymousSurvey进行测试"""def test_store_three_response(self):"""测试三个答案会被妥善存储"""question = "你喜欢什么语言"my_survey = AnonymousSurvey(question)responses = ["汉语", "英语", "日语"]for response in responses:my_survey.store_response(response)for response in responses:self.assertIn(response, my_survey.responses)unittest.main()'''
.
----------------------------------------------------------------------
Ran 1 test in 0.000sOK'''

setUp()方法

在前边的测试中,我们为每个测试都创建了一个AnonymousSurvey类的实例,并在每个方法都创建了答案。unittest.TestCase类中包含的方法setUp(),让我们可以只用创建这些对象一次,并在每个测试方法中使用它们。如果TestCase类中包含方法setUp(),Python会先运行它,再运行以test开头的方法,下边使用setUp()来创建一个调查对象和一组答案,供方法test_store_single_response()test_store_three_response()使用。

import unittest
from Variable import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):"""针对AnonymousSurvey进行测试"""def setUp(self):"""创建一个调查对象和一组答案,共使用的测试方法使用"""question = "你喜欢什么语言"self.my_survey = AnonymousSurvey(question)#加上self是为了保证在整个类中都可以使用变量self.responses = ["汉语", "英语", "日语"]def test_store_single_response(self):"""测试三个答案会被妥善存储"""self.my_survey.store_response(self.responses[0])self.assertIn(self.responses[0], self.my_survey.responses)def test_store_three_response(self):"""测试三个答案会被妥善存储"""for response in self.responses:self.my_survey.store_response(response)for response in self.responses:self.assertIn(response, self.my_survey.responses)unittest.main()'''
..
----------------------------------------------------------------------
Ran 2 tests in 0.000sOK
'''

测试自己编写的类时,方法setUp()让测试方法编写起来更容易,可以在setUp()方法中创建一系列实例并设置它们的属性,再在测试方法中使用这些实例,这种方式相比再每个测试中都创建实例要容易的多。

运行测试用例的时候,每完成一个单元测试,Python都会打印一个字符,测试通过打印一个句点,测试引发错误打印一个E,测试导致断言失败打印一个F。如果测试中包含很多单元测试,需要运行很久,可以通过观察这些结果来了解有多少测试通过了。

更多推荐

Python学习(五)类,文件,异常,代码测试

本文发布于:2023-07-28 17:49:13,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1267200.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:异常   代码   文件   测试   Python

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!