Python Unittest测试方法执行顺序深度解析与解决方案
本文深入解析Python Unittest框架中测试方法的默认执行机制,指出其依据字典序而非编写顺序执行的特点。针对由此引发的测试依赖问题,提供了两种解决方案:一是通过补零命名法调整方法名以符合字典序;二是继承TestLoader类重写getTestCaseNames方法,按代码定义顺序执行测试用例,确保测试逻辑的准确性与可维护性。
Unittest
对于许多开发者而言,unittest并不陌生。在五六年前,它曾是博主进行单元测试的首选框架,但近年来,其市场份额正逐渐被pytest和nose等新兴工具所侵蚀。
随着社区讨论重心的转移,越来越多的开发者从使用unittest结合HTMLTestRunner转向了pytest搭配allure2等更现代化的测试组合。
这不禁让人感叹技术迭代之快,仿佛自己已跟不上时代发展的步伐。
回到主题
尽管unittest的使用热度有所下降,但它依然是一款功能全面且成熟的测试框架,值得深入理解。
近日在技术交流群中,一位群友的发言引发了我对过往经历的回忆。
此前,我已知晓unittest中测试方法的执行顺序并非按照代码编写的先后顺序,而是遵循字典序规则。遗憾的是,当时我往往采取变通手段来规避这一问题,具体做法将在后文详述。
接下来,我们将深入探讨unittest类中test方法的实际执行顺序机制。
源码初窥
通过研读unittest.TestLoader的源码可以发现,当加载某个类下的测试方法时,原生Loader会对测试方法列表进行排序,并利用functools.cmp_to_key方法对排序逻辑进行处理。

值得注意的是,unittest无需开发者手动指定具体要执行的方法,它会自动从类中提取所有以test开头的方法,并将其识别为测试用例。

进一步查询self.sortTestMethodsUsing属性,可以发现这是一个用于定义排序方式的配置项。

从源码实现来看,该比较逻辑非常明确:若x小于y则返回-1,x等于y返回0,x大于y返回1。
许多开发者可能不了解Python中字符串比较的底层逻辑,这里需要特别说明字典序的概念。参考以下示例:
a = "abc" b = "abcd" c = "abce" print(a > b) print(b > c)
预测一下执行结果,显然,字典序的比较是基于A-Z字母顺序进行的。如果前缀相同但字符串长度不同,则较长字符串的字典序排在后面。

理解了字典序规则后,我们可以将unittest查找并执行用例的过程简化为以下步骤:
- 首先,定位到对应类下所有以test开头的测试方法
- 其次,对这些方法名进行字典序排序
- 最后,按排序后的顺序依次执行测试
这就解释了为什么有时编写的测试用例并不按照我们预期的顺序执行。
回到问题的本质
明确了用例执行混乱的原因,便能找到对应的解决策略。由于直接修改源码并不推荐,因此我们提供两种可行的替代方案。
假设存在多个test方法,例如:
class Testcase(unittest.TestCase):
def setUp(self) -> None:
pass
def test_1(self):
print("执行第一个")
def test_2(self):
print("第二个")
def test_3(self):
print("第三个")
def test_10(self):
print("第四个")
def test_11(self):
print("第五个")
def tearDown(self) -> None:
pass
if __name__ == "__main__":
unittest.main()
实际执行顺序按字典序排列,结果为test_1、test_10、test_11、test_2、test_3。

1. 以字典序的方式编写test方法
我们可以手动调整test方法的命名,这也是我早期采用的处理方式。即通过命名让期望先执行的用例在字典序中排在前面:
class Testcase(unittest.TestCase):
def setUp(self) -> None:
pass
def test_0_1(self):
print("执行第一个")
def test_0_2(self):
print("第二个")
def test_0_3(self):
print("第三个")
def test_1_0(self):
print("第四个")
def test_1_1(self):
print("第五个")
def tearDown(self) -> None:
pass
具体做法是将数字部分按位数拆分,对于个位数,在十位补0,例如将test_1改为test_0_1。若预计有100个用例,则需补两个0,如test_0_0_1。通常情况下,单个文件中的用例数量不会过多,因此这种方法易于实施。
如果遇到如test_login这类非数字结尾的方法名,该如何处理?
处理逻辑类似,可以统一采用test_数字_业务描述的命名模式。有开发者编写了专门的装饰器来解决此类问题,大家可以参考相关实现。
2. 回归本质,从根本解决问题
方案一虽然有效,但依赖装饰器且改变了方法本身的名称。我们可以从排序机制入手,通过自定义排序逻辑,使测试方法按照代码编写的顺序执行,从而更自然地达到预期目的。
具体思路如下:
- 手写一个继承自TestLoader的新类,并重写其中的排序逻辑
- 在unittest运行时,传入这个自定义的Loader实例
以下是完整代码,注释部分已做详细说明。
import unittest
class MyTestLoader(unittest.TestLoader):
def getTestCaseNames(self, testcase_class):
# 调用父类的获取“测试方法”函数
test_names = super().getTestCaseNames(testcase_class)
# 拿到测试方法list
testcase_methods = list(testcase_class.__dict__.keys())
# 根据list的索引对testcase_methods进行排序
test_names.sort(key=testcase_methods.index)
# 返回测试方法名称
return test_names
class Testcase(unittest.TestCase):
def setUp(self) -> None:
pass
def test_1(self):
print("执行第一个")
def test_2(self):
print("第二个")
def test_3(self):
print("第三个")
def test_10(self):
print("第四个")
def test_11(self):
print("第五个")
def tearDown(self) -> None:
pass
if __name__ == "__main__":
unittest.main(testLoader=MyTestLoader())

执行后发现结果仍不符合预期,这是否意味着代码有误?
问题可能出在PyCharm默认使用的unittest调试模式上,我们需要将其更改为普通运行方式。




尝试通过控制台直接执行脚本:

总结
至此,关于Python Unittest测试方法执行顺序的解析已全部介绍完毕。如需了解更多Python Unittest执行顺序相关内容,欢迎查阅本站历史文章或继续浏览后续教程,感谢大家的支持!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









