Python 进阶之单元测试框架 第13节课(单元测试 debug调试方法)

单元测试

1.什么是单元、单元测试

单元: 指的是函数或者是类,测试的最小单元

单元测试:就是测试代码里面的函数或者是类,是不是按照预先定义好的去执行

2.为什么要做单元测试?

好处:投入小,收益大,能够精准的、更早的发现问题

3.单元测试与我有什么关系?

Python语言很难测试java的单元

单元测试一般是由开发,测开人员写的

但是自动化测试可以做 集成测试、系统测试、验收测试

4.学单元测试框架干嘛呢?(每一种语言都会自带一个单元测试框架)

单元测试框架可以用于集成测试、系统测试

5.编写被测对象(函数),后面根据这个函数再编写测试用例

♥单元测试框架:unittest

♥unittest的注意规范:模块名称  test_.....

          类名 Test....

          测试用例的方法名称  test_....

          测试用例类TestLogin(unittest.TestCase) 不要把继承的父类丢掉

unittest 中,建立的 python file的名字必须是以test开头,比如:test_login.py

(1)、编写测试登录功能(函数)

"""测试登录功能(函数)"""
def login(username = None,password = None):
    """
    :param username: 登录校验的用户名
    :param password: 登录校验的密码
    :return:dict type
    """
    if username != None and password != None:
        if username == "polo" and password == "123456":
            return {"code":0,"msg":"登录成功!"}
        else:
            return {"code":1,"msg":"登录失败,用户名或密码错误!"}
    else:
        return {"code":1,"msg":"登录失败,用户名或密码为空!"}

#用户名或密码为空
if __name__ =='__main__':
    print(login())
#密码错误
if __name__ =='__main__':
    print(login('polo','123'))

#用户名、密码都正确
if __name__ =='__main__':
    print(login('polo','123456'))
'''
{'code': 1, 'msg': '登录失败,用户名或密码为空!'}
{'code': 1, 'msg': '登录失败,用户名或密码错误!'}
{'code': 0, 'msg': '登录成功!'}
'''

 (2)、测试用例---TestCase (根据被测函数,设计测试用例)---------测试用例的方法是实例方法,不然用不了self.assertTrue

测试用例的类名,必须是Test开头,比如:TestLogin

断言:assertTrue(表达式)  重点用好这个,

           assertEqual(两个参数)--------assertEqual(4,3),不相等,报错

   assertGreater(两个参数)------assertGreater(4,3)4>3是正确的,就是等价于assertTrue(4>3)

由上可知,assertTrue(表达式)能够包含别的用法,所以重点用好assertTrue(表达式)。熟悉了用法再去拓展别的用法。

(assert断言的源码里已经进行了判断,用了try,用起来方便,比外面再进行if判断,写很多分支方便)

♥运行测试用例的注意事项

①写完代码换行,在空白行处运行

②运行测试用例,在setting→搜索unittest,将Default test runner 设置为Unittests

举例1:两个测试用例都通过

#导入单元测试框架unittest
import unittest

"""测试登录功能(函数)"""
def login(username = None,password = None):
    """
    :param username: 登录校验的用户名
    :param password: 登录校验的密码
    :return:dict type
    """
    if username != None and password != None:
        if username == "polo" and password == "123456":
            return {"code":0,"msg":"登录成功!"}
        else:
            return {"code":1,"msg":"登录失败,用户名或密码错误!"}
    else:
        return {"code":1,"msg":"登录失败,用户名或密码为空!"}

#设计登录测试用例类
class TestLogin(unittest.TestCase):#继承unittest.TestCase

    def test_login_success(self):
        '''登录成功'''
        username = "polo"
        password = "123456"
        expected_reponse = {"code":0,"msg":"登录成功!"}

        #实际结果:调用login函数
        actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用
        #判断预期结果跟实际结果是否一样   断言
        self.assertTrue(expected_reponse == actual_reponse)


    def test_login_error(self):
        '''登录失败'''
        username = ''
        password = ''
        expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"}
        actual_reponse = login(username, password)
        self.assertTrue(expected_reponse == actual_reponse)

 '''

  Ran 2 tests in 0.002s

  OK

  '''

 ♥上面的类直接运行测试用例,但是没有实例化对象------->因为unittest内部已经做了设置初始化,直接使用run 'unittest......'即可

举例2:两个测试用例,有一个没通过(下面代码的黄底做了改动,预期结果跟实际结果不相等,异常)

