Python: 让单元测试输出像GoogleTest一样

news2025/1/9 18:12:09

文章目录

    • 1. 目的
    • 2. 原版 unittest 的输出
    • 3. 仿 GoogleTest 的输出效果
    • 4. 实现原理浅析
      • 传入 testRunner 参数
      • testRunner 参数应该满足的条件
      • 颜色高亮: ASCII 转义字符的使用
      • 测试用例输出文本内容的格式调整:仿googletest
    • 5. 完整实现代码
    • 6. 完整调用代码

在这里插入图片描述

1. 目的

习惯了 C++ 单元测试框架 GoogleTest 的输出: 成功则输出为绿色,失败则输出为红色,不同的case之间有布局上的分隔。使用 Python 虽然提高了开发效率, 但为了保证程序质量, 尤其是考虑到规模化的效果时,写单元测试仍然是不二法宝。对于 Python 自带的 unittest 单元测试模块而言, 在配色上过于朴素(测试用例成功、失败都显示同一种颜色),考虑改进, 而改为 GoogleTest 的样式则相对来说是一个明确的、接受度较高的目标。

2. 原版 unittest 的输出

首先给出两个测试用例,分别是成功的和失败的,然后查看 unittest 的输出效果:

import unittest

def inc(x):
    return x + 1

class JustTest(unittest.TestCase):
    def test_t1(self):
        assert inc(3) == 5
    def test_t2(self):
        assert inc(1) == 2

在这里插入图片描述
可以看到颜色很朴素。

3. 仿 GoogleTest 的输出效果

绿色表示成功,红色表示失败,很醒目了:
在这里插入图片描述
对应的单元测试写法也很简单,只需要改两行代码:

  • 引入定制化 unittest 输出的模块
  • 开启 unittest 时, 传入定制化模块中的的类作为参数
import unittest
import mytest  ## ~ 新增这句

def inc(x):
    return x + 1

class JustTest(unittest.TestCase):
    def test_t1(self):
        assert inc(3) == 5
    def test_t2(self):
        assert inc(1) == 2

if __name__ == '__main__':
    #unittest.main()  ## ~ 这句是老的写法
    unittest.main(testRunner=mytest.MyTestRunner()) ## ~ 新增这句

4. 实现原理浅析

传入 testRunner 参数

unittest 模块的 main 函数接受 testRunner 这一参数, 而 unittest.main() 其实是 unittest.TestProgram 类的实例,传入的 testRunner 参数作为 TestProgram 类的构造函数的参数:
在这里插入图片描述在这里插入图片描述

testRunner 参数应该满足的条件

满足的条件是: 不传入 testRunner 参数时默认的 testRunner 对应的行为, 也就是 runner.TextTestRunner 类。
在这里插入图片描述runner.TextTestRunner 类其实很简单, 核心函数只有一个: run():
在这里插入图片描述
因此只要我们实现的 MyTestRunner 类能够兼容 TextTestRunner 即可。

颜色高亮: ASCII 转义字符的使用

在实现 log 的颜色打印时, ASCII 转义字符被使用到,网络资源很多可以自行查阅,效果大致如下:
在这里插入图片描述这里唯一要注意的是,要同时支持 Windows 和 Linux,MacOSX. Windows 下的颜色高亮实现方式略有不同,因而需要两份实现。

测试用例输出文本内容的格式调整:仿googletest

这一部分负责在每个测试用例运行的最开始,输出“套话”:[ Run ], 运行结果如果成功则输出 ok, 不成功则输出 fail 或 error, 就像 googletest 一样。


    def startTest(self, test):
        self.stream.green('[ Run      ] ')
        self.stream.writeln(self.getDescription(test))
        unittest.TestResult.startTest(self, test)
        if self.showAll:
            self.stream.write(self.getDescription(test))
            self.stream.write(" ... ")

    def addSuccess(self, test):
        unittest.TestResult.addSuccess(self, test)
        if self.showAll:
            self.stream.writeln("ok")
        elif self.dots:
            self.stream.green('[       OK ] ')
            self.stream.writeln(self.getDescription(test))

    def addError(self, test, err):
        unittest.TestResult.addError(self, test, err)
        if self.showAll:
            self.stream.writeln("ERROR")
        elif self.dots:
            self.stream.red('[  ERRORED ] ')
            self.stream.writeln(self.getDescription(test))
            self.stream.write(self._exc_info_to_string(err, test))

    def addFailure(self, test, err):
        unittest.TestResult.addFailure(self, test, err)
        if self.showAll:
            self.stream.writeln("FAIL")
        elif self.dots:
            #self.stream.write(self._exc_info_to_string(err, test))
            content = self._exc_info_to_string(err, test)
            content_lines = content.split('\n')
            linenum_line = content_lines[1]
            file_desc, linenum_desc, testcase_desc = linenum_line.split(', ')
            linenum = linenum_desc.split(' ')[-1]
            filename = file_desc.split(' ')[-1][1:-1]
            result = '{:s}:{:s} Failure'.format(filename, linenum)
            self.stream.writeln(result)
            self.stream.writeln('Expected:')
            self.stream.writeln("\t" + content_lines[2].strip())
            self.stream.writeln('Actual:')
            self.stream.writeln("\t" + content_lines[3].strip())

            self.stream.red('[   FAILED ] ')
            self.stream.writeln(self.getDescription(test))

