从这节开始,就需要一定的pyhton编程基础啦, 若完全不懂python,就需要先去学习python编程语言哦
为什么要学UnitTest ? 因为UnitTest作为一款轻量级测试框架,也是十分重要的, 学习它有助于更加深刻的理解pytest 框架, 何况 Pytest 直接兼容UnitTest 测试用例

1 . UnitTest框架的基本介绍

  1. Unittest框架(又名PyUnit框架)为Python语言的单元测试框架。
  2. 其官方介绍文档链接为:[https://docs.python.org/zh-cn/3/library/unittest.html#](https://docs.python.org/zh-cn/3/library/unittest.html#) (中文的哦)
  3. 从名字开看,不难发现, 此测试框架设计的初衷是为了进行软件单元测试。 <br /> <br /> unittestxUnit系列框架中的一员,如果你了解xUnit的其他成员,那你用unittest来应该是很轻松的,它们的工作方式都差不多。比如Junitjava UnitTest )。
  4. [unittest](https://docs.python.org/zh-cn/3/library/unittest.html#module-unittest) 单元测试框架是受到 JUnit 的启发,与其他语言中的主流单元测试框架有着相似的风格。其支持测试自动化,配置共享和关机代码测试。支持将测试样例聚合到测试集中,并将测试与报告框架独立 <br /> unittest当然是软件单元测试利器,现在还可以基于在UnitTest单元测试框架进行二次开发,也可以用于接口自动化测试(接口)、功能自动化测试(UI)等等 ,

2 . UnitTest组成框架的四大元素

image.png

(1)test fixture(测试固件/夹具):专用于测试执行前的准备工作和测试结束后的清理工作,例如:创建数据库链接、关闭数据库链接、启动服务进程、测试环境的清理等。

setUp():准备环境,执行每个测试用例的前置条件;
# tearDown():环境还原,执行每个测试用例的后置条件;
# setUpClass():必须使用@classmethod装饰器,所有case执行的前置条件,只运行一次;
# tearDownClass():必须使用@classmethod装饰器,所有case运行完后只运行一次;

(2)test suite(测试套件):一个测试套件是一组测试用例的集合,它的作用是将测试用例集合到一起一次性执行集合中所有的测试用例。

一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了测试套件TestSuite的概念。TestSuite用来组装单个测试用例。可以通过addTest加载TestCase到TestSuite中,从而返回一个TestSuite实例。而且TestSuite也可以嵌套TestSuite


(3) test case(测试用例):一个测试用例是一个完整的测试流程,是最小的测试单元,通常会继承unittest.TestCase类。

什么是测试用例呢?就是一个完整的测试流程。包括测试前准备环境的搭建(SetUP)、实现测试过程的代码(run),以及测试后环境的还原(tearDown)。单元测试(Unittest)的本质也就在这里,一个测试用例就是一个完整的测试单元,通过运行这个测试单元,可以对某一个功能进行验证


(4) test runner(测试运行器):一个测试运行器执行设定的测试用例并将测试结果反馈给用户两部分功能组成。

测试的执行也是单元测试中非常重要的一个概念,一般单元测试框架中都会提供丰富的执行策略和执行结果。在Unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行test suite/test case。test runner可以使用图形界面、文本界面,或返回一个特殊的值等方式来表示测试执行的结果

3 . 一个官方的例子

代码3-1

# coding=utf-8  
#这是官方的例子哦,直接引用啦
# unittest 虽然是Python自带的模块 也要先导入
import unittest  

# 类名可以任意,不需要以test开头,需要继承unittest.TestCase基类
class StringMethods_test(unittest.TestCase):

    #测试函数一定要以test开头,否则无法识别测试用例
    def test_upper(self):
        print("测试用例1 开始执行") #这里一般编写测试用例的执行代码
        self.assertEqual('foo'.upper(), 'FOO')# 断言函数 对测试结果进行判定

    def test_isupper(self):
        print("测试用例2 开始执行")
        self.assertTrue('FOO'.isupper())p
        self.assertFalse('Foo'.isupper()) #这里断言了2次

    def test_split(self):
        print("测试用例3 开始执行")
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):#这里利用with对非字符串异常进行捕捉
             s.split(2)

if __name__ == '__main__':
    #unittest.main() #这里是程序的入口
    unittest.mian(verbosity=2)#输出详细的执行结果

执行结果:

image.png