#导入单元测试框架unittest
import unittest

"""测试登录功能(函数)"""
def login(username = None,password = None):
    """
    :param username: 登录校验的用户名
    :param password: 登录校验的密码
    :return:dict type
    """
    if username != None and password != None:
        if username == "polo" and password == "123456":
            return {"code":0,"msg":"登录成功!"}
        else:
            return {"code":1,"msg":"登录失败,用户名或密码错误!"}
    else:
        return {"code":1,"msg":"登录失败,用户名或密码为空!"}

#设计登录测试用例类
class TestLogin(unittest.TestCase):#继承unittest.TestCase

    def test_login_success(self):
        '''登录成功'''
        username = "polo"
        password = "123456"
        expected_reponse = {"code":0,"msg":"登录成功....!"}

        #实际结果:调用login函数
        actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用
        #判断预期结果跟实际结果是否一样   断言
        self.assertTrue(expected_reponse == actual_reponse)


    def test_login_error(self):
        '''登录失败'''
        username = ''
        password = ''
        expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"}
        actual_reponse = login(username, password)
        self.assertTrue(expected_reponse == actual_reponse)

结果:一个用例测试异常,不会影响下一个用例的执行。左边的Test Results 结果中可以查看异常的用例名称,右侧主要看该异常用例的错误类型

须知:

断言失败会抛出异常:AssertionError

一个测试用例,写一个断言。

多个用例之间互不影响:即上一个测试用例出现断言异常,不会影响下面的测试用例的执行(继续执行)

unittest框架中设置好了初始化,在测试用例这个类里面不需要再进行初始化__init__了。

(3).fixture----测试环境的搭建和销毁 (前置、后置条件)

setUp() 、tearDown()是unitttest框架的固定写法,不能改。

  def setUp(self):
        '''前置条件'''
        pass
    
    def tearDown(self):
        '''后置条件'''
        pass

♥ ♥ 每执行一个测试用例,都会自动执行一遍setUp() 、tearDown(),举例:

#导入单元测试框架unittest
import unittest

"""测试登录功能(函数)"""
def login(username = None,password = None):
    """
    :param username: 登录校验的用户名
    :param password: 登录校验的密码
    :return:dict type
    """
    if username != None and password != None:
        if username == "polo" and password == "123456":
            return {"code":0,"msg":"登录成功!"}
        else:
            return {"code":1,"msg":"登录失败,用户名或密码错误!"}
    else:
        return {"code":1,"msg":"登录失败,用户名或密码为空!"}

#设计登录测试用例类
class TestLogin(unittest.TestCase):#继承unittest.TestCase

    def setUp(self):
        '''前置条件'''
        print('连接数据库')

    def tearDown(self):
        '''后置条件'''
        print('断开数据库')

    def test_login_success(self):
        '''登录成功'''
        username = "polo"
        password = "123456"
        expected_reponse = {"code":0,"msg":"登录成功!"}

        #实际结果:调用login函数
        actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用
        #判断预期结果跟实际结果是否一样   断言
        self.assertTrue(expected_reponse == actual_reponse)


    def test_login_error(self):
        '''登录失败'''
        username = ''
        password = ''
        expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"}
        actual_reponse = login(username, password)
        self.assertTrue(expected_reponse == actual_reponse)

结果如下:两个测试用例,前置、后置条件都会自动执行一遍

♥♥ 如果所有的测试用例,前置、后置条件都只执行一遍应该怎么办?------了解即可

使用setUpclass(cls),一个测试类中,只会执行一次的前置条件(类方法)

使用tearDownclass(cls),一个测试类中,只会执行一次的后置条件(类方法)

#导入单元测试框架unittest
import unittest

"""测试登录功能(函数)"""
def login(username = None,password = None):
    """
    :param username: 登录校验的用户名
    :param password: 登录校验的密码
    :return:dict type
    """
    if username != None and password != None:
        if username == "polo" and password == "123456":
            return {"code":0,"msg":"登录成功!"}
        else:
            return {"code":1,"msg":"登录失败,用户名或密码错误!"}
    else:
        return {"code":1,"msg":"登录失败,用户名或密码为空!"}

