【Python教程】06.类与单元测试

大纲

面向对象

面向对象编程是一种程序设计思想。
咱们到目前为止所编写的程序,都是基于面向过程的。
image.pngpython

面向过程就是咱们在写程序过程当中,将需求拆成多个流程,每一个封装一个方法,逐个调用。
好比买菜洗菜炒菜装盘这四个步骤,咱们对于的方法就是buywashcookdish
若是需求有变化,如我还要买米、作饭,用铁锅炒菜,用微波炉作菜等等。这时候用面向过程就很是麻烦且会愈来愈乱。git

面向对象则是从另外一个角度来思考,它用对象来取代方法做为程序的基础
对象是对事物的抽象,如人能够抽象成名字、年龄以及能作的事情,这就是
咱们在程序设计时,就只要建立这个对象,让它来作它能作的事情
仍是买菜洗菜炒菜装盘这个四个步骤,如今咱们就能够有超市厨师两个对象。超市负责买菜,厨师负责洗菜、炒菜、装盘。
进一步厨师用什么洗菜,用什么作菜怎么作、怎么装盘又是一系列的对象来处理。
超市这个对象提供买米买菜买肉等等功能,必要时还能够继续添加
除了基本的功能外,沃尔玛和永辉卖的东西就有差异,即便一样是卖米也会有差别。
这就是继承,沃尔玛和永辉都是超市的继承,但并非相同的。github

class】就是对对象的描述
咱们要把人的名字和年龄以及作的事情用python的类写出来。编程

建立一个类class
咱们建立一个user包,在包里建立一个Person模块。在模块中编写Person类。
image.png框架

class Person:
    def __init__(self):
        pass

class后面就是类名Person,其下的代码块就是类的定义。类名使用驼峰写法
一个文件一个类,大部分状况咱们会这样作。Python中并不反对一个文件多个类
第一个在类中要定义的方法就是构造方法,方法名为__init__。这是类默认存在的方法。
构造方法里的第一个参数是self,表明对象本身默认类的方法第一个参数都是self单元测试

实例化:将类建立出来进行使用。
咱们建立一个main.py来实例化这个类。
image.png测试

from user import Person
p1 = Person.Person()
from user.Person import Person
p1 = Person()

上面两种方式是在包下的类实例化方式,与方法的调用时同样的。
对于类,因为一文件一类的原则,咱们更但愿能简单一点调用到包里的类。以下:spa

from user import Person
p1 = Person()
p2 = Person()

那么咱们就要在包的__init__.py文件里作文章了,写入设计

from .Person import Person

而后咱们就能够直接从包导入Person类了。3d

构造方法:用于初始化,每一个类被实例化时会默认调用的方法。

def __init__(self, name, age):
    self.name = name    # 名字
    self.age = age      # 年龄

咱们加入Person类有的数据,名字和年龄。
self表明本身,它出来的变量称为成员变量
self.nameself.age分别被赋值给构造方法传入的参数name和age,用于初始化

实例化并获取成员变量

p1 = Person('Green', 12)
print(p1.name, p1.age)

使用构造方法进行建立,而后直接用建立好的变量,点出成员变量便可。
成员变量原则上只能在构造方法声明

成员方法:构造方法实际上是一个特殊的成员方法。

def sleep(self, t):
    print('{} sleeps for {} seconds'.format(self.name, t))

定义了一个sleep方法,一样的第一个参数self,表明该方法是成员方法。
在成员方法内部,咱们就能够用self来得到成员变量
调用:一样使用的方式调用该方法

p2 = Person('Lucy', 14)
p2.sleep(10)

这时若是咱们须要在sleep前添加一步到床上的步骤,咱们能够这样作,
添加一个go2bed成员方法,并在sleep方法中用self调用这个成员方法

class Person:

    def __init__(self, name, age):
        self.name = name    # 名字
        self.age = age      # 年龄
        
    def sleep(self, t):
        self.go2bed()
        print('{} sleeps for {} seconds.'.format(self.name, t))
        
    def go2bed(self):
        print(self.name + ' go to bed.')

访问限制:对成员变量和成员方法进行限制,不让使用者能够直接使用。
成员变量做为一个类的数据,不该该能够直接被使用者修改,保证类被正确使用。
变量名用 两个下划线__】开头的就是被限制访问私有变量,只可内部访问

def __init__(self, name, age):
    self.__name = name    # 名字
    self.__age = age      # 年龄

调用时报错
image.png

若是须要获取这两个变量的值,或进行修改,咱们定义getset方法进行。

