二、Unittest测试框架(了解思想即可)

单元测试概述

  • 什么是单元测试(白盒测试)?
    • 单元测试是开发者编写的一小段代码,用于检验被测代的一个很小的、很明确的功能是否正确.。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
  • 单元测试什么时候开始?
    • 越早介入越好
  • 单元测试由谁负责?
    • 由开发负责

单元测试需要注意

  • 单元测试的时候一个大前提就是需要清楚的知道,自己要测试的程序块所预期的输入和输出,然后根据这个预期和程序逻辑来书写case。这里的预期结果一定要针对需求/设计的逻辑去写,而不是针对程序的实现去写,否则单测就失去了意义,照着错误的实现设计出的case也很可能是错的。

单元测试框架列举及国内大厂现状

  • Unittest
    • Python内置的标准类库。它的API跟Java的JUnit、.net的NUnit,C++的CppUnit很相似
  • Pytest
    • 丰富、灵活的测试框架,语法简单,可以结合allure生成一个炫酷的测试报告,现在比较主流
  • Nose
    • Nose是对unittest的扩展,使得python的测试更加简单
  • Mock
    • unittest.mock是用来测试python的库。这个是一个标准库(出现在python3.3版本以后)

单元测试覆盖率

  • 代码覆盖率也被用于自动化测试和手工测试来度量测试是否全面的指标之一,应用覆盖率的思想增强测试用例的设计
  • 单元测试覆盖类型:
    • 语句覆盖
    • 条件覆盖
    • 判断覆盖
    • 路径覆盖
  • 被测试代码片段
def demo_method(a,b,x):

  if (a>1 and b == 0):
    x = x/a

  if (a == 2 or x>1):
    x = x+1

  return x
image.png

语句覆盖

  • 语句覆盖定义

    • 通过设计一定量的测试用例,保证被测试的方法每一行代码都会被执行一遍
    • 运行测试用例的时候被击中的代码行即称为被覆盖的语句
  • 测试用例

    • 仅需要一条case,即可实现行覆盖
    • a =3, b=0, x=4
  • 漏洞

    • and -> or
  • 行覆盖是一个最基础的覆盖方式,但是也是最薄弱的,如果完全依赖行覆盖,会出现很严重的问题

判断覆盖

  • 判断覆盖定义
    • 运行测试用例的过程中被击中的判定语句
  • 测试用例


    image.png
  • 漏洞
    • 大部分的判定语句是由多个逻辑条件组合而成,若仅仅判断其整个最终结果,而忽略每个条件的取值情况,必然会遗漏部分测试路径
    • a==2 or x>1 -> a==2 or x<1

条件覆盖

  • 条件覆盖定义
    • 条件覆盖和判定覆盖类似,不过判定覆盖关注整个判定语句,而条件覆盖则关注某个判断条件
  • 测试用例:if(a>1 and b ==0)


    image.png
  • 缺陷
    • 测试用例指数级增加(2**conditions)

路径覆盖

  • 路径覆盖定义
    • 覆盖所有可能执行的路径
  • 测试用例


    image.png

    image.png
  • 应用这些方法设计测试用例

unittest框架介绍

unittest编写与规范

  • Unittest提供了了test cases、test suites、test fixtures、test runner相关的组件
  • 编写规范
    • 测试模块首先import unittest
    • 测试类必须继承unittest.TestCase
    • 测试方法必须以"test_"开头
    • 类名使用“驼峰”命名法
import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        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):
            s.split(2)

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

测试框架结构

  • 总结:
  • setUp用来为测试准备环境, tearDown用来清理环境
  • 如果想要在所有case执行之前准备一次环境,并在所有case执行结束之后再清理环境,我们可以用setUpClass() 与 tearDownClass();比如:数据库连接及销毁
  • 如果想有些方法不在本次执行使用@unittest.skip
  • 测试方法的命名:以test开头
  • 各种执行-单一用例,全部
