unittest单元测试框架详解

对于单元测试框架来说,主要是完成以下三件事:
①提供用例组织与执行
当用例数量少时,可以不需要框架;但是当数量特别多的时候,维护性和扩展性的必要就展现出来了,此时就需要考虑用例的组织与管理
②提供丰富的比较方法
单元测试框架提供了丰富的断言方法
③提供丰富的日志
用例执行失败的原因、丰富的执行结果、总执行时间、失败用例数、成功用例数等等

1.认识unittest

单元测试是负责对最小的软件设计单元(模块)进行验证,而unittest框架 (原名PyUnit框架) 为Python语言自带的单元测试框架

1.1 认识单元测试

1.1.1 没有使用单元测试框架的单元测试

先准备一个被调用的测试方法类 Count

class Count:
    def __init__(self, a, b):
        self.a = int(a)
        self.b = int(b)

    def add(self):
        return self.a + self.b

再写一个测试类

from web.单元测试01的调用 import Count

class TestCount:
    def test_add(self):
        try:
            j = Count(2, 3)
            add = j.add()
            assert (add == 5), 'Integer addition result error!'  
            #这里的5若是换成其他数,则断言结果不相等
        except AssertionError as msg:
            print(msg)
        else:
            print('Test pass!')

mytest = TestCount()
mytest.test_add()

断言比较相等的结果:
在这里插入图片描述

断言比较不相等的结果:
在这里插入图片描述

1.1.2 使用单元测试框架的单元测试

依旧得先准备一个被调用的测试方法类 Count

class Count:
    def __init__(self, a, b):
        self.a = int(a)
        self.b = int(b)

    def add(self):
        return self.a + self.b

再写一个unittest单元测试框架的测试类

import unittest
from web.单元测试01的调用 import Count

class TestCount(unittest.TestCase):

    def setUp(self):
        print("Test start")

    def test_add(self):
        j = Count(2, 3)
        self.assertEqual(j.add(), 5)

    def tearDown(self):
        print("Test end")

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

断言比较相等的结果:
在这里插入图片描述
断言比较不相等的结果:
在这里插入图片描述

main()方法使用TestLoader类来搜索所有包含在该模块中以“test”命名开头的测试方法,并自动执行它们;

1.2 重要概念

①Test Case

一个Test Case的实例就是一个测试用例.
测试用例就是一个完整的测试流程,包括测试前准备环境的搭建(SetUp),实现测试过程的代码(run),以及测试后环境的还原(tearDown).
一个测试用例就是一个完整的测试单元,通过运行这些测试单元,可以对某一个功能就行验证

②Test Suite

一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了测试套件TestSuite的概念

③Test Runner

测试的执行,在unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行测试套件或者测试用例.
Test Runner可以使用图形界面,文本界面,或返回一个特殊的值等方式来表示测试执行的结果

④Test Fixture

对一个测试用例环境的搭建和销毁,就是一个fixture.比如说在这个测试用例中需要访问数据库,那么可以在setUp()中通过建立数据库连接来进行初始化,在tearDown()中清除数据库产生的数据,然后关闭连接等.

例子:

import unittest
from web.单元测试01的调用 import Count

class TestCount(unittest.TestCase):
    def setUp(self):
        print("Test Start")

    def test_01(self):
        j = Count(2, 4)
        self.assertEqual(j.add(), 6)

    def test_02(self):
        b = Count(67, 33)
        self.assertEqual(b.add(), 100)

    def tearDown(self):
        print("Test End")

if __name__ == '__main__':
    # 构造测试集
    suite = unittest.TestSuite()
    suite.addTest(TestCount.test_01())
    suite.addTest(TestCount.test_02())
    # 执行测试
    runner = unittest.TextTestRunner()
    runner.run(suite)

执行结果:
在这里插入图片描述

1.3 断言方法

1.3.1 assertEqual(first, second, msg=None)

案例:

import unittest

class TestCount(unittest.TestCase):
    def setUp(self):
        print("Test Start")
        number = input("Enter a number:")
        self.number = int(number)
        print(self.number)

    def test_01(self):
        self.assertEqual(self.number, 10, msg='Your input is not 10!')

    def tearDown(self):
        print("Test End")

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

执行结果:
在这里插入图片描述

1.3.2 assertTrue(expr,msg=None)

案例:

# 判断是否为质数
class Test:
    def is_prime(n):
        # 如果小于或等于1,则不是质数,返回FALSE
        if n <= 1:
            return False
        else:
            for i in range(2, n):
                # 如果能整除2-n的任意一个数,都说明不是质数,返回FALSE,否则是质数返回TRUE
                if n%i == 0:
                    return False
                else:
                    return True
