提升测试可视化:在 Allure 报告中实现日志输出

487 阅读7分钟

前言

Allure相信大家都比较熟悉了,一种流行的测试结果可视化工具,已经成为许多测试人员的首选, 它通过丰富的图表和清晰的报告格式,帮助快速理解测试结果。然而,日志作为寻找问题重要的一部分,记录 了程序运行的详细信息,如果将日志集成到测试报告中,可以更快速全面的理解测试结果。这篇文章就探讨 如何将日志输出集成到Allure报告中。

未集成日志

我们先看看默认的allure报告,这里做一个简单演示,还不熟悉pytest中如何使用allure的 同学,可以翻看笔者的文章优雅展示:使用Allure和Pytest创建漂亮测试报告. 看一个简单case:

class TestDictController:

    @allure.title("获取曲谱类型")
    def test_dict(self):
        response = requests.get("http://127.0.0.1:8080/pangxiaolu/dict/all?type=score_type")
        assert response.status_code == 200

然后执行如下命令:

pytest --alluredir=./allure_report
allure serve allure_report

这样就可以看到详细报告,如下图所示

pic_1.png

可以看到用例执行成功了,但是没有任何信息,从日志中无法知道请求的具体信息,接下来我们就进行改造。

利用requests的hook机制

可能有些同学还不知道requests的hook机制,这里简要说一下。

  • requests 提供了钩子机制,允许你在请求的不同阶段插入自定义行为。钩子函数在请求的处理流程中被调用,如请求发送后、响应接收后等。
  • hooks参数: 在 requests 的函数调用中,你可以通过 hooks 参数传递钩子函数。钩子参数是字典,键是钩子的名称,值是钩子函数。
  • 钩子函数定义: 钩子函数接收响应对象和其他参数。可以在钩子函数中访问请求和响应信息,并将其记录到 Allure 报告或其他日志系统中。

请求/响应信息集成到allure

知道了request的hook机制就容易解决了,我们实现钩子函数如下:

class TestDictController:

    @allure.title("获取曲谱类型")
    def test_dict(self):
        def log_request(response, *args, **kwargs):
            # 记录请求信息
            allure.attach(
                body=f"Request URL: {response.request.url}\nRequest Headers: {response.request.headers}\nRequest Body: {response.request.body}",
                name="Request Information",
                attachment_type=allure.attachment_type.JSON
            )
            # 记录响应信息
            allure.attach(
                body=f"Response Status Code: {response.status_code}\nResponse Headers: {response.headers}\nResponse Body: {response.text}",
                name="Response Information",
                attachment_type=allure.attachment_type.JSON
            )

        hooks = {'response': log_request}
        response = requests.get("http://test.api.pangxiaolu.cn/pangxiaolu/dict/all?type=score_type", hooks=hooks)
        assert response.status_code == 200

log_request就是记录请求/响应信息的钩子函数,这里用到了allure.attach方法,这个方法就是 将一些额外的信息添加到测试报告中。
好了,现在我们执行用例,效果如下图所示:

pic_2.png

可以看到,已经多了请求以及响应信息了,比之前要清晰多了。

继续改造

虽然已经有详细信息了,但是在两个日志文件,而且没有格式,不容易看,接下来再进行小小改动,会 变得更加直观.
钩子函数改动如下,

def formatting(payload):
    if isinstance(payload, dict):
        return json.dumps(payload, indent=2, separators=(',', ':'), ensure_ascii=False)
    return payload


def log_request(response, *args, **kwargs):
    allure_info = {
        "url": response.request.url,
        "request_body": response.request.body,
        "response_body": response.text,
    }
    allure.attach(formatting(allure_info),
                  name="Request Information",
                  attachment_type=allure.attachment_type.JSON)

我们合并了信息,并且使用formatting函数进行了格式处理,再次执行效果如下:

pic_3.png

哈哈,这样是不是更直观了。

日志集成

前面只是将请求/响应信息写到了allure报告中,我们还想将其余日志也集成到allure报告中,比如控制台的日志。
先看案例:

class TestDictController:

    @allure.title("获取曲谱类型")
    def test_dict(self):
        logger.info("开始执行获取曲谱类型用例!!!")
        ......

这里logger,使用的是笔者之前文章提到的日志类测试框架-自定义日志系统 ,只添加了控制台和 文件处理器,这样执行后,这行日志会输出到控制台和文件,但是allure报告中是没有的。

日志类增加Allure处理器

import logging
class AllureHandler(logging.Handler):
    def emit(self, record):
        logging.getLogger(record.name).handle(record)

代码做一个简单解释:

  1. 继承自logging.Handler:AllureHandler 继承了 logging.Handler,它是一个自定义的日志处理器,可以用于处理日志记录。

  2. emit方法:emit是logging.Handler 中的一个方法,用于定义如何处理日志记录。每当日志记录需要被处理时,emit方法会被调用。

  3. record 参数:record 是一个 LogRecord 对象,包含了日志记录的所有信息,如日志级别、消息、时间戳等。

  4. logging.getLogger(record.name).handle(record):

    • record.name 是日志记录的名称(即 Logger 对象的名称)。
    • logging.getLogger(record.name) 根据记录的名称获取相应的 Logger 对象。
    • .handle(record) 将日志记录传递给 Logger 对象的处理器(即 Logger 对象的所有附加的 Handler)。

然后,配置AllureHandler处理器即可:

class PytestXLogger:

    def _initialize(self, level: str = Log.DEFAULT_LOG_LEVEL, log_path: str = LOG_PATH):

        self._allure_format = "[{module}.{function}:{line}]-[{level}]:{message}"
        self.configure_logger(level.upper(), log_path)

    def configure_logger(self, level: str, log_file_path: str):
        # Remove existing handlers only once
        self.logger.remove()

        # Add allure handler
        # self.logger.add(AllureHandler(), level=level, format=self._allure_format)

现在,我们再次执行用例,查看allure报告:

pic_4.png

可以看到日志已经集成到allure报告中了。

最后

可能需要结合测试框架-自定义日志系统这篇文章一起看, 这样会容易理解,因为部分重复代码在这篇文章中没展示,核心是allure处理器的添加。