#设计登录测试用例类
class TestLogin(unittest.TestCase):#继承unittest.TestCase

    @classmethod
    def setUpClass(cls):
        print('一个测试类中只会执行一次的前置条件')

    @classmethod
    def tearDownClass(cls):
        print('一个测试类中只会执行一次的后置条件')

    def setUp(self):
        '''前置条件'''
        print('连接数据库')

    def tearDown(self):
        '''后置条件'''
        print('断开数据库')

    def test_login_success(self):
        '''登录成功'''
        username = "polo"
        password = "123456"
        expected_reponse = {"code":0,"msg":"登录成功!"}

        #实际结果:调用login函数
        actual_reponse = login(username,password)#login()函数是全局的,可以在类里面调用
        #判断预期结果跟实际结果是否一样   断言
        self.assertTrue(expected_reponse == actual_reponse)


    def test_login_error(self):
        '''登录失败'''
        username = ''
        password = ''
        expected_reponse = {"code":1,"msg":"登录失败,用户名或密码错误!"}
        actual_reponse = login(username, password)
        self.assertTrue(expected_reponse == actual_reponse)

'''

  一个测试类中只会执行一次的前置条件
  连接数据库
  断开数据库
  连接数据库
  断开数据库
  一个测试类中只会执行一次的后置条件

  '''

注:运行代码是,右键-->run "unittest........"只有pycharm有这种功能

其他的编辑器,可以在运行的代码下面顶格加如下:

if __name__ == '__main__':
    #使用python去运行用例
    unittest.main()

然后在terminal中用python运行:python lesson14 estcaseslogin.py

(4)Test Suite------测试集/测试套件,多个测试用例集合在一起

测试须知知识:

① unittest 中,测试用例的执行顺序

不是从上到下来执行的,是根据测试用例的名称----→ASCII编码,美国标准编码规范

测试用例名称的ASCII码的大小(前后顺序)来排列的

上面例子的两个测试用例名称,success error, e 在s的前面,error的用例先执行.

如果让success的用例先执行,error后执行,如下,加排序

def test_login_01_success(self):

  pass

def test_login_02_error(self):

  pass

 ②测试用例的执行方法

1)右键→run"Unittests in .................",运行当前模块(只有pycharm有unittest 运行,其他的编辑器就要用方法2)去运行)

2)增加 if __name__ == '__main__':    

      unittest.main()

在终端里面,使用python,命令行运行当前这个模块里的测试用例.

运行所有的测试用例怎么办???-----收集所有的用例 TestSuite&TestLoader()一起使用

① 测试用例的组织--会把测试用例的代码放到一个统一的文件夹中,目录当中tests / testcases

    方法:在项目录下new 一个directory,命名为tests / testcases,把所有的测试用例代码放在这个文件夹里面

②项目根目录,new一个python file(比如:run_tests.py,运行所有的程序),最好跟tests / testcases平级

     在这个模块里执行所有的程序,

 包括:收集所有的测试用例、执行、生成测试报告

测试整个项目的时候,一般不会单独运行某个模块(调试的时候会运行单个模块)

 ③测试用例收集步骤

    1)将所有待测的测试用例放在,项目目录下new 一个directory,命名为tests / testcases里

  2)在run_test.py中,运行收集用例的代码

   1)). import unittest

   2)).初始化一个加载器    loader = unittest.TestLoader()

   3)).加载器去收集所有的测试用例(testcases里的所有用例),放在testsuite里面  test_suite = loader.discover(case_path)

    要获取testcases里的所有用例,就要获取该目录的绝对路径,该路径作为loader.discover()的参数(下面的例子testcases文件夹里有test_login.py文件,包含3个测试用例)

import unittest
import os

#初始化一个加载器 test_loadder
loader = unittest.TestLoader()

#获取测试用例目录的路径,测试用例都放在tesetcases文件夹里
dir_path  = os.path.dirname(os.path.abspath(__file__))
case_path = os.path.join(dir_path,'testcases')

#使用loader获取所有的测试用例
test_suite = loader.discover(case_path)  #在case_path里面发现所有的测试用例
print(test_suite)#就是返回一个testSuite对象,列表存储的

 测试用例收集完,怎么去执行测试用例呢?---------TextTestRunner()去执行测试集,用run方法

步骤:1).初始化一个执行器runner

   2).runner.run(test_suite)  -----传入测试集合执行import unittest

import os

#初始化一个加载器 test_loadder
loader = unittest.TestLoader()

#获取测试用例目录的路径,测试用例都放在tesetcases文件夹里
dir_path  = os.path.dirname(os.path.abspath(__file__))
case_path = os.path.join(dir_path,'testcases')