import unittest
from web.单元测试05的调用 import Test

class TestCount(unittest.TestCase):
    def setUp(self) -> None:
        print("Test Start")

    def test_01(self):
        self.assertTrue(Test.is_prime(7), msg="Is not prime!")

    def tearDown(self) -> None:
        print("Test End")

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

执行结果:
在这里插入图片描述

1.3.3 assertIn(first,second,msg=None)

案例:

import unittest

class TestCount(unittest.TestCase):
    def setUp(self) -> None:
        print("Test Start")

    def test_01(self):
        a = "hello"
        b = "hello world"
        self.assertIn(a, b, msg='a is not in b')

    def tearDown(self) -> None:
        print("Test End")

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

执行结果:
在这里插入图片描述

1.4 组织单元测试用例

1.4.1 单元测试用例

案例:

import unittest

class Method():
    def __init__(self, a, b):
        self.a = int(a)
        self.b = int(b)

    def add(self):
        return self.a + self.b

    def sub(self):
        return self.a - self.b

class TestAdd(unittest.TestCase):
    def setUp(self) -> None:
        print("Add Test Start")

    def test_01(self):
        j = Method(5, 7)
        self.assertEqual(j.add(), 12)

    def test_02(self):
        j = Method(50, 57)
        self.assertEqual(j.add(), 107)

    def tearDown(self) -> None:
        print("Add Test End")

class TestSub(unittest.TestCase):
    def setUp(self) -> None:
        print("Sub Test Start")

    def test_01(self):
        j = Method(25, 20)
        self.assertEqual(j.sub(), 5)

    def test_02(self):
        j = Method(4, 4)
        self.assertEqual(j.sub(), 0)

    def tearDown(self) -> None:
            print("Sub Test End")

if __name__ == '__main__':
    # suite = unittest.TestSuite()
    # suite.addTest(TestAdd.test_01())
    # suite.addTest(TestAdd.test_02())
    # suite.addTest(TestSub.test_01())
    # suite.addTest(TestSub.test_02())
    #
    # runner = unittest.TextTestRunner()
    # runner.run(suite)
    unittest.main()

执行结果:
在这里插入图片描述
由此可见,每个类中的setUp()和tearDown()所做的事情都是一样的,分别作用于每个测试用例的开始和结束,因此可以将其封装成一个测试类

案例:

import unittest

from web.单元测试07 import Method


class MyTest(unittest.TestCase):
    def setUp(self) -> None:
        print("Test Case Start")

    def tearDown(self) -> None:
        print("Test Case End")

class TestAdd(MyTest):
    def test_01(self):
        j = Method(7, 7)
        self.assertEqual(j.add(), 14)

    def test_02(self):
        j = Method(80, 1)
        self.assertEqual(j.add(), 88)

class TestSub(MyTest):
    def test_03(self):
        j = Method(7, 7)
        self.assertEqual(j.sub(), 0)

    def test_04(self):
        j = Method(80, 1)
        self.assertEqual(j.sub(), 79)

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

执行结果:
在这里插入图片描述

1.4.2 discover(start_dir, pattern=‘test*.py’,top_level_dir=None)

start_dir :要测试的模块名或测试用例的目录
pattern=‘test*.py’ :此处匹配文件名以‘test‘开头的’.py‘类型的文件,’*‘表示任意多个字符
top_level_dir=None :测试模块的顶层目录,如果没有顶层目录,默认为None

现在通过discover()方法重新实现runtest.py文件的功能

import unittest

# 定义测试用例的目录为当前目录
test_dir = './'
discover = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')

if __name__ == '__main__':
	runner = unittest.TextTestRunner()
	runner.run(discover)

2.关于unittest

2.1 用例执行的顺序

用例执行顺序涉及多个层级:在多个测试目录的情况下,先执行哪个目录?在多个测试文件的情况下,先执行哪个文件?在多个测试类的情况下,先执行哪个测试类?在多个测试方法的情况下,先执行哪个测试方法?

unittest 框架默认根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。

案例:

import unittest

class TestBdd(unittest.TestCase):
    def setUp(self) -> None:
        print("TestBdd Start")

    def test_ccc(self):
        print("test ccc")

    def test_aaa(self):
        print("test aaa")

    def test_fff(self):
        print("test fff")

    def tearDown(self) -> None:
        print("TestBdd End")

class TestAdd(unittest.TestCase):
    def setUp(self) -> None:
        print("TestAdd Start")

    def test_bbb(self):
        print("test bbb")

    def test_eee(self):
        print("test eee")

    def tearDown(self) -> None:
        print("TestAdd End")

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

执行结果:
在这里插入图片描述
由上图可知,TestAdd 类会优先于 TestBdd 类被执行,test_aaa() 方法会优先于 test_ccc() 被执行,并没有按照用例从上到下的顺序执行。

