笔记|Python 工具函数|文件夹内所有 doctest 批量执行工具类

doctest 使用方法:笔记|Python 的 doctest 使用方法

"""
Doctest 批量执行器工具类
"""

import doctest
import importlib
import os
from typing import Generator, List, NoReturn

import coverage


class DoctestRunner:
    """Doctest 批量执行器

      在构造 DoctestRunner 实例时,需提供项目根目录 source_root;后续逐个使用 add_module 方法添加需要执行
    doctest 的模块(自动扫描模块下包含的子模块并添加);添加完成后,执行 run 方法,即批量执行所有添加的模块的 doctest
    并整理结果。
      如果需要忽略某个模块,则可以使用 add_ignore 方法忽略。
    """

    def __init__(self, source_root: str):
        """构造方法

        Parameters
        ----------
        source_root : str
            项目根目录 (Source Root),后续添加的 module 均需基于项目根目录
        """
        # 解析项目路径 (忽略末尾的 "/")
        self._source: str = source_root.rstrip("/")

        # 所有需要检查的模块
        self._modules = set()

        # 需要忽略的模块
        self._ignore_modules = set()

        # 汇总统计结果文件
        self._n_failures: int = 0  # 失败用例总数
        self._n_tries: int = 0  # 测试用例总数
        self._fail_module: List[str] = []  # 失败模块列表
        self._error_module: List[str] = []  # 报错模块列表

    def add_module(self, module: str, recursion: bool = True) -> NoReturn:
        """添加需要执行 doctest 的 module 模块

        Parameters
        ----------
        module : str
            需要添加的 module 模块;如果模块下包含子模块,则将自动扫描所有子模块并添加(如不需要添加子模块,则需将参数
            recursion 置为 False)
            例如:package1.package2.file_name
        recursion : bool, default = True
            是否递归查找 module 模块下的子模块
        """
        self._modules.add(module)  # 添加当前 module 模块
        if recursion is True:
            for sub_module in self._iter_sub_module(module):
                self._modules.add(sub_module)  # 添加当前 module 下的子模块

    def add_ignore(self, module, recursion: bool = True) -> NoReturn:
        """忽略 module 模块不执行 doctest

        Parameters
        ----------
        module : str
            需要忽略的 module 模块;如果模块下包含子模块,则将自动扫描所有子模块并忽略(如不需要忽略子模块,则需将参数
            recursion 置为 False)
            例如:package1.package2.file_name
        recursion : bool, default = True
            是否递归查找 module 模块下的子模块
        """
        self._ignore_modules.add(module)  # 忽略当前 module 模块
        if recursion is True:
            for sub_module in self._iter_sub_module(module):
                self._ignore_modules.add(sub_module)  # 忽略当前 module 下的子模块

    def _iter_sub_module(self, module: str) -> Generator[str, None, None]:
        """获取 module 模块下的所有子模块的迭代器"""
        path = os.path.join(self._source, *module.split("."))  # 生成 module 所在路径
        if os.path.isdir(path):
            for root, dirs, files in os.walk(path):
                for file in files:
                    if file.endswith(".py"):
                        sub_module = root.replace(self._source + "/", "").split("/")
                        sub_module.append(file.replace(".py", ""))
                        yield ".".join(sub_module)

    def run(self) -> NoReturn:
        """批量执行所有添加的模块的 doctest 并整理结果"""
        for module in sorted(self._modules - self._ignore_modules):  # 排序以保证相邻路径结果相邻
            # noinspection PyBroadException
            try:
                package = importlib.import_module(module)
                failures, tries = doctest.testmod(package, report=False)  # 执行 doctest
                self._n_failures += failures  # 累计报错次数
                self._n_tries += tries  # 累加尝试次数
                if failures > 0:
                    self._fail_module.append(module)
            except Exception:
                self._error_module.append(module)

    @property
    def n_failures(self) -> int:
        """返回失败用例总数"""
        return self._n_failures

    @property
    def n_success(self) -> int:
        """返回成功用例总数"""
        return self._n_tries - self._n_failures

    @property
    def n_tries(self) -> int:
        """返回测试用例总数"""
        return self._n_tries

    @property
    def fail_module(self) -> List[str]:
        """返回失败模块列表"""
        return self._fail_module

    @property
    def n_fail_module(self) -> int:
        """返回失败模块总数"""
        return len(self._fail_module)

    @property
    def error_module(self) -> List[str]:
        """返回报错模块列表"""
        return self._error_module

    @property
    def n_error_module(self) -> int:
        """返回报错模块总数"""
        return len(self._error_module)


if __name__ == "__main__":
    cov = coverage.coverage()
    cov.start()

    dr = DoctestRunner("/home/txjiang/power4/src")
    dr.add_module("data_govern")
    print(dr.run())

    cov.stop()
    print(cov.html_report(directory="/home/txjiang/doctest"))

猜你喜欢

转载自blog.csdn.net/Changxing_J/article/details/130329603
今日推荐