def get_age(self):
    return self.__age
    
def set_age(self, age):
    self.__age = age
    
def get_name(self):
    return self.__name

咱们但愿age能够进行设置,而name没有set方法。这样成员变量的任何修改都在咱们的控制范围内。

成员方法也使用两个下划线来作限制。
__go2bed就是只能内部访问的方法了。

class Person:
    
    def __init__(self, name, age):
        self.__name = name    # 名字
        self.__age = age      # 年龄
    
    def sleep(self, t):
        self.__go2bed()
        print('{} sleeps for {} seconds.'.format(self.__name, t))
        
    def __go2bed(self):
        print(self.__name + ' go to bed.')
        
    def get_name(self):
        return self.__name
        
    def get_age(self):
        return self.__age

主要目的是:
一、不但愿使用者调用到。
二、对用户提供的方法也能够叫作接口。接口就是使用者能够调用到的全部方法了。这样对于使用者来讲也是最好的。

继承

继承是在已有类【父类】的基础上,再编写一个子类,其拥有父类的全部内容。

from user import Person
class Student(Person):
    pass

在类名Student后面用括号抱起来的Person就是父类。
此时这个子类没有任何的代码,可是它已经继承了父类全部的内容,使用方法同样。

from user import Student
student1 = Student('Green', 12)
print(student1.get_name())

全部方法变量自动继承。

object类:默认的Python继承类。
没有作继承的类,其实都默认继承了python的object类【对象类】。

class Person(object):

object能够不写。

添加成员变量和方法:子类通常拥有比父类更强大的功能。
在Students类中,咱们添加一个grade变量来标识年级,并添加一个新方法。

class Student(Person):

    def __init__(self, name, age, grade=1):
        Person.__init__(self, name, age)    # 父类构造方法
        self.__grade = grade                # 年级
    
    def get_grade(self):
        return self.__grade

代码中的Person.__init__表示了咱们在新的构造方法中先调用了Person父类构造方法
这样咱们就减小了重复的代码。

复写成员方法修改父类的方法内容。
方法的复写必须保证方法名参数父类一致

def sleep(self, t):
    print('student ' + self.get_name())
    Person.sleep(self, t)           # 父类方法

经过调用父类的方法来扩充该方法。或者直接不调用父类方法所有重写也是能够的。

def sleep(self, t):
    print('student {} sleeps for {} seconds.'.format(self.__name, t))

子类的方法覆盖父类的方法,在被调用时就会运行子类方法。
复写实现了面向对象中多态的概念。

此时咱们发现代码报错了,缘由在于__name变量是只能Person类内部访问的成员变量。
只能使用self.get_name()来得到名字,子类要如何调用父类的私有变量?

继承中的访问限制
两个下划线使得该成员变量和方法只能在这个类中内部使用。
在继承中成员变量和方法除了不能被外部调用外,其子类应该是能够正常使用的。
使用一个下划线_】开头定义的成员变量和方法就能够实现。

def __init__(self, name, age):
    self._name = name    # 名字
    self._age = age      # 年龄

def _go2bed(self):
    print(self._name + ' go to bed.')

这样刚才的复写方法中就可使用了。

def sleep(self, t):
    print('student {} sleeps for {} seconds.'.format(self._name, t))

访问限制分类
public:公开权限,无下划线【self.name】,任何地方均可以访问修改。
protected:保护权限,单下划线【self._name】,只容许本身和其子类访问修改。
private:私有权限,双下划线【self.__name】,只容许本身访问修改。
根据咱们的须要,选择对应的权限进行开发。

多态

判断变量类型typeisinstance

p1 = Person('Green', 12)
s1 = Student('Green', 12)
print(type(s1))
print(type(p1))

输出:
image.png
type能够得到该变量的类型

print(isinstance(p1, Person))
print(isinstance(p1, Student))

输出:image.png
isinstance能够直接判断变量是否是某个类型
对于父类变量结果与下面的代码相同:

print(type(p1) == Person)
print(type(p1) == Student)

但对于子类,状况并不相同

子类的类型判断

type(s1) == Person          # False
type(s1) == Student         # True
isinstance(s1, Person)      # True
isinstance(s1, Student)     # True

咱们发现子类变量在type的相等判断中,并不等于父类。
但在isinstance判断中,子类被判断为与父类同样,这就是多态
一个类能够是本身,也能够是其父类,逻辑上也是通的,学生也是一我的。
反之则不对,一我的未必是学生。

多态的用处

