如何在Django中随机打乱并运行指定数量的测试子集?
需求说明
我们基于Django开展测试工作,现有约6000个测试用例,全量运行耗时约40分钟。需要实现以下功能:
- 通过命令行参数指定要运行的随机测试用例数量(例如200,可自定义)
- 该参数需支持与Django自带的
--shuffle参数配合使用 - 若总测试用例数少于指定数量,则自动运行全部测试
现有代码
speedy/core/base/management/commands/test.py
from django.core.management.commands import test class Command(test.Command): def add_arguments(self, parser): super().add_arguments(parser=parser) parser.add_argument( "--test-all-languages", action="store_true", help="If run with this argument, test all languages, and don't skip languages.", )
speedy/core/base/test/models.py
class SiteDiscoverRunner(DiscoverRunner): def __init__(self, *args, **kwargs): assert (django_settings.TESTS is True) super().__init__(*args, **kwargs) self.test_all_languages = kwargs.get('test_all_languages', False) def build_suite(self, test_labels=None, extra_tests=None, **kwargs): if (not (test_labels)): # Default test_labels are all the relevant directories under "speedy". For example ["speedy.core", "speedy.net"]. # Due to problems with templates, "speedy.match" label is not added to speedy.net tests, and "speedy.net" label is not added to speedy.match tests. # ~~~~ TODO: fix this bug and enable these labels, although the tests there are skipped. test_labels = [] for label in django_settings.INSTALLED_APPS: if (label.startswith('speedy.')): label_to_test = '.'.join(label.split('.')[:2]) if (label_to_test == 'speedy.net'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_NET_SITE_ID) elif (label_to_test == 'speedy.match'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_MATCH_SITE_ID) elif (label_to_test == 'speedy.composer'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_COMPOSER_SITE_ID) elif (label_to_test == 'speedy.mail'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_MAIL_SOFTWARE_SITE_ID) else: add_this_label = True if (add_this_label): if (not (label_to_test in test_labels)): test_labels.append(label_to_test) print(test_labels) return super().build_suite(test_labels=test_labels, extra_tests=extra_tests, **kwargs) def setup_test_environment(self, **kwargs): super().setup_test_environment(**kwargs) django_settings.TEST_ALL_LANGUAGES = self.test_all_languages def teardown_test_environment(self, **kwargs): super().teardown_test_environment(**kwargs) del django_settings.TEST_ALL_LANGUAGES
实现方案
1. 添加命令行参数
修改speedy/core/base/management/commands/test.py,新增--random-count参数:
from django.core.management.commands import test class Command(test.Command): def add_arguments(self, parser): super().add_arguments(parser=parser) parser.add_argument( "--test-all-languages", action="store_true", help="If run with this argument, test all languages, and don't skip languages.", ) # 新增随机测试数量参数 parser.add_argument( "--random-count", type=int, default=None, help="Run a random subset of N test cases. If N exceeds total tests, run all. Compatible with --shuffle.", )
2. 修改测试运行器逻辑
修改speedy/core/base/test/models.py中的SiteDiscoverRunner,添加随机抽取测试用例的逻辑:
import random from django.test import TestSuite class SiteDiscoverRunner(DiscoverRunner): def __init__(self, *args, **kwargs): assert (django_settings.TESTS is True) super().__init__(*args, **kwargs) self.test_all_languages = kwargs.get('test_all_languages', False) # 接收随机数量参数 self.random_count = kwargs.get('random_count', None) def build_suite(self, test_labels=None, extra_tests=None, **kwargs): if (not (test_labels)): test_labels = [] for label in django_settings.INSTALLED_APPS: if (label.startswith('speedy.')): label_to_test = '.'.join(label.split('.')[:2]) if (label_to_test == 'speedy.net'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_NET_SITE_ID) elif (label_to_test == 'speedy.match'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_MATCH_SITE_ID) elif (label_to_test == 'speedy.composer'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_COMPOSER_SITE_ID) elif (label_to_test == 'speedy.mail'): add_this_label = (django_settings.SITE_ID == django_settings.SPEEDY_MAIL_SOFTWARE_SITE_ID) else: add_this_label = True if (add_this_label): if (not (label_to_test in test_labels)): test_labels.append(label_to_test) print(test_labels) suite = super().build_suite(test_labels=test_labels, extra_tests=extra_tests, **kwargs) # 新增随机抽取逻辑 if self.random_count is not None: # 提取所有测试用例到列表 all_tests = [] for test_case in suite: if hasattr(test_case, '_tests'): all_tests.extend(test_case._tests) else: all_tests.append(test_case) total = len(all_tests) if self.random_count >= total: selected_tests = all_tests else: # 随机抽取指定数量的用例 selected_tests = random.sample(all_tests, self.random_count) # 构建新的测试套件 new_suite = TestSuite() for test in selected_tests: new_suite.addTest(test) suite = new_suite return suite def setup_test_environment(self, **kwargs): super().setup_test_environment(**kwargs) django_settings.TEST_ALL_LANGUAGES = self.test_all_languages def teardown_test_environment(self, **kwargs): super().teardown_test_environment(**kwargs) del django_settings.TEST_ALL_LANGUAGES
使用示例
- 运行200个随机测试用例:
python manage.py test --random-count 200 - 配合
--shuffle参数使用(注:随机抽取已保证用例随机性,--shuffle在此场景下作用有限,但仍可兼容):python manage.py test --random-count 200 --shuffle
内容的提问来源于stack exchange,提问作者Uri
相关产品推荐
相关产品推荐

