最近在琢磨unittest的执行器,简单写个demo,实现多线程实现并发执行测试用例TestRunner,记录一下
import unittest
# unnittest 自带的执行器
suite = unittest.defaultTestLoader.discover(r"/****/testcases")
runner = unittest.TextTestRunner()
runner.run(suite)
# 不使用执行器执行用例
import unittest
from testcases.test_demo import TestDemo
result = unittest.TestResult()
case1 = TestDemo('test_login')
# 定义一个测试用例对象
res = case1.run(result)
case2 = TestDemo('test_register')
# 定义一个测试用例对象
case1.run(result)
case2.run(result)
print(res)
import unittest
# 内置执行器执行的原理
def test_runner(suite):
result = unittest.TestResult()
for item in suite:
print(f"用例模块:{item}")
for test_cls in item:
print(f"测试类:{test_cls}")
for case_ in test_cls:
print(f"测试用例对象:{case_}")
case_.run(result)
return result
res = test_runner(suite)
print(res)
完善基于线程池的多线程的执行器
import unittest
from concurrent.futures import ThreadPoolExecutor
class TestRunner:
def __init__(self, thread_count):
self.thread_count = thread_count
self.result = unittest.TestResult()
def run(self, suite):
"""执行器执行用例的方法"""
with ThreadPoolExecutor(max_workers=self.thread_count) as pool:
for item in suite:
print(f"用例模块:{item}")
for test_cls in item:
print(f"测试类:{test_cls}")
# 遍历测试用例
# self.cls_run(test_cls)
pool.submit(self.__cls_run, test_cls) # 以类为线程可以保障用例的执行顺序
return self.result
def __cls_run(self, test_cls):
"""执行测试类中所有的测试用例"""
for case_ in test_cls:
print(f"测试用例对象:{case_}")
case_.run(self.result)
if __name__ == '__main__':
suite = unittest.defaultTestLoader.discover(r"/Users/***/Yio/WrokHomePy/PWDemo/testcases/")
runner = TestRunner(2)
res = runner.run(suite)
print(res)
模块级别的执行器
import unittest
from concurrent.futures import ThreadPoolExecutor
class TestRunner:
def __init__(self, thread_count):
self.thread_count = thread_count
self.result = unittest.TestResult()
def run(self, suite):
"""以模块为执行的执行器,这种可以保证模块内的测试用例的执行顺序"""
with ThreadPoolExecutor(max_workers=self.thread_count) as pool:
for item in suite:
print(f"用例模块:{item}")
pool.submit(item.run, self.result)
return self.result
if __name__ == '__main__':
suite = unittest.defaultTestLoader.discover(r"/Users/***/Yio/WrokHomePy/PWDemo/testcases/")
print(f"收集到的测试用例数:{suite.countTestCases()}")
runner = TestRunner(2)
res = runner.run(suite)
print(res)
用例收集套件的区别
import unittest
# 通过路径加载测试套件
suite = unittest.defaultTestLoader.discover(r"/Users/***/Yio/WrokHomePy/PWDemo/testcases")
# 通过测试模块去加载测试用例
from testcases import test_demo
# 模块级别测试套件:里面类基本的测试套件(测试类基本的套件中包含用例)
suite2 = unittest.defaultTestLoader.loadTestsFromModule(test_demo)
# 通过测试类去加载测试用例
from testcases.test_demo import TestDemo
# 测试类级别测试套件:里面包含的就是测试用例
suite3 = unittest.defaultTestLoader.loadTestsFromTestCase(TestDemo)
print()
重写TestResult,结合自定义的runner
import platform
import time
import unittest
from concurrent.futures import ThreadPoolExecutor
class TestResult(unittest.TestResult):
"""自定义TestResult"""
def __init__(self, case_sum):
super().__init__()
# 收集到的用例总数
self.case_sum = case_sum
# 执行通过的用例
self.success_cases = []
# 用例执行开始时间
self.test_start_time = None
# 用例执行结束时间
self.test_stop_time = None
def startTestRun(self) -> None:
"""所有测试执行前"""
print("当前系统版本:", platform.platform())
print("python版本:", platform.python_version())
print("套件中的用例总数:", self.case_sum)
self.test_start_time = time.time()
def stopTestRun(self) -> None:
"""所有测试用例执行完之后,执行的方法"""
self.test_stop_time = time.time()
print(f"执行用例数:{self.testsRun}")
print(f"失败用例数:{len(self.failures)}")
print(f"通过用例数:{len(self.success_cases)}")
print(f"异常用例数:{len(self.errors)}")
print(f"跳过用例数:{len(self.skipped)}")
print(f"用例执行开始时间:{self.test_start_time},结束时间:{self.test_stop_time}, 总时间:",
self.test_stop_time - self.test_start_time)
def addSuccess(self, test):
# 用例执行通过调用
self.success_cases.append(test)
print(f"用例{test}----> 执行通过")
def addFailure(self, test, err):
super().addFailure(test, err)
print(f"用例{test}----> 执行失败")
def addError(self, test, err):
super().addError(test, err)
print(f"用例{test}----> 执行报错:{err}")
def addSkip(self, test: unittest.case.TestCase, reason: str):
super().addSkip(test, reason)
print(f"用例{test}----> 跳过:{reason}")
class TestRunner:
def __init__(self, path, task, thread_count):
self.suite = unittest.defaultTestLoader.discover(path)
self.thread_count = thread_count
self.result = TestResult(self.suite.countTestCases())
self.task = task
def __parse_suite_to_task(self):
"""解析测试套件,加入到执行的任务列表"""
tasks = []
if self.task == 'cls':
"""测试类级别的用例执行"""
for item in self.suite:
print(f"用例模块:{item}")
for test_cls in item:
print(f"测试类:{test_cls}")
tasks.append(test_cls)
elif self.task == 'model':
"""测试模块级别的用例执行"""
for model in self.suite:
print(f"用例模块:{model}")
tasks.append(model)
else:
for model in self.suite:
print(f"用例模块:{model}")
for cls in model:
print(f"用例模块:{cls}")
for case_ in cls:
print(f"测试类:{case_}")
tasks.append(case_)
return tasks
def run(self):
# 执行测试前,记录基本信息
self.result.startTestRun()
# 获取所有需要执行的任务
tasks = self.__parse_suite_to_task()
with ThreadPoolExecutor(max_workers=self.thread_count) as pool:
"""执行器线程池执行测试"""
for work in tasks:
pool.submit(work, self.result)
# 所有测试结束后进行执行数据统计
self.result.stopTestRun()
# 然后这块可以生成一个html报告
return self.result
def __cls_run(self, test_cls):
"""执行测试类中所有的测试用例"""
for case_ in test_cls:
print(f"测试用例对象:{case_}")
case_.run(self.result)
if __name__ == '__main__':
runner = TestRunner(r"C:\PyWorkHome\demo\testcase", 'case', thread_count=2)
result = runner.run()
print(result)