class TestClass(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        print("这是测试整个类前要执行的方法")
    
    def setUp(self) -> None:
        print("这是每一个测试方法前面运行的方法")
        
    def tearDown(self) -> None:
        print("这是每一个测试方法后面运行的方法")
    
    @unittest.skip("这次不想执行这个测试")
    def test_second(self):
        print("这是测试方法2")
        
    @classmethod
    def tearDownClass(cls) -> None:
        print("这是测试整个类后要执行的方法")

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

Tips:-> None 表示默认返回值为 None。setUp 和 tearDown 是在每条测试用例的前后调用,setUpClasstearDownClass 是在整个测试类的前后调用,注意 setUpClasstearDownClass 要加上装饰器@classmethodunittest.main() 可以调用当前模块所有的测试类和测试用例。

setUp 与 tearDown

  • 基于测试方法级别的setUp,tearDown
    • 执行每个测试方法的时候都会执行一次setUp和tearDown
  • 基于类级别的setUpClass,tearDownClass
  • 执行这个类里面的所有测试方法只有一次执行setup, tearDown。

unittest断言

  • 用unittest组件测试用例的时候,断言的方法还是很多的,下面介绍几种常用的断言方法: assertEqual、assertln、assertTrue
  • 基本断言方法
    • 基本的断言方法提供了测试结果是True还是False。所有的断言方法都有一个msg参数,如果指定msg参数的值,则将该信息作为失败的错误信息返回。


      image.png

Tips:Pycharm中运行测试用例后程序会自动记住上一次的执行代码,这时候需要去右上角点击项目将运行历史清空。
image.png

unittest执行测试用例

  • 编写unittest测试用例的原则

    • unittest会自动识别以test开头的函数是测试代码,test一定要是小写
  • 执行测试用例的顺序

    • 测试用例执行顺序是以test后面的字母顺序执行的。例如test_a,test_b,test_c
  • 多个测试用例的集合就是测试套件,通过测试套件来管理多个测试用例

  • 执行方法一:

    • unittest.main()
  • 执行方法二:加入容器中执行

    • suite=unittest.TestSuite()
    • suite.addTest(TestMethod("test_01"))
    • suite.addTest(TestMethod("test_02"'))
    • unittest.TextTestRunner().run(suite)
  • 执行方法三:此用法可以同时测试多个类

    • suite1 = unittest.TestLoader().loadTestsFromTestCase(TestCase1)
    • suite2 = unittest.TestLoader().loadTestsFromTestCase(TestCase2)
    • suite = unittest.TestSuite([suite1, suite2])
    • unittest.TextTestRunner(verbosity=2).run(suite)
  • 执行方法四:匹配某个目录下所有以test开头的py文件,执行这些文件下的所有测试用例

  • test_dir = "./ test_case"

  • discover = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")

    • discover可以一次调用多个脚本
    • test_dir 被测试脚本的路径
    • pattern脚本名称匹配规则
import unittest
class Search():
    def search(self):
        print("search")

class TestSearch(unittest.TestCase):
    def setUp(self) -> None:
        print("setUp_方法级别")
        self.search = Search()

    def tearDown(self) -> None:
        print("tearDown_方法级别")
        self.search = Search()

    @classmethod
    def setUpClass(cls) -> None:
        print("setUp_类级别")
        cls.search = Search()

    @classmethod
    def tearDownClass(cls) -> None:
        print("tearDown_类级别")
        cls.search = Search()

    def test_search1(self):
        print("search1")
        assert True == self.search.search()

    def test_equal(self):
        print("断言相等")
        self.assertEqual(1,2,"判断1==2")

    def test_noteqal(self):
        print("断言不相等")
        self.assertNotEqual(1,2,"判断1!=2")

class TestSearch1(unittest.TestCase):
    def setUp(self) -> None:
        print("setUp_方法级别")
        self.search = Search()

    def tearDown(self) -> None:
        print("tearDown_方法级别")
        self.search = Search()

    @classmethod
    def setUpClass(cls) -> None:
        print("setUp_类级别")
        cls.search = Search()

    @classmethod
    def tearDownClass(cls) -> None:
        print("tearDown_类级别")
        cls.search = Search()

    def test_search1(self):
        print("search1")
        assert True == self.search.search()

    def test_equal(self):
        print("断言相等")
        self.assertEqual(1,2,"判断1==2")

    def test_noteqal(self):
        print("断言不相等")
        self.assertNotEqual(1,2,"判断1!=2")

if __name__ == '__main__':
    # 方法一:运行所用测试用例
    # unittest.main()

    # 方法二:测试套件
    # 创建一个测试套件,testsuite
    suite = unittest.TestSuite()
    # 只运行TestSearch1下的test_search1测试用例
    suite.addTest(TestSearch1("test_search1"))
    # 执行用例
    unittest.TextTestRunner().run(suite)

    # 方法三:执行某个测试类
    suite1 = unittest.TestLoader().loadTestsFromTestCase(TestSearch1)
    suite2 = unittest.TestLoader().loadTestsFromTestCase(TestSearch)
    suite = unittest.TestSuite([suite1, suite2])
    unittest.TextTestRunner(verbosity=2).run(suite)

Tips:Pycharm运行时IDE不太准确,可以在Pycharm中切换到命令行里面运行,如:python test_search.py

D:\0_NSFocus_My_Files\5_Python_Test\Unittest_Demo>python test_unittest.py
setUp_类级别
setUp_方法级别
search1
search
tearDown_方法级别
FtearDown_类级别

======================================================================
FAIL: test_search1 (__main__.TestSearch1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_unittest.py", line 63, in test_search1
    assert True == self.search.search()
AssertionError

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)
  • 执行方法四:
    目录结构:


    image.png
import unittest

if __name__ == '__main__':
    test_dir = './testcases'
    discover = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")
    unittest.TextTestRunner(verbosity=2).run(discover)
  • 测试用例执行过程


    image.png

unittest实战

import unittest

from Unittest_Demo.HTMLTestRunner import HTMLTestRunner

if __name__ == '__main__':
    report_titile = '用例执行报告 我的标题'
    desc = '用于展示修改样式后的HTMLTestRunner'
    report_file = './result.html'

    test_dir = './testcases'
    discover = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")
    unittest.TextTestRunner(verbosity=2).run(discover)

    with open(report_file,'wb') as report:
        runner = HTMLTestRunner(stream=report, title=report_titile,description=desc)
        runner.run(discover)

下一节:pytest 测试框架,包括测试用例管理框架、fixture机制、断言机制。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容