Python中如何针对不同被测对象复用测试用例?
问题描述
我有同一功能的多个实现,希望复用测试用例,和常见的Python单元测试基类子类场景不同的是,待复用的测试会执行由子类设置的不同被测对象(subjects-under-test)。我写了如下代码,但运行后没有执行任何测试,尝试自定义TestBase.__init__(包括用**kwargs)也没用,请问该如何对TestBase进行参数化?
原代码:
class TestBase(unittest.TestCase): def __init__(self, sut: Callable[[int], int]): super().__init__() self.sut = sut def test_1(self): expected = 1 n = 1 actual = self.sut(n) self.assertEqual(expected, actual) def test_2(self): expected = 2 n = 2 actual = self.sut(n) self.assertEqual(expected, actual) class TestImplA(TestBase): def setUp(self): super().sut = impl_a class TestImplB(TestBase): def setUp(self): super().sut = impl_b
解决方案
问题核心是你重写了TestBase的__init__方法,但unittest.TestCase的构造函数签名有固定要求,自定义参数会导致测试运行器无法正确实例化测试类,同时子类的setUp写法也不符合规范。以下是几种可行的解决方法:
方法一:通过类属性传递被测对象(最简洁)
修改基类,去掉自定义的__init__,改用类属性让子类指定被测对象,再在setUp中完成初始化:
import unittest from typing import Callable # 模拟被测实现 def impl_a(n: int) -> int: return n def impl_b(n: int) -> int: return n class TestBase(unittest.TestCase): sut: Callable[[int], int] = None # 基类定义占位属性 def setUp(self): if self.sut is None: raise NotImplementedError("子类必须设置sut属性") def test_1(self): self.assertEqual(self.sut(1), 1) def test_2(self): self.assertEqual(self.sut(2), 2) class TestImplA(TestBase): sut = impl_a class TestImplB(TestBase): sut = impl_b if __name__ == "__main__": unittest.main()
这种方式不需要额外依赖,子类只需一行代码指定被测对象,测试运行器能正常识别并执行所有测试方法。
方法二:使用参数化测试(适合多实现批量验证)
如果不想创建多个子类,可以用unittest自带的subTest上下文管理器,或者第三方库parameterized来批量测试所有实现:
标准库实现
import unittest from typing import Callable def impl_a(n: int) -> int: return n def impl_b(n: int) -> int: return n class TestAllImpls(unittest.TestCase): def test_all_implementations(self): # 定义所有要测试的实现 implementations = [("ImplA", impl_a), ("ImplB", impl_b)] for name, sut in implementations: with self.subTest(implementation=name): self.assertEqual(sut(1), 1) self.assertEqual(sut(2), 2) if __name__ == "__main__": unittest.main()
第三方库parameterized实现
先通过pip install parameterized安装依赖,再编写代码:
import unittest from parameterized import parameterized from typing import Callable def impl_a(n: int) -> int: return n def impl_b(n: int) -> int: return n class TestAllImpls(unittest.TestCase): @parameterized.expand([ ("ImplA", impl_a), ("ImplB", impl_b), ]) def test_implementations(self, name, sut): self.assertEqual(sut(1), 1) self.assertEqual(sut(2), 2) if __name__ == "__main__": unittest.main()
方法三:动态生成测试类(保留基类子类结构)
如果坚持要使用基类子类的结构,也可以通过动态生成测试类的方式,避免手动编写重复的子类代码:
import unittest from typing import Callable def impl_a(n: int) -> int: return n def impl_b(n: int) -> int: return n def create_test_class(sut: Callable[[int], int], class_name: str) -> type: class TestImpl(TestBase): def setUp(self): self.sut = sut TestImpl.__name__ = class_name return TestImpl class TestBase(unittest.TestCase): sut: Callable[[int], int] = None def test_1(self): self.assertEqual(self.sut(1), 1) def test_2(self): self.assertEqual(self.sut(2), 2) # 动态生成测试类 TestImplA = create_test_class(impl_a, "TestImplA") TestImplB = create_test_class(impl_b, "TestImplB") if __name__ == "__main__": unittest.main()
内容的提问来源于stack exchange,提问作者Noel Yap
相关产品推荐
相关产品推荐