#使用loader获取所有的测试用例
test_suite = loader.discover(case_path)  #在case_path里面发现所有的测试用例
print(test_suite)#就是返回一个testSuite对象,列表存储的


#执行测试用例
#先初始化一个执行器runner
runner = unittest.TextTestRunner()
runner.run(test_suite)

'''

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s


OK
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_login.TestLogin testMethod=test_login_error>, <test_login.TestLogin testMethod=test_login_pwderror>, <test_login.TestLogin testMethod=test_login_success>]>]>, <unittest.suite.TestSuite tests=[]>]>
连接数据库
断开数据库
连接数据库
断开数据库
连接数据库
断开数据库

'''

上面的运行结果,没有测试报告。

如何去生成一个测试报告呢?-------with open('test_report.txt') as f:

import unittest
import os

#初始化一个加载器 test_loadder
loader = unittest.TestLoader()

#获取测试用例目录的路径,测试用例都放在tesetcases文件夹里
dir_path  = os.path.dirname(os.path.abspath(__file__))
case_path = os.path.join(dir_path,'testcases')

#使用loader获取所有的测试用例
test_suite = loader.discover(case_path)  #在case_path里面发现所有的测试用例
print(test_suite)#就是返回一个testSuite对象,列表存储的


#执行 并生成text测试报告
with open('test_report.txt','a',encoding='utf-8') as f:
    runner  = unittest.TextTestRunner(f) #f作为参数传入
    runner.run(test_suite)

执行完,在run_test.py的同级目录下生成一个test_report.txt文件,记录结果信息

下图,将其中一个测试用例的数据修改,让其运行在断言的地方报错

 报告结果中显示:..F (3个测试用例)   点 表示通过,F表示failure(测试用例的问题:①测试用例的预期结果不对 ②执行中确实存在bug),E 表示error,代码本身存在问题(添加1/0去运行,就会报错E)

但是在实际中,不会使用txt形式的报告,比较low,会换用HTML的报告.

HTML报告不是内置的,需要自己去下载HTML的测试报告的运行器和模板(HTMLTestRunnerNew.py),导入到pycharm的seite_package 或者直接导入项目目录下.

 首先看一下HTMLTestRunner的源码

class HTMLTestRunner(Template_mixin):
    """
    """
    def __init__(self, stream=sys.stdout, verbosity=2,title=None,description=None,tester=None):

执行 生成HTML报告

#执行 并生成html测试报告
with open('test_report.html','wb') as f:
    runner = HTMLTestRunner(
        f,
        title='python29期第一次自动化测试报告',
        description='测试报告的描述',
        tester='polo'

    )
    runner.run(test_suite)

结果:

 总结:运行用例的流程

--------1.编写用例方法,用例方法放到一个目录中

--------2.写脚本run.py  收集用例  运行用例  loader收集用例 suite = discover()

--------3.得到测试集 test_suite

--------4.运行,test_runner = HTMLTestRunner

拓展知识(了解,用的比较少):不使用discover,只想运行某2个用例,不运行所有的用例

课程-数据分离结合excel实际应用(在25min左右时间段讲解)

 from   XXX import test_login,test_register

 最后运行:runner.run(suit_total)

注:运行哪些用例,将用例add起来,是用list表示的,表示一个可迭代的对象。

(5) debug 设置断点  ------运行用debug  run

①断点打在哪里?

   ♥知道会报错的那行,打断点,程序运行到这一行就会停止(该行不会运行)

   ♥如果不知道哪里会报错,就打在程序最开始的地方,再一步一步调试

②断点调试,pycharm中调式的类型

 ♥1)--------step over(F8)   ,表示单步执行

♥2)-------------step  into(F7),表示进入函数内

举例,在下面这行代码设置断点,运行后,执行step into ,就会进入login()函数中去

actual_response = login(username,password)

进入login()函数中以后,可以使用step into,或者step over.

 3)-----------step into my code ,只能进入我自己写的代码,别人写的进不去,一般很少用,用step into比较多

4)-------------step out 退出函数

♥5)-------------run to Cursr 表示运行到光标的位置, 指定的行. 即打了断点以后,将光标放在想要运行指定的哪行代码上,点击run to Cursr ,就会运行指定的这行代码

6)------------evaluate expression 计算器 

可以在进行调试的时候,对数据进行某些处理,查看结果

 7)--------rerun  重新运行,点击会再次运行程序

8)--------resume program  运行到下一个断点(中间不会停顿)

原文地址:https://www.cnblogs.com/ananmy/p/12939423.html