5. 完整实现代码

##################################################################################################################
# Make Python unittest output like googletest
# Author: ChrisZZ <imzhuo@foxmail.com>
# Homepage: <https://github.com/zchrissirhcz>
# --------------------
#
# Example usage:
# --------------------
# import unittest
# from mytest import MyTestRunner
#
# def inc(x):
#     return x + 1
#
# class JustTest(unittest.TestCase):
#     def test_answer(self):
#         assert inc(3) == 5
#
# if __name__ == '__main__':
#     unittest.main(testRunner=MyTestRunner())
#
# References
# --------------------
# https://www.cnblogs.com/coderzh/archive/2010/08/23/custom-python-unittestoutput-as-gtest.html
# https://github.com/xxhfg/youlook/blob/8d30e008260540902dacf5fbc5d4015bd68dc13e/libs/ColorUnittest/myunittest.py
# https://github.com/sndnyang/Tools/blob/d0bc2a7f5aa645048bd2fbaa28a43b1cccfac323/colortest.py
#
##################################################################################################################

import unittest
import time
import sys


import os

if os.name == 'nt':
    import ctypes

    ## {{{ http://code.activestate.com/recipes/496901/ (r3)
    # See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winprog/winprog/windows_api_reference.asp
    # for information on Windows APIs.
    STD_INPUT_HANDLE = -10
    STD_OUTPUT_HANDLE = -11
    STD_ERROR_HANDLE = -12

    FOREGROUND_WHITE = 0x0007
    FOREGROUND_BLUE = 0x01  # text color contains blue.
    FOREGROUND_GREEN = 0x02  # text color contains green.
    FOREGROUND_RED = 0x04  # text color contains red.
    FOREGROUND_INTENSITY = 0x08  # text color is intensified.
    FOREGROUND_YELLOW = FOREGROUND_RED | FOREGROUND_GREEN

    BACKGROUND_BLUE = 0x10  # background color contains blue.
    BACKGROUND_GREEN = 0x20  # background color contains green.
    BACKGROUND_RED = 0x40  # background color contains red.
    BACKGROUND_INTENSITY = 0x80  # background color is intensified.

    std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)

    def set_color(color, handle=std_out_handle):
        """(color) -> BOOL
        Example: set_color(FOREGROUND_GREEN | FOREGROUND_INTENSITY)
        """
        bool = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, color)
        return bool

    class _ColorWritelnDecorator:
        """Used to decorate file-like objects with a handy 'writeln' method"""
        def __init__(self, stream):
            self.stream = stream
            if os.name == 'nt':
                self.std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)

        def __getattr__(self, name):
            return getattr(self.stream, name)

        def yellow(self, msg):
            set_color(FOREGROUND_YELLOW | FOREGROUND_INTENSITY)
            self.write(msg)
            set_color(FOREGROUND_WHITE)

        def writeln(self, msg=None):
            if msg:
                self.write(msg)
            self.write('\n')

        def red(self, msg):
            set_color(FOREGROUND_RED | FOREGROUND_INTENSITY)
            self.write(msg)
            set_color(FOREGROUND_WHITE)

        def green(self, msg):
            set_color(FOREGROUND_GREEN | FOREGROUND_INTENSITY)
            self.write(msg)
            set_color(FOREGROUND_WHITE)

