在 Python 中,单元测试(unit testing)是一种将代码分割成独立的小片段(单元)并进行测试的过程,以确保每个单元都能按预期工作。
Python 自带的unittest
模块提供了一个全面的框架,用于编写和运行测试。
如果你听说过“测试驱动开发”(TDD:Test-Driven Development),单元测试就不陌生。
1. 编写测试用例 #
使用 unittest.TestCase 创建测试用例类,每个测试方法都以 test 开头。以下是一个简单的例子:
import unittest
# 要测试的代码
def add(a, b):
return a + b
# 测试用例类
class TestAddFunction(unittest.TestCase):
def test_add_integers(self):
self.assertEqual(add(1, 2), 3)
def test_add_floats(self):
self.assertAlmostEqual(add(1.2, 2.3), 3.5)
def test_add_strings(self):
self.assertEqual(add('foo', 'bar'), 'foobar')
if __name__ == '__main__':
unittest.main()
2. 断言方法 #
unittest
提供了多种断言方法,用于检查测试结果是否符合预期:assertEqual(a, b)
:断言a == b
assertNotEqual(a, b)
:断言a != b
assertTrue(x)
:断言x
为True
assertFalse(x)
:断言x
为False
assertIs(a, b)
:断言a is b
assertIsNot(a, b)
:断言a is not b
assertIsNone(x)
:断言x
为None
assertIsNotNone(x)
:断言x
不是None
assertIn(a, b)
:断言a
在b
中assertNotIn(a, b)
:断言a
不在b
中assertIsInstance(a, b)
:断言a
是b
的实例assertNotIsInstance(a, b)
:断言a
不是b
的实例
3. 组织测试 #
可以将多个测试用例组织到测试套件中,并使用测试运行器运行它们。 测试套件(Test Suite)是一种将多个测试用例(Test Case)组合在一起的方式,方便统一管理和运行。它可以包含不同测试用例类的实例,从而可以在一次运行中执行多个测试用例。
import unittest
# 测试用例类
class TestMathFunctions(unittest.TestCase):
def test_add(self):
self.assertEqual(add(1, 2), 3)
def test_subtract(self):
self.assertEqual(subtract(10, 5), 5)
# 测试套件
def suite():
suite = unittest.TestSuite()
suite.addTest(TestMathFunctions('test_add'))
suite.addTest(TestMathFunctions('test_subtract'))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
可以将多个测试用例类的测试方法添加到同一个测试套件中:
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 suite():
suite = unittest.TestSuite()
suite.addTest(TestMathFunctions('test_add'))
suite.addTest(TestMathFunctions('test_subtract'))
suite.addTest(TestStringMethods('test_upper'))
suite.addTest(TestStringMethods('test_isupper'))
return suite
- 这种方式允许将所有需要测试的方法都包含在一个测试套件中,从而可以统一管理和运行这些测试。
- 使用测试套件可以有效地组织和运行多个测试用例。通过将测试用例添加到测试套件中,可以方便地批量运行测试,提高测试的效率和可维护性.
4. 设置和清理 #
在单元测试中,设置(setup)和清理(teardown)方法用于在每个测试方法执行前后进行初始化和清理操作。这有助于确保每个测试在一个干净的环境中运行,避免测试之间的相互干扰。
-
setUp
和tearDown
方法setUp
方法:在每个测试方法执行之前运行,用于初始化测试所需的环境。tearDown
方法:在每个测试方法执行之后运行,用于清理setUp
方法创建的环境。
-
以下是一个使用 setUp 和 tearDown 方法的示例:
import unittest
class TestExample(unittest.TestCase):
def setUp(self):
# 在每个测试方法执行前调用
self.test_data = [1, 2, 3, 4, 5]
print("setUp: 初始化测试数据")
def tearDown(self):
# 在每个测试方法执行后调用
self.test_data = None
print("tearDown: 清理测试数据")
def test_sum(self):
# 测试求和函数
self.assertEqual(sum(self.test_data), 15)
print("test_sum: 测试求和函数")
def test_max(self):
# 测试最大值函数
self.assertEqual(max(self.test_data), 5)
print("test_max: 测试最大值函数")
if __name__ == '__main__':
# 如果文件作为脚本直接运行,调用 unittest.main() 来运行所有的测试
unittest.main()
-
为什么使用
setUp
和tearDown
?- 确保每个测试方法在独立的环境中运行:
setUp
和tearDown
方法确保每个测试方法都有一个干净的起始环境,避免测试方法之间的状态影响。
- 减少码重复:
- 如果多个测试方法需要相同的初始化和清理代码,可以将这些代码放在
setUp
和tearDown
方法中,而不是在每个测试方法中重复。
- 如果多个测试方法需要相同的初始化和清理代码,可以将这些代码放在
- 提高测试的可维护性:
- 将初始化和清理逻辑集中在
setUp
和tearDown
方法中,使测试代码更加简洁和易于维护。
- 将初始化和清理逻辑集中在
- 确保每个测试方法在独立的环境中运行:
-
更复杂的设置和清理
- 在实际应用中,设置和清理操作可能涉及数据库连接、文件操作或网络通信等更复杂的情况。
unittest
模块还提供了setUpClass
和tearDownClass
方法,用于在整个测试类的所有测试方法执行之前和之后进行一次性设置和清理操作。import unittest class TestDatabaseOperations(unittest.TestCase): @classmethod def setUpClass(cls): # 在测试类的所有测试方法执行之前调用一次 cls.db_connection = cls.create_db_connection() print("setUpClass: 创建数据库连接") @classmethod def tearDownClass(cls): # 在测试类的所有测试方法执行之后调用一次 cls.db_connection.close() print("tearDownClass: 关闭数据库连接") def setUp(self): # 在每个测试方法执行之前调用 self.cursor = self.db_connection.cursor() print("setUp: 创建数据库游标") def tearDown(self): # 在每个测试方法执行之后调用 self.cursor.close() print("tearDown: 关闭数据库游标") def test_insert(self): # 测试插入操作 self.cursor.execute("INSERT INTO test_table (name) " "VALUES ('test')") self.db_connection.commit() print("test_insert: 测试插入操作") def test_query(self): # 测试查询操作 self.cursor.execute("SELECT * FROM test_table") result = self.cursor.fetchall() self.assertGreater(len(result), 0) print("test_query: 测试查询操作") @staticmethod def create_db_connection(): # 假设返回一个数据库连接对象 return DummyDBConnection() class DummyDBConnection: # 一个模拟的数据库连接类,用于示例 def cursor(self): return DummyCursor() def close(self): print("DummyDBConnection: 连接已关闭") class DummyCursor: # 一个模拟的数据库游标类,用于示例 def execute(self, query): print(f"DummyCursor: 执行查询: {query}") def fetchall(self): return [('test',)] def close(self): print("DummyCursor: 游标已关闭") if __name__ == '__main__': unittest.main()
setUpClass
和tearDownClass
方法:setUpClass
:在测试类的所有测试方法执行之前调用一次,用于创建数据库连接。tearDownClass
:在测试类的所有测试方法执行之后调用一次,用于关闭数据库连接。
setUp
和tearDown
方法:setUp
:在每个测试方法执行之前调用,用于创建数据库游标。tearDown
:在每个测试方法执行之后调用,用于关闭数据库游标。
- 测试方法
test_insert
和test_query
:test_insert
:测试插入操作,执行插入语句并提交事务。test_query
:测试查询操作,执行查询语句并验证结果。
- 通过使用
setUpClass
、tearDownClass
、setUp
和tearDown
方法,可以更好地管理复杂的测试环境,确保测试代码的简洁性和可维护性。