如果想让 test_ccc() 比 test_aaa() 先执行呢?这时候就需要通过 TestSuite 类的 addTest() 方法按照一定的顺序加载

案例:

import unittest

class TestBdd(unittest.TestCase):
    def setUp(self):
        print("TestBdd Start")

    def test_ccc(self):
        print("test ccc")

    def test_aaa(self):
        print("test aaa")

    def test_fff(self):
        print("test fff")

    def tearDown(self):
        print("TestBdd End")

class TestAdd(unittest.TestCase):
    def setUp(self):
        print("TestAdd Start")

    def test_bbb(self):
        print("test bbb")

    def test_eee(self):
        print("test eee")

    def tearDown(self):
        print("TestAdd End")

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(TestBdd("test_ccc"))
    suite.addTest(TestBdd("test_aaa"))
    suite.addTest(TestBdd("test_fff"))
    suite.addTest(TestAdd("test_eee"))
    suite.addTest(TestAdd("test_bbb"))

    runner = unittest.TextTestRunner()
    runner.run(suite)

执行结果:
在这里插入图片描述
除了使用addTest()方法来调整方法执行的优先级外,还可以通过命名来控制。

如果使用addTest()来调整方法执行的优先级没有起作用,还是按原来的顺序执行,这是受unittest框架的影响的原因。
如何解决呢?【下列截图按顺序执行】
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.2 执行多级目录的用例

当测试用例达到一定量级时,就要考虑划分目录,比如下面这个:

  • test_project/test_case/
    • test_bbb/
      • test_b.py
      • test_ccc/
        • test_c.py
    • test_ddd/
      • test_d.py
    • test_a.py

上面的目录结构,如果将discover()方法中的start_dir参数定义为“./test_case/”目录,那么只能加载 test_a.py中的测试用例。怎么让unittest框架查找到test_case的子目录中的测试文件呢?在每个子目录下放一个__init__.py 文件就行了。

2.3 跳过测试和预期失败

//无条件跳过,说明跳过的原因
unittest.skip(reason)
// 判断条件为真时,跳过
unittest.skipIf(condition,reason)
// 判断条件为假时,跳过
unittest.skipUnless(condition,reason)
// 不管测试条件结果怎样,都统一标记为失败,且不会抛出错误信息
unittest.expectedFailure()

案例:

import unittest

class TestCount(unittest.TestCase):
    def setUp(self) -> None:
        print("test start")

    @unittest.skip("直接跳过测试")
    def test_01(self):
        print("test_01")

    @unittest.skipIf(2 < 5, "条件为TRUE时跳过")
    def test_02(self):
        print("test_02")

    @unittest.skipUnless(3 < 5, "条件为FALSE时跳过")
    def test_03(self):
        print("test_03")

    @unittest.expectedFailure
    def test_04(self):
        print("test_04")

    def tearDown(self) -> None:
        print("test end")

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

执行结果:
在这里插入图片描述

2.4 fixtures

fixtures在前面就简单介绍过,对一个测试用例环境的搭建和销毁,就是一个fixture。除此之外,unittest还提供了更大的范围fixtures,比如测试类、模块的fixtures。

案例:

import unittest

def setUpModule():
    print("TestModule Start")

def tearDownModule():
    print("TestModule End")

class Test(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        print("TestClass Start")

    @classmethod
    def tearDownClass(cls) -> None:
        print("TestClass End")

    def setUp(self) -> None:
        print("Test Start")

    def tearDown(self) -> None:
        print("Test End")

    def test_01(self):
        print("Test_01")

    def test_02(self):
        print("Test_02")

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

执行结果:
在这里插入图片描述

由此可见
setUpModule、tearDownModule:在整个模块开始和结束时被执行
setUpClass、tearDownClass:在测试类开始与结束时被执行
setUp、tearDown:在测试用例的开始与结束时被执行

2.5 保存测试结果

在编写脚本的地方新建一个login.txt 文件,然后打开Windows命令提示符,如下图所示:切换到当前脚本所在目录,然后输入 python xx.py >> login.txt 2>&1 回车
在这里插入图片描述
回车之后,这时的 login.txt 文件就变成了这样:
在这里插入图片描述

解释 python xx.py >> login.txt 2>&1
python xx.py >> login.txt 就是将 xx.py 脚本执行的结果重定向到 login.txt中
2>&1中:
0 表示stdin标准输入,用户键盘输入的内容
1 表示stdout标准输出,输出到显示屏的内容
2 表示stderr标准错误,报错内容
2>&1是一个整体,>左右不能有空格,即将错误内容重定向输入到标准输出中去。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值