def poly_test(person):
    if not isinstance(person, Person):
        raise Exception('数据类型传入错误')
    return person.sleep(10)

这个方法里,咱们对传入的类型进行了限定。
这样咱们就能够直接传入Student、Person或者其余子类均可以。
sleep也是父类就有方法,直接调用不会有问题。
当咱们新增一个子类,如Programmer类,咱们就不须要对这个方法进行修改,直接使用。
根据对象的真实类型运行,就是多态的用处。

静态

除了成员变量和成员方法,类中还能够定义静态属性类变量类方法
静态的意思是不须要实例化就可使用。

类变量的数据属于全部实例共享一个。
直接建立在类的定义中与方法同级。调用时使用类名 . 变量名

class Programmer(Person):
    
    programmers = {}    # 静态变量
    
    def __init__(self, name, age):
        Person.__init__(self, name, age)
        if name not in Programmer.programmers:
            Programmer.programmers[name] = 0
        Programmer.programmers[name] += 1

这里作了一个统计,统计同一姓名的人有几个。每建立一个实例就统计一次。

from user import Programmer
p = Programmer('Green', 18)
print(Programmer.programmers)

类方法是直接属于类的方法。
定义时,与成员方法不一样的是第一个参数不是self而是cls【这个类】。并须要在方法上添加一个装饰器

@classmethod
def count_by_name(cls, name):
    return cls.programmers[name]

@开头并写在def上面一行的就是装饰器,具体内容之后的高阶语法再介绍。
@classmethod就定义了下面的方法是一个类方法,且没有self参数,由于没有实例化。
第一参数是cls,表明了当前类,能够经过cls调用类变量,至关于Programmer.programmers
使用时直接经过类名 . 方法调用,无需实例化

count = Programmer.count_by_name('Green')

静态方法与类方法相似,但更为独立,仅仅表现为放在类里面托管的方法。
定义时,没有self参数也没有cls参数。下面是上个方法的静态方法版本。

@staticmethod
def count_by_name(name):
    return Programmer.programmers[name]

@staticmethod就定义了下面的方法是一个静态方法。其余都与类方法同样。
使用上,静态方法通常与类没有什么关系,不会涉及类的操做,是一个比较独立的方法。

继承问题:类变量与子类共享,父类没有该类变量。
类方法一样能够继承和复写,有复写时要调用这个类的类方法。
静态方法也同样。

单元测试

单元测试是一个测试框架
咱们直接来看一个简单的示例代码。

import unittest         # 单元测试框架
from user import Student    # 要测试的类


class TestStudentMethods(unittest.TestCase):

    def test_student(self):         # 测试用例
        s = Student('Green', 12)
        self.assertEqual(12, s.get_age())
        self.assertEqual('Green', s.get_name())

if __name__ == '__main__':  # 看成为启动文件时
    unittest.main()

unittest是要引入的包。建立一个类继承unittest.TestCase类。而后其余方法就不须要了。
咱们直接开始写测试用例。以test开头的成员方法就是一个用例
定义完类后,咱们启动该测试unittest.main()
前面的if语句判断了该文件是否启动文件,也是一种经常使用的方法。
启动后,单元测试会自动执行全部用例,并给出结果。

测试用例

def test_student(self):         # 测试用例
    s = Student('Green', 12)
    self.assertEqual(12, s.get_age())
    self.assertEqual('Green', s.get_name())

先进行一些操做代码,就是咱们的用例过程,这里建立了一个Student对象。
而后就要使用单元测试类给咱们提供的assert系列方法来判断结果是否正确。
assertEqual是最经常使用的方法之一了,参数1写入指望值,参数2写入要验证的数据
在运行过程当中就会进行对比,若是二者相等则正确,不等则错误。
经常使用的assert方法还有:
assertNotEqualassertTrue 、assertFalseassertIsNoneassertIsNotNoneassertIn等。
一个单元测试能够包含不少测试用例,一次进行测试。

测试结果:全部assert都正确的状况下,即测试经过。
image.png
有没有经过的用例时,会告诉你哪一个用例的哪一个assert有问题,与指望值不符。
image.png

练习

一、模拟一个小游戏,编写一个Sprite类【拥有血量和攻击力两个属性,并进行攻击】。
而后继承出Monster类和Hero类,让二者每回合互相攻击一次,直到有一方死亡。
每次攻击扣除对方的血量是攻击力加减N的随机值。
二、编写一个进行接口请求的单元测试。


github: https://github.com/lvancer/course_python

相关文章
相关标签/搜索