注意点:

       1、unittest提供了全局main()方法,使用它可以方便地将一个单元测试模块变成可以直接运行的测试脚本。main()方法使用TestLoader类来搜索所有包含在**该模块**中以"test"命名开头的测试方法,并自动执行它们(**因此:所有的测试函数以test开头)**

      2、在unittest.main()中加 verbosity 参数可以控制输出的错误报告的详细程度,默认是 1,如果设为 0,则不输出每一用例的执行结果;如果设为 2,则输出详细的执行结果   

<br />           **  unittest.mian(verbosity=0) **   <br />            ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1642066209903-f566525d-84e6-4986-b19a-509a72ec1d81.png#clientId=u9755e043-d11f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=118&id=u64b12c9a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=184&originWidth=735&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13384&status=done&style=none&taskId=u26bbe0f7-38cd-4be2-9af3-314f3740043&title=&width=469.5)<br />           <br />         **   unittest.mian(verbosity=1)**      <br />              注意这里有显示测试结果 就是测试用例1文字那个红点  ,PASS结果直接就是用点来表示<br />              Fail结果则用F来表示<br />               ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1642066279180-62edb496-9751-46c8-8754-f8cf6ee7ae84.png#clientId=u9755e043-d11f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=122&id=u8b7a70d2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=205&originWidth=812&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14294&status=done&style=none&taskId=u5e35a44d-de6d-48ed-acf1-ac5df81edad&title=&width=482)

unittest.mian(verbosity=1) 存在Fali case
image.png

nittest.mian(verbosity=2) 显示详细的测试结果

            ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1642066542397-32da58ee-84ef-4593-8f04-80a69fcd47f2.png#clientId=u9755e043-d11f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=291&id=u9e6809ea&margin=%5Bobject%20Object%5D&name=image.png&originHeight=418&originWidth=739&originalType=binary&ratio=1&rotation=0&showTitle=false&size=36323&status=done&style=none&taskId=u97c551a0-1dd9-4a72-925a-941b3a142e6&title=&width=514.5)<br />          <br />         3 . UnitTest 支持多次多次断言,最终测试用例判定结果通过各个断言结果进行逻辑与

4. UnitTest的各个属性和方法

          <br />              介绍一些unittest模块的各个属性,所谓知己知彼方能百战百胜, 了解unittest的各个属性, 对于后续编写用例有很大的帮助。

         通过**dir()函数**可以查看类的属性,然后通过print打印出来,例如:print(dir(unittest)) 即可查看unittest的类属性。

打印结果如下:

[‘BaseTestSuite’, ‘FunctionTestCase’, ‘IsolatedAsyncioTestCase’, ‘SkipTest’, ‘TestCase‘, ‘TestLoader’, ‘TestProgram’, ‘TestResult’, ‘TestSuite‘, ‘TextTestResult’, ‘TextTestRunner’, ‘TextTestResult’, ‘all‘, ‘builtins‘, ‘cached‘, ‘dir‘, ‘doc‘, ‘file‘, ‘getattr‘, ‘loader‘, ‘name‘, ‘package‘, ‘path‘, ‘_spec‘, ‘__unittest’, ‘addModuleCleanup’, ‘case’, ‘defaultTestLoader’, ‘expectedFailure’, ‘findTestCases’, ‘getTestCaseNames’, ‘installHandler’, ‘load_tests’, ‘loader’, ‘main’, ‘makeSuite’, ‘registerResult’, ‘removeHandler’, ‘removeResult’, ‘result’, ‘runner’, ‘signals’, ‘skip’, ‘skipIf’, ‘skipUnless’, ‘suite’, ‘util’]

还是这四大金刚!

unittest.TestCase:TestCase类,所有测试用例类继承的基类。

unittest.main(): 使用它可以方便的将一个单元测试模块变为可直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中以“test”命名开头的测试方法,并自动执行他们。执行方法的默认顺序是:根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。所以以A开头的测试用例方法会优先执行,以a开头会后执行 (这就是为什么例子-1测试用例执行的顺序不是按照代码从上往下的顺序执行 )

unittest.TestSuite():
测试套件类,如何理解测试套件这个概念呢,从它的类定义来看,可以理解为:多个独立的测试用例
(test case)或者多个独立的测试套件(test suite,可以理解为子套件)可以构成一个测试套件,包括添加或删除测试套件到组建的测试套件里,管理测试用例的执行顺序,组织不同文件里的测试用例
执行等。

