提供一个用于记录unittest运行失败的case的模块,当一个测试方法failure之后,会在项目根目录下创建一个failure_rerun.sh或failure_rerun.bat文件来存储测试失败的case的访问方式。这样就可以以shell脚本或dos批处理的方式直接运行失败的case。该模块中的装饰器同时支持ddt和paramunittest。
请注意:该段代码没有经过windows测试,所以可能出现路径分隔符的问题,请使用windows用户自行调试修改。
# coding=utf-8
import sys
import functools
import importlib
from manage import root_path # 项目根目录,需自行实现
def __deal_fail_command(file_name, s="", flag=True): # flag=true,add failure.flag=false,delete success
unit_command = "python -m unittest"
if sys.platform == "win32":
file_name = root_path + "\\" + file_name + ".bat"
else:
file_name = root_path + "/" + file_name + ".sh"
with open(file_name, "a+"):
pass
global line
line = None
with open(file_name, "r") as f:
line = f.readline()
if flag:
if line.startswith(unit_command):
if line.find(s) == -1:
line += " " + s
else:
line = unit_command + " " + s
else:
if line.startswith(unit_command):
if line.find(s) != -1:
line = "".join(line.split(" " + s))
else:
line = unit_command
with open(file_name, "w") as f:
f.write(line)
def rerun_class(cls, prefix="test"):
for name, func in list(cls.__dict__.items()):
if hasattr(func, "__call__") and name.startswith(prefix):
setattr(cls, name, rerun_method(func))
return cls
def rerun_method(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
s = None
if s is None:
test_instance = args[0]
native_c = type(test_instance)
# 若虚拟类的模块是"paramunittest",则表示该类是由paramunittest构造的
if native_c.__module__ == "paramunittest":
father = native_c.__bases__ # 获取本类的super集
for T in father:
real_m, real_c = T.__module__, T
if native_c.__name__.startswith(real_c.__name__ + "_") != -1:
s = native_c.__name__ + "." + getattr(test_instance, "_testMethodName")
module_path = importlib.import_module(real_m).__file__ # type:str
module_name = module_path.split("/")[-1].split(".")[0]
module_dir = "/".join(module_path.split("/")[0:-1])
if module_dir.startswith(root_path):
path = module_dir[root_path.__len__() + 1:]
s = ".".join(path.split("/")) + "." + module_name + "." + s
break
else:
s = native_c.__name__ + "." + getattr(test_instance, "_testMethodName")
module_path = importlib.import_module(native_c.__module__).__file__ # type:str
module_name = module_path.split("/")[-1].split(".")[0]
module_dir = "/".join(module_path.split("/")[0:-1])
if module_dir.startswith(root_path):
path = module_dir[root_path.__len__() + 1:]
s = ".".join(path.split("/")) + "." + module_name + "." + s
try:
func(*args, **kwargs)
__deal_fail_command("failure_rerun", s, flag=False)
return
except AssertionError:
__deal_fail_command("failure_rerun", s)
raise
return wrapper
能够通过命令行重新执行仍旧不完美,故此提供一个直接py脚本。其中的参数is_test_all可以控制是否执行全部case,当is_test_all==False时,该脚本会自动解析先前所生成的failure_rerun.*文件,并以其中的失败case构建一个testSuite,并重新运行这些case。
# coding=utf-8
import os
import unittest
import common.HTMLTestRunner as HTMLTestRunner # 这是网上的HTMLTestRunner
import unittest.loader as loader
import unittest.suite as suite
root_path = os.path.abspath(os.path.dirname(__file__))
is_test_all = False # 用于控制运行all case和failure case
if __name__ == '__main__':
with open("failure_rerun.sh", "r") as f: # set is_test_all
line = f.readline()
if is_test_all:
pass
elif line == "python -m unittest" or line == "":
is_test_all = True
if is_test_all: # run all case
discover = unittest.defaultTestLoader.discover("testcases", pattern="*.py", top_level_dir=None)
fp = open('reports/my_report.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(
verbosity=2,
stream=fp,
title=u'跑all case',
description='This demonstrates the report output by HTMLTestRunner.'
)
runner.run(discover)
fp.close()
else: # run failure case
line = line["python -m unittest".__len__():]
test_list = line.split(" ")
test_suite = suite.TestSuite()
for test in test_list:
if test.__len__() > 0 and test.__contains__("."):
test_suite.addTest(loader.TestLoader().loadTestsFromName(name=test))
fp = open('reports/my_report.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(
verbosity=2,
stream=fp,
title=u'只跑失败的case',
description='This demonstrates the report output by HTMLTestRunner.'
)
runner.run(test_suite)
fp.close()
一个结构示例

为了便于效果演示,在此提供了一个用于测试的的脚本。
import unittest
from decorator.rerun import rerun_class, rerun_method
@rerun_class
class T(unittest.TestCase):
def setUp(self):
print "setUp"
def tearDown(self):
print "tearDown"
def test_001(self):
print "test_001"
@rerun_method
def test_002(self):
print "test_002"
assert False
def test_003(self):
print "test_003"
assert False
网友评论