unittest

发布时间 2024-01-07 20:31:54作者: vorn

一、测试框架的作用

1.找到测试用例。根据默认的测试用例的规则。

2.执行测试用例。

3.判断测试用例的结果。

4.生成测试报告。

二、基础概念

1、默认规则

导入unittest模块

新建一个类,必须继承unittest.TestCase

测试用例必须以test_开头

2、夹具

setUp/tearDown 在每个用例的前后执行

setUpClass/tearDownClass 在每个类的前后执行

setUpModule/tearDownModule 在每个模块的前后执行

3、断言

self.assertEqual()

4、失败用例重跑

不支持

5、参数化

DDT

三、unittest重要组件

TestCase测试用例:包下的模块下的类下的各个函数。

TestSuite测试套件:整理测试用例,形成一个集合。

TestFixtrue测试固件:如前后置等。

TestLoader测试加载器:加载测试用例套件或者测试用例。

TestRunner测试运行器:运行测试用例。

四、命令行方式运行测试用例

一个unittest脚本App_001.py:

import unittest

class AppMedia(unittest.TestCase):

    def test_001(self):
        print('001号单元')

    def test_002(self):
        print('002号单元')

使用命令行方式运行脚本:

#参数说明:

-m 以命令行的方式运行测试用例

-v 以详细的方式展示测试结果,如显示测试用例所属函数名、模块名、类名

-k 通过通配符匹配的方式去查找测试用例

注:命令行方式是unittest默认的执行方式