unittest.TextTextRunner():unittest框架的TextTextRunner()类,通过该类下面的run()方法来运行suite所组装的测试用例,入参为suite测试套件

unittest.defaultTestLoader(): defaultTestLoader()类,通过该类下面的discover()方法可自动更具测试目录start_dir匹配查找测试用例文件(test*.py),并将查找到的测试用例组装到测试套件,因此可以直接通过run()方法执行discover。

用法如下:
discover=unittest.defaultTestLoader.discover(testdir, pattern=’test*.py’)

unittest.skip():装饰器,当运行用例时,有些用例可能不想执行等,可用装饰器暂时屏蔽该条测试用例。一种常见的用法就是比如说想调试某一个测试用例,想先屏蔽其他用例就可以用装饰器屏蔽。
@unittest.skip(reason): skip(reason)装饰器:无条件跳过装饰的测试,并说明跳过测试的原因。
@unittest.skipIf(reason): skipIf(condition,reason)装饰器:条件为真时,跳过装饰的测试,并说明跳过测试的原因。
@unittest.skipUnless(reason): skipUnless(condition,reason)装饰器:条件为假时,跳过装饰的测试,并说明跳过测试的原因。
@unittest.expectedFailure(): expectedFailure()测试标记为失败

5. UnitTest测试框架的运行原理

       <br />             ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1642233254235-9a40d884-e2ae-4000-8024-cef9a9a264e9.png#clientId=u9755e043-d11f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=381&id=u77a93b0b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=700&originWidth=872&originalType=binary&ratio=1&rotation=0&showTitle=false&size=100348&status=done&style=none&taskId=u435b0c67-41c8-4184-a659-caa1f6a1973&title=&width=475)<br />   <br />                                          ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25630832/1642233551248-e677d80e-fa1c-4fc9-977a-fa3d8454b4da.png#clientId=u9755e043-d11f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=452&id=u33e5d38a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=672&originWidth=517&originalType=binary&ratio=1&rotation=0&showTitle=false&size=86713&status=done&style=none&taskId=ufe485dee-7e1b-4ecf-9a02-beaf73f2cd6&title=&width=347.5)

TestCase: 测试用例类
此类的一个实例,就是一个测试用例。 一个完整的测试用例包括: 包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。

TestSuite:测试套件类
对多个测试用例的整合到一起,打包进行测试,就用到了TestSuite 实例化一个TestSuite对象,可以添加上多个测试用例

TestLoader: 测试用例加载类
加载TestCase实例(即测试用例)到TestSuite实例中, 所以,这个类里面有很多加载的方法,例如:几个loadTestsFrom__()方法

TextTestRunner:测试用例运行类实例化出此类的一个对象,然后把TestSuite实例做为参与传进去,就可以运行测试了
TextTestResult: 测试用例结果类
TextTestRunner的运行结果保存在TextTestResult中, 包括运行了多少测试用例,成功了多少,失败了多少等信息 对一个测试用例环境的搭建和销毁,是一个fixture,通过覆盖TestCase的setUp()和TearDown()方法来实现

6. UnitTest测试框架的断言

       在测试用例中,执行完测试用例后,最后一步是判断测试结果是 pass 还是 fail,自动化测试脚本里面一般把这种生成测试结果的方法称为断言(assert)。很明显理解和灵活应用断言方法是十分重要的。它直接影响到测试用例的最终判定结果。

          python unintest单元测试框架提供了一整套内置的断言方法:<br />             1、如果断言失败,则抛出一个AssertionError,并标识该测试为失败状态;<br />             2、如果异常,则当做错误来处理<br />             3、如果成功,则标识该测试为成功状态

6.1 基本常用的断言方法

  基本的断言方法提供了测试结果是True还是False。所有的断言方法都有一个msg参数,如果指定msg参数的值,则将该信息作为失败的错误信息返回。