else:
    # https://en.wikipedia.org/wiki/ANSI_escape_code
    black = (1, 1, 1)
    red = (222, 56, 43)
    green = (57, 181, 74)
    yellow = (255, 199, 6)
    blue = (0, 111, 184)
    magenta = (118, 38, 113)
    cyan = (44, 181, 233)
    white = (204, 204, 204)
    bright_black = (128, 128, 128)
    bright_red = (255, 0, 0)
    bright_green = (0, 255, 0)
    bright_yellow = (255, 255, 0)
    bright_blue = (0, 0, 255)
    bright_magenta = (255, 0, 255)
    bright_cyan = (0, 255, 255)
    bright_white = (255, 255, 255)

    class _ColorWritelnDecorator:
        """Used to decorate file-like objects with a handy 'writeln' method"""
        def __init__(self, stream):
            self.stream = stream
            if os.name == 'nt':
                self.std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)

        def __getattr__(self, name):
            return getattr(self.stream, name)

        def set_color(self, color):
            #stream.write("\033[38;2;{};{};{}m{} \033[38;2;255;255;255m".format(255, 0, 0, msg))
            r, g, b = color
            self.stream.write("\033[38;2;{};{};{}m ".format(r, g, b))

        def yellow(self, msg):
            self.set_color(yellow)
            self.write(msg)
            self.set_color(white)

        def writeln(self, msg=None):
            if msg:
                self.write(msg)
            self.write('\n')

        def red(self, msg):
            self.set_color(red)
            self.write(msg)
            self.set_color(white)

        def green(self, msg):
            self.set_color(green)
            self.write(msg)
            self.set_color(white)

import re

pattern = re.compile('File "(.+)",', re.IGNORECASE)

class MyTestResult(unittest.TestResult):
    separator1 = '[----------] '
    separator2 = '[==========] '

    def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
        unittest.TestResult.__init__(self)
        self.stream = stream
        self.showAll = verbosity > 1
        self.dots = verbosity == 1
        self.descriptions = descriptions

    def getDescription(self, test):
        if self.descriptions:
            return test.shortDescription() or str(test)
        else:
            return str(test)

    def startTest(self, test):
        self.stream.green('[ Run      ] ')
        self.stream.writeln(self.getDescription(test))
        unittest.TestResult.startTest(self, test)
        if self.showAll:
            self.stream.write(self.getDescription(test))
            self.stream.write(" ... ")

    def addSuccess(self, test):
        unittest.TestResult.addSuccess(self, test)
        if self.showAll:
            self.stream.writeln("ok")
        elif self.dots:
            self.stream.green('[       OK ] ')
            self.stream.writeln(self.getDescription(test))

    def addError(self, test, err):
        unittest.TestResult.addError(self, test, err)
        if self.showAll:
            self.stream.writeln("ERROR")
        elif self.dots:
            self.stream.red('[  ERRORED ] ')
            self.stream.writeln(self.getDescription(test))
            self.stream.write(self._exc_info_to_string(err, test))

    def addFailure(self, test, err):
        unittest.TestResult.addFailure(self, test, err)
        if self.showAll:
            self.stream.writeln("FAIL")
        elif self.dots:
            #self.stream.write(self._exc_info_to_string(err, test))
            content = self._exc_info_to_string(err, test)
            content_lines = content.split('\n')
            linenum_line = content_lines[1]
            file_desc, linenum_desc, testcase_desc = linenum_line.split(', ')
            linenum = linenum_desc.split(' ')[-1]
            filename = file_desc.split(' ')[-1][1:-1]
            result = '{:s}:{:s} Failure'.format(filename, linenum)
            self.stream.writeln(result)
            self.stream.writeln('Expected:')
            self.stream.writeln("\t" + content_lines[2].strip())
            self.stream.writeln('Actual:')
            self.stream.writeln("\t" + content_lines[3].strip())

            self.stream.red('[   FAILED ] ')
            self.stream.writeln(self.getDescription(test))


class MyTestRunner:
    def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
        self.stream = _ColorWritelnDecorator(stream)
        self.descriptions = descriptions
        self.verbosity = verbosity

    def run(self, test):
        result = MyTestResult(self.stream, self.descriptions, self.verbosity)
        self.stream.green(result.separator2)
        self.stream.writeln('Your Unit Tests Start')

        startTime = time.time()
        test(result)

        stopTime = time.time()
        timeTaken = stopTime - startTime
        self.stream.green(result.separator2)
        run = result.testsRun
        self.stream.writeln("Run %d test%s in %.3fs" %
                            (run, run != 1 and "s" or "", timeTaken))

        failed, errored = map(len, (result.failures, result.errors))

        self.stream.green("[  PASSED  ]  %d tests" % (run - failed - errored))
        self.stream.writeln()

        if not result.wasSuccessful():
            errorsummary = ""
            if failed:
                self.stream.red("[  FAILED  ] %d tests, listed below:" % failed)
                self.stream.writeln()
                for failedtest, failederorr in result.failures:
                    match = pattern.findall(failederorr)
                    if match:
                        src_file = match[0]
                    self.stream.red("[  FAILED  ] %s in %s" % (failedtest, src_file))
                    self.stream.writeln()
            if errored:
                self.stream.red("[  ERRORED ] %d tests, listed below:" % errored)
                self.stream.writeln()
                for erroredtest, erorrmsg in result.errors:
                    match = pattern.findall(erorrmsg)
                    if match:
                        src_file = match[0]
                    self.stream.red("[  ERRORED ] %s in %s" % (erroredtest, src_file))
                    self.stream.writeln()

            self.stream.writeln()
            if failed:
                self.stream.writeln("%2d FAILED TEST" % failed)
            if errored:
                self.stream.writeln("%2d ERRORED TEST" % errored)

        return result

