unittest与测试
unittest 是 Python 标准库中的单元测试框架,灵感来自 Java 的 JUnit。它支持测试用例组织、断言、测试夹具(fixture)、测试套件等功能,是 Python 2.7 中编写自动化测试的标准工具。
基本测试用例
import unittest
def add(a, b):
return a + b
class TestMathOperations(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(1, 2), 3)
def test_add_negative(self):
self.assertEqual(add(-1, -2), -3)
def test_add_zero(self):
self.assertEqual(add(0, 5), 5)
if __name__ == "__main__":
unittest.main()
运行测试:
python test_math.py
python test_math.py -v # 详细输出
常用断言
import unittest
class TestAssertions(unittest.TestCase):
def test_equal(self):
self.assertEqual(1 + 1, 2)
self.assertNotEqual(1 + 1, 3)
def test_true_false(self):
self.assertTrue(1 == 1)
self.assertFalse(1 == 2)
def test_none(self):
self.assertIsNone(None)
self.assertIsNotNone("hello")
def test_in(self):
self.assertIn(2, [1, 2, 3])
self.assertNotIn(4, [1, 2, 3])
def test_raises(self):
with self.assertRaises(ZeroDivisionError):
1 / 0
def test_almost_equal(self):
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)
def test_greater(self):
self.assertGreater(5, 3)
self.assertLess(3, 5)
if __name__ == "__main__":
unittest.main()
测试夹具(Fixture)
setUp 和 tearDown 在每个测试方法前后执行:
import unittest
import tempfile
import os
class TestFileOperations(unittest.TestCase):
def setUp(self):
"""每个测试前创建临时文件。"""
self.temp_file = tempfile.mkstemp()[1]
def tearDown(self):
"""每个测试后清理。"""
if os.path.exists(self.temp_file):
os.remove(self.temp_file)
def test_write_read(self):
with open(self.temp_file, "w") as f:
f.write("hello")
with open(self.temp_file, "r") as f:
content = f.read()
self.assertEqual(content, "hello")
if __name__ == "__main__":
unittest.main()
setUpClass 和 tearDownClass(类方法,Python 2.7+)在所有测试前后执行一次:
import unittest
class TestDatabase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.connection = create_test_database()
@classmethod
def tearDownClass(cls):
cls.connection.close()
def test_query(self):
result = self.connection.query("SELECT 1")
self.assertEqual(result, [(1,)])
跳过测试
import unittest
import sys
class TestPlatformSpecific(unittest.TestCase):
@unittest.skip("Not implemented yet")
def test_future_feature(self):
pass
@unittest.skipIf(sys.platform == "win32", "Not supported on Windows")
def test_unix_feature(self):
import pwd
pwd.getpwuid(0)
@unittest.skipUnless(sys.version_info >= (2, 7), "Requires Python 2.7+")
def test_new_feature(self):
pass
if __name__ == "__main__":
unittest.main()
测试组织
测试发现:
# 自动发现当前目录下的测试
python -m unittest discover
# 指定目录和模式
python -m unittest discover -s tests -p "test_*.py" -v
测试套件:
import unittest
from test_math import TestMathOperations
from test_string import TestStringOperations
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestMathOperations))
suite.addTest(unittest.makeSuite(TestStringOperations))
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
Mock(模拟对象)
Python 2.7 标准库没有 unittest.mock(Python 3.3+),需要安装 mock 包:
pip install mock
import unittest
try:
from unittest.mock import Mock, patch
except ImportError:
from mock import Mock, patch
class TestAPIClient(unittest.TestCase):
@patch("urllib2.urlopen")
def test_fetch_data(self, mock_urlopen):
# 模拟响应
mock_response = Mock()
mock_response.read.return_value = '{"status": "ok"}'
mock_urlopen.return_value = mock_response
result = fetch_data("http://api.example.com")
self.assertEqual(result["status"], "ok")
mock_urlopen.assert_called_once()
if __name__ == "__main__":
unittest.main()
实际应用
测试驱动开发示例:
import unittest
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
class TestPrime(unittest.TestCase):
def test_small_primes(self):
self.assertTrue(is_prime(2))
self.assertTrue(is_prime(3))
self.assertTrue(is_prime(5))
self.assertTrue(is_prime(7))
def test_non_primes(self):
self.assertFalse(is_prime(1))
self.assertFalse(is_prime(4))
self.assertFalse(is_prime(9))
def test_edge_cases(self):
self.assertFalse(is_prime(0))
self.assertFalse(is_prime(-1))
if __name__ == "__main__":
unittest.main()
测试 Web 应用:
import unittest
from myapp import create_app
class TestWebApp(unittest.TestCase):
def setUp(self):
self.app = create_app(testing=True)
self.client = self.app.test_client()
def test_homepage(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
self.assertIn("Welcome", response.data)
def test_404(self):
response = self.client.get("/nonexistent")
self.assertEqual(response.status_code, 404)
if __name__ == "__main__":
unittest.main()
测试最佳实践
- 测试方法名以
test_开头 - 每个测试只验证一个概念
- 使用
setUp创建共享的测试数据 - 测试应该是独立的,不依赖执行顺序
- 运行要快,便于频繁执行
- 覆盖正常路径和边界情况
与 pytest 的选择
| 特性 | unittest | pytest |
|---|---|---|
| 标准库 | 是 | 否(需安装) |
| 断言风格 | 方法调用 | 原生 assert |
| 插件生态 | 少 | 丰富 |
| 参数化测试 | 较繁琐 | @pytest.mark.parametrize |
| 兼容性 | Python 2/3 | Python 2.7+(需旧版本) |
Python 2.7 项目中,unittest 无需安装,是稳妥的选择。如果允许安装第三方库,pytest 的语法更简洁。