序号 断言方法 断言描述
1 assertEqual(arg1, arg2, msg=None) 验证arg1==arg2,不等则fail
2 assertNotEqual(arg1, arg2, msg=None) 验证arg1 != arg2, 相等则fail
3 assertTrue(expr, msg=None) 验证expr是true,如果为false,则fail
4 assertFalse(expr,msg=None) 验证expr是false,如果为true,则fail
5 assertIs(arg1, arg2, msg=None) 验证arg1、arg2是同一个对象,不是则fail
6 assertIsNot(arg1, arg2, msg=None) 验证arg1、arg2不是同一个对象,是则fail
7 assertIsNone(expr, msg=None) 验证expr是None,不是则fail
8 assertIsNotNone(expr, msg=None) 验证expr不是None,是则fail
9 assertIn(arg1, arg2, msg=None) 验证arg1是arg2的子串,不是则fail
10 assertNotIn(arg1, arg2, msg=None) 验证arg1不是arg2的子串,是则fail
11 assertIsInstance(obj, cls, msg=None) 验证obj是cls的实例,不是则fail
12 assertNotIsInstance(obj, cls, msg=None) 验证obj不是cls的实例,是则fail


代码3-2:

# -*- coding:utf-8 -*-
# Author: tang_ren_li
# 2021-1-16 20:00
import unittest

str0="xiao zhu "
str1="xiao he"
str2="xiao bing"
str3="xiao"
str4="xiao zhu "

class A():
    pass
class B():
    pass

a=A() #实例化A

b=B() #实例化B


def Demo_no_return():
    print('no return')

def Demo_return():
    return 'return'


class  Assert_test_demo1(unittest.TestCase):

       def  test_case1(self):
           print('case 1  验证arg1==arg2,不等则fail')
           self.assertEqual(str0,str4,)

       def test_case2(self):
           print('case 2 验证arg1 != arg2, 相等则faill')
           self.assertNotEqual(str0, str1)

       def test_case3(self):
           print('case 3 验证expr是true,如果为false,则fail')
           self.assertTrue(1==1)

       def test_case4(self):
           print('case 4 验证expr是true,如果为false,则fail')
           self.assertFalse(1>2)

       def test_case5(self):
           print('case 5 验证arg1、arg2是同一个对象,不是则fail')
           self.assertIs(a,b,'a,b 不是一个对象 所以 Fail') #这里传入了测试Fail的msg

       def test_case6(self):
           print('case 6 验证arg1、arg2不是同一个对象,是则fail')
           self.assertIsNot(a,b)

       def test_case7(self):
            print('case 7 验证expr是None,不是则fail') #可以用例判断是否采集或抓取到数据
            self.assertIsNone(Demo_no_return())

       def test_case8(self):
            print('case 8 验证expr不是None,是则fail')
            self.assertIsNotNone(Demo_return())

       def test_case9(self):
           print('case 9 验证arg1是arg2的子串,不是则fail')
           self.assertIn(str3,str1) #

       def test_case10(self):
           print('case 10 验证arg1不是arg2的子串,是则fail')
           self.assertNotIn(str1,str3)

       def test_case11(self):
           print('case 11 验证obj是cls的实例,不是则fail')  #
           self.assertIsInstance(a,A)

       def test_case12(self):
           print('case 12 验证obj不是cls的实例,是则fail')
           self.assertNotIsInstance(b,A)


if __name__ == '__main__':
    unittest.main(verbosity=2)

执行结果:
image.png

6.2 其它不常用的断言方法

Method Checks that New in
assertRaises(exc,fun,args,*kwds) fun(args, *kwds) raises exc
assertRaisesRegex(exc,r,fun,args,*kwds) fun(args, *kwds) raises exc and the message matches regex r 3.1
assertWarns(warn,fun,args,*kwds) fun(args, *kwds) raises warn 3.2
assertWarnsRegex(warn,r,fun,args,*kwds) fun(args, *kwds) raises warn and the message matches regex r 3.2
assertLogs(logger,level) The with block logs on logger with minimum level 3.4
assertNoLogs(logger,level) The with block does not log on 3.10
Method Checks that New in
assertAlmostEqual(a,b) round(a-b, 7) == 0
assertNotAlmostEqual(a,b) round(a-b, 7) != 0
assertGreater(a,b) a > b 3.1
assertGreaterEqual(a,b) a >= b 3.1
assertLess(a,b) a < b 3.1
assertLessEqual(a,b) a <= b 3.1
assertRegex(s,r) r.search(s) 3.1
assertNotRegex(s,r) not r.search(s) 3.2
assertCountEqual(a,b) a and b have the same elements in the same number, regardless of their order. 3.2

大家感兴趣的可以探索一下,在某些特殊的场景或许可以使用的到。

7. 总结

首先是要写好TestCase,包括测试用例执行过程及对结果的断言, 然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,整个过程集成在unittest.main模块中