6. 完整调用代码

import unittest
import mytest

def inc(x):
    return x + 1

class JustTest(unittest.TestCase):
    def test_t1(self):
        assert inc(3) == 5
    def test_t2(self):
        assert inc(1) == 2

if __name__ == '__main__':
    #unittest.main()
    unittest.main(testRunner=mytest.MyTestRunner())

Enjoy it~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/541897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

$‘\r‘: command not found syntax error near unexpected token `$‘do\r‘‘ 解决方案

问题描述 今天在执行代码时出现了这样的错误&#xff1a; bash xxx.sh xxx.sh: line 2: $\r: command not found xxx.sh: line 7: $\r: command not found xxx.sh: line 8: syntax error near unexpected token $do\r 经查阅&#xff0c;发现是.sh文件在windows下编辑&#xf…

Nevron Open Vision for .NET Crack

Nevron Open Vision for .NET Crack 增加了对Microsoft.NET 7.0的支持-NOV现在完全支持.NET Core 7.0&#xff0c;此外还支持Microsoft.NET Framework 4.7.2、.NET Core 5.0和.NET Core 6.0的内部版本。 用于.NET改进的NOV图表 添加了WPF和WinForms版本中提供的新3D渲染引擎。新…

展会回顾 | 2023元宇宙生态博览会圆满落幕,3DCAT荣获“元宇宙交互技术奖”

2023年5月10日-5月12日&#xff0c;一场涵盖了元宇宙终端头显、数字文娱、数字艺术、数字运动、数字多媒体展陈设计、数字展厅展馆、科技文旅、夜游演艺、沉浸式KTV/酒吧等多个领域的元宇宙商业盛会——2023第2届世界元宇宙生态博览会在广州广交会展馆A区3.2馆、4.2馆掀开帷幕。…

Python求balance_list【三】

本文为博主原创&#xff0c;未经授权&#xff0c;严禁转载及使用。 本文链接&#xff1a;https://blog.csdn.net/zyooooxie/article/details/130159648 很早之前&#xff0c;我写了2篇 如何求balance_list 的博客&#xff1a; https://blog.csdn.net/zyooooxie/article/detail…

DeepFM - 工业界经典baseline(哈工大 华为)

文章目录 1、模型结构如下:2、关键理解点:3、代码实现细节:DeepFM: A Factorization-Machine based Neural Network for CTR Prediction。dfm由哈工大和华为合作发表在IJCAI-2017;模型结构很简单,wide&deep结构。1、模型结构如下: 相比wide&deep [下文简称wd] 有…

就业内推 | 应届生校招、实习,上市公司有岗,最高18k*15薪

01 UCloud &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;职责描述&#xff1a; 1、负责UCloud全球骨干网或数据中心网络工作&#xff0c;包括设备技术选型、架构运营方案设计、日常运维支持 2、持续提升网络稳定性与性能。 &#x1f537;任职要求&#xff1a; …

Flowable-modeler可视化教程

Flowable-Modeler功能 提供可视化编辑器&#xff0c;编辑BPMN流程&#xff0c;编辑CASE模型&#xff0c;编辑Form表单&#xff0c;编辑App应用&#xff0c;编辑决策表提供可视化参数配置&#xff1a;每个流程可以配置详细的参数设置&#xff0c;按照流程对应的规范来设计。提供…

学Python的都在说爬虫容易进去,你还敢做爬虫吗?十分钟带你规避可拷风险

阅读文本大概需要 10 分钟&#xff0c;今天&#xff0c;不要面向监狱编程了。 序言 前段时间有一篇名为《只因写了一段爬虫&#xff0c;公司200多人被抓&#xff01;》的文章非常火&#xff0c;相信大家应该都看到了。 这篇文章火起来之后&#xff0c;本来经过了一个多月的时…

【Spring框架】--01.Spring概述、入门

文章目录 Spring1.概述1.1Spring是什么&#xff1f;1.2 Spring 的狭义和广义1.3 Spring Framework特点1.4 Spring模块组成1.5 Spring6特点1.5.1版本要求 2.入门2.1 构建模块2.2 程序开发2.2.1 引入依赖2.3.2 创建java类2.3.3 创建配置文件2.3.4 创建测试类测试2.3.5 运行测试程…

centos上搭建redis伪集群

1.安装ruby 搭建redis集群需要ruby脚本&#xff0c;需要安装ruby的环境 (1)yum install ruby (2) yum install rubygems 2. 复制安装完成的redis /usr/local目录下创建redis-cluster目录&#xff0c;复制已经安装完成的redis到 /usr/local/redis-cluster/redis01目录&#x…

京东软件测试岗位经典面试题(附答案)

1、黑盒测试的测试用例常见设计方法都有哪些&#xff1f;请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。 1&#xff09;等价类划分&#xff1a;等价类是指某个输入域的子集合.在该子集合中&#xff0c;各个输入数据对于揭露程序中的错误都是等效的.并合理地假…

【从零开始写视觉SLAM】v0.1基于特征点的简单VO

v0.1版本的oSLAM实现了基于orb特征点的简单视觉里程计&#xff0c;通过连续两帧的rgbd数据实现相机相对位姿的估计。 #mermaid-svg-ibQfHFVHezQD5RWW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ibQfHFVHezQD5RW…

MySQL数据库---笔记1

MySQL数据库---笔记1 一、数据库概述1.1、什么是数据库1.2、数据库的安装与启动1.3、MySQL数据模型 二、SQL2.1、通用语法及分类2.2、DDL2.2.1、数据库操作 一、数据库概述 1.1、什么是数据库 名称全称简称数据库存储数据的仓库&#xff0c;数据是有组织的进行存储DataBase (…

LeetCode每日一题之二分搜索

文章目录 1.关于二分搜索常见的误区2.左闭右闭区间的写法3.左闭右开区间的写法4.找到第一个大于target的数5.找到第一个小于target的数6.找到第一个大于等于taregt的数7.找到第一个小于等于target的数 1.关于二分搜索常见的误区 区间的定义&#xff1a; 2.左闭右闭区间的写法…

(MIT6.045)自动机、可计算性和复杂性-正则表达式

语言(language)的计算性质&#xff1a;交、并、补、反转、拼接、星号&#xff08;*&#xff09; 星号是一元运算符&#xff0c;表示一个语言和自己的有穷次笛卡尔积。 回顾&#xff1a;正则语言&#xff08;Regular Language&#xff09;指可以用DFA描述的语言。 正则表达式…

Linux内核panic简析

源码基于&#xff1a;Linux 5.4 0. 前言 内核异常的级别大致分为三个&#xff1a;BUG、oops、panic。 BUG 是指那些不符合内核的正常设计&#xff0c;但内核能够检测出来并且对系统运行不会产生影响的问题&#xff0c;比如在原子上下文中休眠&#xff0c;在内核中用 BUG 标识。…

Linux——线程3|线程互斥和同步

加锁保护 我们上一篇提到过,多个线程执行下面代码可能会出错,具体原因可查看上一篇Linux博客。

Vue3相关知识点笔记(持续更新中。。。。)

目录标题 1、Vue3中的组合式Api有哪些? 和Vue2的Options Api有什么不同?Vue3中的组合式API主要包括以下几个&#xff1a;与Vue2的option Api有什么不同 2、Vue3.0的设计目标是什么&#xff1f;做了哪些优化&#xff1f;设计目标是什么&#xff1f;哪些优化&#xff1f; 3、Vu…

python:随机森林分类器的性能评估(决策树数量的影响)

作者:CSDN @ _养乐多_ 随机森林(Random Forest)是一种强大的机器学习算法,常用于分类和回归任务。它由多个决策树构成,通过集成学习的方式进行预测。在本篇博客中,我们将探讨随机森林分类器在不同决策树数量下的性能,并绘制相应的图表进行可视化分析。OOB误差,0被误判为…

C++ 实现堆排序

时空复杂度 时间复杂度 排序复杂度 O ( n l o g n ) O(nlogn) O(nlogn) 建堆复杂度 O ( n ) O(n) O(n) 空间复杂度 由于堆排序是一种就地设计的排序算法&#xff0c;空间需求是恒定的&#xff0c;所以是O(1) 稳定性 不稳定。 C代码&#xff08;大根堆&#xff09; cla…