1-单元测试的概念
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
def demo(a,b):
return a / b
假设我们有一个方法demo,对于这个方法我们可以简单的进行如下的单元测试:
- a , b为 4, 2我们期待输出2
- a为1 , b为0,期待输出ZeroDivisionError
- a为字符串, b为字符串,则期待输出TypeError
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。
2-单元测试的工作原理
2.1 unittest最核心的四个组件:TestCase,TestSuite,TestRunner,TestFixture:
- test fixture:为了开展一项或多项测试所需要进行的准备工作
- TestCase:一个测试用例是一个独立的测试单元。它检查输入特定的数据时的响应。UnitTest 提供一个基类(TestCase )用于新建测试用例。
- test suite: 是一系列的测试用例,或测试套件,或两者皆有(TestSuite也可以嵌套TestSuite)。它用于归档需要一起执行的测试
- test runner:一个用于执行和输出测试结果的组件。这个运行器可能使用图形接口、文本接口,或返回一个特定的值表示运行测试的结果。
2.2 工作原理:
- 一个TestCase的实例就是一个测试用例。测试用例就是指一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。
- 而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite
- TestLoader是用来加载TestCase到TestSuite中的
- TextTestRunner是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法
- 测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。
他们之间的关系:整个流程就是首先要写好TestCase,然后由TestLoader (用来加载TestCase到TestSuite中的) 加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,整个过程集成在unittest.main模块中。
3-简单实例
import unittest
class UtMber(unittest.TestCase):
def setUp(self):
print ("setUp执行了")
def test_multiply(self):
c = 1 * 2
print("test_multiply")
self.assertEqual(c, 2)
def test_add(self):
c = 1 + 1
print("test_add")
self.assertEqual(c, 2)
# 跳过测试(参数是原因)
@unittest.skip("我就是想跳过")
def test_skip(self):
print("test_skip")
self.assertEqual(1, 2)
# 跳过测试(第一个参数是状态(布尔),第二个原因)
@unittest.skipIf(True, "我也想跳过测试")
def test_skipif(self):
print("test_skipif")
self.assertEqual(1, 2)
def tearDown(self):
print ("tearDown执行了")
if __name__ == '__main__':
unittest.main()
执行结果
setUp执行了
..ss
test_add
tearDown执行了
setUp执行了
test_multiply
----------------------------------------------------------------------
tearDown执行了
Ran 4 tests in 0.000s
OK (skipped=2)
从上面结果我们可以得出结论:
-
setUp:表示前置条件,它在每一个用例执行之前必须会执行一次
-
tearDown:表示释放资源,它在每次用例执行完之后会执行一次
-
单元测试必须以test开头,执行顺序与字母顺序有关(test_multiply虽然在前面但是test_add先执行)
-
unittest.skip,unittest.skipIf可以跳过执行单元测试
4-批量运行测试用例
第一种是通过手动添加,通过unittest.Testsuite来管理用例集,通过addTest添加用例方法,再通过实例化TextTestrunner运行测试
#coding:utf-8
import unittest
class DemoTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
print ("setUpClass")
@classmethod
def tearDownClass(cls):
print ("tearDownClass")
def test_add(self):
a = 12 + 8
self.assertEqual(a, 20)
print ('12+8=',a)
def test_sub(self):
b = 3-2
self.assertEqual(b, 1)
print ('3-2=',3-2)
if __name__=="__main__":
#实例化用例集
suite = unittest.TestSuite()
suite.addTest(DemoTest("test_add"))
suite.addTest(DemoTest("test_sub"))
#实例化运行类
runner = unittest.TextTestRunner()
#运行用例集,会根据添加的用例顺序进行用例的执行
runner.run(suite)
第二种是通过自动识别用例的目录对所有的用例进行运行。使用到discover()函数
#coding:utf-8
import unittest
import os
class demoTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
print "前置测试条件"
@classmethod
def tearDownClass(cls):
print "结束测试条件"
def test_add(self):
a = 12 + 8
self.assertEqual(a, 20)
print ('12+8=',a)
def test_sub(self):
b = 3-2
self.assertEqual(b, 1)
print ('3-2=',3-2)
if __name__=="__main__":
test_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)))#获取当前工作目录
#用discover的方法去覆盖所有的测试用例
# test_dir 要执行的测试用例目录
# test*表示匹配以test开头的所有.py文件
# top_level_dir=None 测试模块的顶级目录,没有的话就写None
discover = unittest.defaultTestLoader.discover(test_dir,pattern="test*.py",top_level_dir=None)
runner = unittest.TextTestRunner() #实例化runner类
runner.run(discover) #执行路径中匹配的所有test*.py用例
5-实际案例
我自己封装了一个单元测试例子,在github上,大家可以参考下。github链接。
推荐阅读:
官方文档
注意:unittest比较简单,看前两个链接就可以快速入门,特别第二个