一文搞懂 unittest 单元测试框架
前言
unittest 是一款基于 Python 语言的单元测试框架。unittest 是 Python 开发包
中的一个标准模块,使用的时候直接导入即可。
官方文档:
https://docs.python.org/2/library/unittest.html
为啥要用框架?
- 方便组建测试用例
- 一键执行用例,跳过用例等
- 并生成可视化测试报告
- 他支持测试自动化,多个用例共享前置以及清理代码
官网文档例子:
import unittest
print(help(unittest))
语法规则:
-
导入unittest:import unittest
-
创建一个测试类,必须要继承unittest.TestCase类
-
创建一个测试方法,且方法要以“test” 开头
import unittest
class IntegerArithmeticTestCase(unittest.TestCase):
def testAdd(self): # test method names begin with 'test' # 测试用例的名称要以test开头
self.assertEqual((1 + 2), 3) # 断言,assertEqual 判断相等
self.assertEqual(0 + 1, 1)
def testMultiply(self):
self.assertEqual((0 * 10), 0)
self.assertEqual((5 * 8), 40)
if __name__ == '__main__':
unittest.main()
"""
执行结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
"""
unittest的执行结果:
-
2 : 表示执行了两条用例
-
. 表示测试用例执行通过
-
F 表示执行失败
-
E 表示执行错误
-
S 表示运行跳过
重要概念
- Test Case:
最小的测试单元,即测试方法。
unittest提供了TestCase基类,我们创建的测试类要继承该基类,它可以用来创建新的测试用例。
- Test Suite:
测试用例、测试套件或两者的集合,用于组装一组要运行的测试。
使用TestSuite类来创建测试套件。
- Test Runner:
Test Runner是一个组件,用于协调测试的执行并向用户提供结果。
unittest提供了TextTestRunner类运行测试用例。
- TestLoder:
是用来加载 TestCase到TestSuite中,其中有几个loadTestsFrom_()方法,就是从各个地方寻找TestCase,创建他们的实例,然后add到TestSuite中,再返回一个TestSuite实例
- TextTestResult:
测试结果会保存到TextTestResult实例中,包括运行了多少用例,成功与失败多少等信息;
- TestFixture:
测试用例的初始化准备及环境还原,主要是setUp() 和 setDown()方法;
测试用例执行顺序
unittest默认按照ASCII码的顺序加载测试用例(包括测试目录和测试文件、测试类、测试方法),即它并不是按照测试用例的创建顺序从上到下执行的。
discover() 和 main()方法的执行顺序是一样的。故想让某个测试文件先执行,可以在命名上加以控制。
如何控制顺序?
可以通过TestSuite类的addTest()方法按照一定的顺序来加载测试用例,这样想先被执行的用例就可以先加载。
import unittest
class IntegerArithmeticTestCase(unittest.TestCase):
def testAdd(self): # test method names begin with 'test'
print("加法")
self.assertEqual((1 + 2), 3)
self.assertEqual(0 + 1, 1)
def testMultiply(self):
print("乘法")
self.assertEqual((0 * 10), 0)
self.assertEqual((5 * 8), 40)
class SubtractionTestCase(unittest.TestCase):
def testSubtraction(self):
print("减法")
self.assertEqual((5 - 3), 2)
if __name__ == '__main__':
# 创建测试套件
suit = unittest.TestSuite()
suit.addTest(IntegerArithmeticTestCase("testAdd")) # 添加测试用例,添加 加法 用例到测试套件中
suit.addTest(SubtractionTestCase("testSubtraction")) # SubtractionTestCase 类名称,testSubtraction 用例名称
# 创建测试运行器
runner = unittest.TextTestRunner()
runner.run(suit)
"""
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
加法
减法
"""
Setup和Teardown
前置和后置
使用场景:
- 前置: 先登录、插入数据,再执行其他用例
- 后置: 执行完用例,退出登录,清理数据等
-
setUpModule/tearDownModule
在整个模块的开始与结束时被执行 -
setUpClass/ tearDownClass
在测试类的开始与结束时被执行 -
setUp/tearDown
在测试用例的开始与结束时被执行
# coding:utf-8
import unittest
class TestCase(unittest.TestCase):
def setUp(self):
print('这是用例的前置处理')
def tearDown(self):
print('这是用例的后置处理')
def test01(self):
print('这是第一条用例')
def test02(self):
print('这是第二条用例')
def test03(self):
print('这是第三条用例')
if __name__ == '__main__':
unittest.main()
"""
结果:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
这是用例的前置处理
这是第一条用例
这是用例的后置处理
这是用例的前置处理
这是第二条用例
这是用例的后置处理
这是用例的前置处理
这是第三条用例
这是用例的后置处理
"""
通过结果发现每执行一条用例,先执行的前置条件,然后执行用例内容,最后执行后置的条件。
正常的使用场景应该是: 登录 - 执行操作,所有操作完成后 - 退出登录
可以通过添加装饰器 @classmethod 进行执行一次前置和后置。
# coding:utf-8
import unittest
class TestCase(unittest.TestCase):
@classmethod
def setUpClass(self):
print('这是用例的前置 - 登录、插入测试数据等')
@classmethod
def tearDownClass(self):
print('这是用例的后置 - 退出登录,清理数据等')
def test01(self):
print('这是第一条用例')
def test02(self):
print('这是第二条用例')
if __name__ == '__main__':
unittest.main()
"""
结果:
这是用例的前置 - 登录、插入测试数据等
这是第一条用例
这是第二条用例
这是用例的后置 - 退出登录,清理数据等
"""
跳过测试和预期失败
有时候版本迭代后,部分功能暂时取消了,那么可以执行跳过用例的操作。
import unittest
class TestCase(unittest.TestCase):
@unittest.skip("跳过这条用例")
def test_skip(self):
print('第一条用例')
@unittest.skipIf(6 > 5, "当条件为真时跳过测试")
def test_skip_if(self):
print('第二条用例')
@unittest.skipUnless(9 > 7, "当条件为假时跳过测试")
def test_skip_unless(self):
print('第三条用例')
# 不论执行结果是什么,都将测试标记为失败
@unittest.expectedFailure
def test_fail(self):
print('第四条用例')
if __name__ == "__main__":
unittest.main()
"""
结果:
Ran 4 tests in 0.007s
FAILED (skipped=2, unexpected successes=1)
"""
discover执行多个测试用例
unittest.defaultTestLoader.discover()方法可以从多个文件中查找测试用例。
该类根据各种标准加载测试用例,并将它们返回给测试套件。
discover(start_dir, pattern='Test*.py', top_level_dir=None)
-
start_dir:待测试的模块名/测试用例目录;
-
discover()方法会自动根据这个参数查找测试用例文件
-
pattern:测试用例文件名的匹配原则
-
top_level_dir:测试模块的顶级目录,如果没有顶级目录,默认为None
# -*- coding:utf-8 -*-
import unittest
def allCase():
caseDir = "D:\\code\\ApiTest\\case" # 测试用例路径
discover = unittest.defaultTestLoader.discover(caseDir,
pattern="test*.py")
print(discover)
return discover
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(allCase())
discover 加载到的用例是一个 list 集合,需要重新写入到一个 list 对象 testcase 里,
这样就可以用 unittest 里面的 TextTestRunner 这里类的 run 方法去执行。
返回 TextTestRunner()类的实例,
调用 run 方法去执行 all_case()这个函数。
Web 自动化测试
import unittest
from selenium import webdriver
from time import sleep
class TestBaiduCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
cls.driver.implicitly_wait(5)
cls.driver.get("http://www.baidu.com")
cls.driver.maximize_window()
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def search(self, text):
self.driver.find_element_by_id("kw").clear()
self.driver.find_element_by_id("kw").send_keys(text)
self.driver.find_element_by_id("su").click()
sleep(2)
def test_search_selenium(self):
search_key = "selenium"
self.search(search_key)
self.assertEqual(self.driver.title, search_key + "_百度搜索")
def test_search_python(self):
search_key = "python"
self.search(search_key)
self.assertEqual(self.driver.title, search_key + "_百度搜索")
if __name__ == "__main__":
unittest.main()
unittest模块的常用方法/TestCase类常用属性
-
setUp()
用于测试用例执行前的初始化工作。如测试用例需要访问数据库,可以在setUp()中建立数据库连接并初始化,需要登录web,可以先实例化浏览器等。 -
tearDown()
用于测试用例执行之后的善后工作,比如关闭数据库连接,关闭浏览器等。 -
assertEqual(a, b, [msg=测试失败时打印的信息])
断言a,b两个值是否相等,相等则测试通过。msg为可选参数,用于失败时打印信息 -
assertNotEqual(a, b, [msg])
断言a,b两个值是否相等,不相等则测试通过. -
assertTrue(x, [msg])
断言x是否True,true则测试通过 -
assertFalse(x, [msg])
断言x是否False,False则测试通过 -
assertIs(a, b, [msg])
断言a是否是b,是则测试通过 -
assertNotIs(a, b, [msg])
断言a是否是b,不是则测试通过 -
assertIsNone(x, [msg])
断言x是否是None,是None则测试通过 -
assertIsNotNone(x, [msg])
断言x是否是None,是None则测试通过 -
assertIn(a, b, [msg])
断言a是否在b中,是则测试通过 -
assertNotIn(a, b, [msg])
断言a是否在b中,不在则测试通过 -
assertIsInstance(a, b, [msg])
断言a是否是b的一个实例,是则测试通过 -
assertNotIsInstance(a, b, [msg])
断言a是否是b的一个实例,不是则测试通过