(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest App_001
001号单元
.002号单元
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest App_001.AppMedia
001号单元
.002号单元
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest App_001.AppMedia.test_001
001号单元
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest -v App_001               
test_001 (App_001.AppMedia) ... 001号单元
ok
test_002 (App_001.AppMedia) ... 002号单元
ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest -v App_001 -k *_001
test_001 (App_001.AppMedia) ... 001号单元
ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

注:unittest的一个坑

在pycharm中,当鼠标放到位置1->右键->运行代码会执行所有测试用例(test_001、test_002)

在pycharm中,当鼠标放到位置2->右键->运行代码只会执行test_001测试用例

在pycharm中,当鼠标放到位置2->右键->运行代码只会执行test_002测试用例

五、unittest运行结果标记

.代表成功。如果加了-v,那么会变成OK

F代表用例执行失败

E代表用例执行出错,有异常抛出

s代表用例被跳过

六、测试用例的执行顺序

根据test_后面字符串首位字符的ASCII码大小排序(由小到大)来执行测试用例,首位字符相同则比较第二位字符的ASCII码,依次类推。

注:ord(value)是python的内置函数,作用是返回value的ASCII码

七、实际使用中unittest的运行方式

加载一个目录下所有测试用例。具体如下:将unittest.main()单独放到一个文件中,通过加载器将包下的所有模块加载到测试套件中,然后通过main运行这个测试套件

目录结构:

>testcase
  App_001.py
  Login_001.py
run.py

文件内容:

App_001.py

import unittest

class AppMedia(unittest.TestCase):

    def test_001(self):
        print('001号单元')

    def test_002(self):
        print('002号单元')

    def test_003(self):
        print('003号单元')

Login_001.py

import unittest

class LoginHome(unittest.TestCase):
    def test_login_001(self):
        print('登录001')

run.py

import unittest

if __name__ == '__main__':
    #通过TestLoader(加载器)加载./testcase文件夹下所有以_001.py结尾的模块到测试套件中,测试套件命名为suite
    suite=unittest.defaultTestLoader.discover('./testcase',pattern='*_001.py')
    #执行测试套件suite中的测试用例
    unittest.main(defaultTest='suite')

执行结果:

D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py 
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
001号单元
002号单元
003号单元
登录001

Process finished with exit code 0

八、unittest的运行原理

TestCase测试用例:包下的模块下的类下的各个函数。

TestSuite测试套件:整理测试用例,形成一个集合。

TestFixtrue测试固件:如前后置等。

TestLoader测试加载器:加载测试用例套件或者测试用例。

TestRunner测试运行器:运行测试用例。

注:可以通过读取Lib\unittest\main.py了解unittest的运行原理,在文件最后一行main = TestProgram,main相当于就是TestProgram类,解读TestProgram类中的各种配置如下:

1、测试加载器初始化

    def __init__(self, module='__main__', defaultTest=None, argv=None,
                    testRunner=None, testLoader=loader.defaultTestLoader,
                    exit=True, verbosity=1, failfast=None, catchbreak=None,
                    buffer=None, warnings=None, *, tb_locals=False):

#参数说明:

module:测试用例所在的路径__mai__代表当前模块。

defaultTest:默认的待测试的测试用例或者测试套件的名称。

argv:接收外部传递给程序的参数。

testRunner:测试运行器。

testLoader:测试加载器。

exit:是否在测试用例结束之后退出程序。

verbosity:显示详细信息的程度。0只显示用例总数以及全局执行结果。1默认值,显示用例总数以及全局执行结果外,还显示一个标记(. F E S)。>=2显示用例总数以及全局执行结果外,显示详解结果。

2、通过测试加载器加载测试用例保存到self.test

    def createTests(self, from_discovery=False, Loader=None):
        if self.testNamePatterns:
            self.testLoader.testNamePatterns = self.testNamePatterns
        if from_discovery:
            loader = self.testLoader if Loader is None else Loader()
            self.test = loader.discover(self.start, self.pattern, self.top)
        elif self.testNames is None:
            self.test = self.testLoader.loadTestsFromModule(self.module)
        else:
            self.test = self.testLoader.loadTestsFromNames(self.testNames,
                                                           self.module)

3、创建文本测试运行器

    def runTests(self):
        if self.testRunner is None:
            self.testRunner = runner.TextTestRunner

4、最后通过self.resut = testRunner.run(self.test)执行已经加载好的测试用例。并且把结果放到self.result

5、内部流程走完后,针对不同的IDE展示测试结果

九、夹具(前置后置)

setUp/tearDown 在每个用例的前后执行

setUpClass/tearDownClass 在每个类的前后执行,必须加装饰器@classmethod

setUpModule/tearDownModule 在每个模块的前后执行,使用较少

文件内容:

App_001.py

import unittest

class AppMedia(unittest.TestCase):

    def setUp(self) -> None:  #-> None表示python建议此函数返回值为None,可以删除这部分
        print('在每一个测试用例之前执行:打开浏览器,加载网页')

    def tearDown(self) -> None:
        print('在每一个测试用例之后执行:关闭浏览器')

    @classmethod  #类的前后置必须加上@classmethod装饰器
    def setUpClass(cls) -> None:
        print('----------在每个类之前执行:初始化日志对象,创建数据库连接----------')

    @classmethod
    def tearDownClass(cls) -> None:
        print('----------在每个类之后执行:销毁日志对象,销毁数据库连接----------')

    def test_001(self):
        print('001号单元')

    def test_002(self):
        print('002号单元')

    def test_003(self):
        print('003号单元')

其余文件内容不变

执行结果:

D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py 
----------在每个类之前执行:初始化日志对象,创建数据库连接----------
在每一个测试用例之前执行:打开浏览器,加载网页
001号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
002号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
003号单元
在每一个测试用例之后执行:关闭浏览器
----------在每个类之后执行:销毁日志对象,销毁数据库连接----------
登录001
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

Process finished with exit code 0

十、夹具封装

文件内容:

my_unit.py

#在testcase目录下新建一个文件,专门存放前后置函数,前后置的类继承unittest.TestCase类

import unittest

class MyUnit(unittest.TestCase):

    def setUp(self) -> None:  #-> None表示python建议此函数返回值为None,可以删除这部分
        print('在每一个测试用例之前执行:打开浏览器,加载网页')

    def tearDown(self) -> None:
        print('在每一个测试用例之后执行:关闭浏览器')

    @classmethod  #类的前后置必须加上@classmethod装饰器
    def setUpClass(cls) -> None:
        print('----------在每个类之前执行:初始化日志对象,创建数据库连接----------')

    @classmethod
    def tearDownClass(cls) -> None:
        print('----------在每个类之后执行:销毁日志对象,销毁数据库连接----------')

APP_001.py

#不再继承unittest.TestCase类,改为继承MyUnit类

from testcase.my_unit import MyUnit

class AppMedia(MyUnit):

    def test_001(self):
        print('001号单元')

    def test_002(self):
        print('002号单元')

    def test_003(self):
        print('003号单元')

Login_001.py

#不再继承unittest.TestCase类,改为继承MyUnit类

from testcase.my_unit import MyUnit

class LoginHome(MyUnit):
    def test_login_001(self):
        print('登录001')

run.py

#执行函数内容不变

import unittest

if __name__ == '__main__':
    #通过TestLoader(加载器)加载./testcase文件夹下所有以_001.py结尾的模块到测试套件中,套件命名为suite
    suite=unittest.defaultTestLoader.discover('./testcase',pattern='*_001.py')
    #执行suite测试套件中的测试用例
    unittest.main(defaultTest='suite')

执行结果:

D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py 
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
----------在每个类之前执行:初始化日志对象,创建数据库连接----------
在每一个测试用例之前执行:打开浏览器,加载网页
001号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
002号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
003号单元
在每一个测试用例之后执行:关闭浏览器
----------在每个类之后执行:销毁日志对象,销毁数据库连接----------
----------在每个类之前执行:初始化日志对象,创建数据库连接----------
在每一个测试用例之前执行:打开浏览器,加载网页
登录001
在每一个测试用例之后执行:关闭浏览器
----------在每个类之后执行:销毁日志对象,销毁数据库连接----------

Process finished with exit code 0

十一、跳过测试用例

在测试用例函数上添加“跳过”装饰器即可跳过此测试用例,有如下三种装饰器

@unittest.skip(reason='无条件跳过用例')

@unittest.skipIf(age <= 18,reason='如果年龄小于等于18,跳过用例')

@unittest.skipUnless(age > 18,reason='如果年龄不大于18,跳过用例')

十二、断言

unittest.TestCase.assertEqual()     #等于

unittest.TestCase.assertNotEquals   #不等于

unittest.TestCase.assertTrue()      #为True

unittest.TestCase.assertIn()        #属于

十三、生成HTML报告

十四、代码演示

目录结构:

>unittest项目
    >Public
        Base_Method.py
        My_Unit.py
    >testcase
        Ecshop.py
    run.py

文件内容:

Base_Method.py

import time
from selenium import webdriver

class BaseMethod():
    """基础方法"""

    def open_browser(self):
        """打开浏览器"""
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(10)  #隐式等待10秒
        return self.driver

    def load_url(self,url):
        """加载网页"""
        self.driver.get(url)
        self.driver.maximize_window()  #最大化窗口

    def switch_to_frame(self,frame_name):
        """进入其他框架"""
        self.driver.switch_to.default_content()  #退出当前框架
        self.driver.switch_to.frame(frame_name)  #进入新frame(框架)

    def pitch_element(self,method,element):
        """定位元素封装"""
        return self.driver.find_element(method,element)

    def send_keys(self,method,element,value):
        """发送值"""
        self.pitch_element(method,element).send_keys(value)

    def click(self,method,element):
        """点击"""
        self.pitch_element(method,element).click()

    def close_browser(self):
        """关闭浏览器"""
        time.sleep(10)
        self.driver.close()

My_Unit.py

import unittest,time
from Pubilc.Base_Method import BaseMethod

class MyUnit(unittest.TestCase):

    def setUp(self) -> None:  #-> None表示python建议此函数返回值为None,可以删除这部分
        print('在每一个测试用例之前执行:打开浏览器,加载网页')
        self.bp=BaseMethod()
        self.driver = self.bp.open_browser()
        self.bp.load_url("http://192.168.0.248/ecshop/admin/privilege.php?act=login")

    def tearDown(self) -> None:
        print('在每一个测试用例之后执行:关闭浏览器')
        time.sleep(5)
        self.bp.close_browser()

    # @classmethod  #类的前后置必须加上@classmethod装饰器
    # def setUpClass(cls) -> None:
    #     print('----------在每个类之前执行:初始化日志对象,创建数据库连接----------')
    #
    # @classmethod
    # def tearDownClass(cls) -> None:
    #     print('----------在每个类之后执行:销毁日志对象,销毁数据库连接----------')

Ecshop.py

from selenium.webdriver.common.by import By
from Pubilc.My_Unit import MyUnit

class Ecshop(MyUnit):

    def test_edit_product(self):

        print("登录")
        self.bp.send_keys(By.NAME, "username", value="admin")  #输入用户名
        self.bp.send_keys(By.NAME, "password", value="admin12345")  #输入密码
        self.bp.click(By.XPATH, "/html/body/form/div/div[2]/div[2]/div[4]/input")  #“登录”按钮

        print("进入商品管理")
        self.bp.switch_to_frame("menu-frame")  #进入左侧菜单框架
        self.bp.click(By.XPATH, "//*[@id='menu-ul']/li[2]")  #“商品管理”按钮
        self.assertIn("admin",self.driver.page_source)  #driver.page_source:页面HTML源码

        print("更新商品信息")
        self.bp.switch_to_frame("main-frame")  #进入主菜单框架
        self.bp.click(By.XPATH, "//*[@id='listDiv']/table[1]/tbody/tr[3]/td[13]/a[2]")  #“编辑”按钮
        self.bp.click(By.XPATH,"//*[@id='tabbody-div']/form/div/input[2]") #“确定”按钮
        text = self.bp.pitch_element(By.XPATH,"//td[@style='font-size: 14px; font-weight: bold']").text
        self.assertEqual(text,"编辑商品成功!")

run.py

import unittest

if __name__ == '__main__':
    #通过TestLoader(加载器)加载./testcase文件夹下所有以_001.py结尾的模块到测试套件中,套件命名为suite
    suite=unittest.defaultTestLoader.discover('./testcase',pattern='E*.py')
    #执行suite测试套件中的测试用例
    unittest.main(defaultTest='suite')

运行结果:

#执行run.py函数运行unittest框架中的Ecshop类下的test_edit_product用例

D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py 
在每一个测试用例之前执行:打开浏览器,加载网页
登录
进入商品管理
更新商品信息
在每一个测试用例之后执行:关闭浏览器
.
----------------------------------------------------------------------
Ran 1 test in 49.691s

OK

Process finished with exit code 0