Pytest:使用Allure进行日志记录

huangapple go评论54阅读模式
英文:

Pytest: logging using allure

问题

为了向我的 allure 报告添加步骤,我正在使用以下函数:

import allure
from datetime import datetime

class Report:
    @staticmethod
    def report_step(step_title):
        with allure.step(f'[{datetime.now()}]: {step_title}'):
            pass

而我想要找到一种方法来添加 logging 到我的代码中。因此,我考虑将 logging 添加到这个函数中:

import logging

def report_step(step_title):
    m = f'[{datetime.now()}]: {step_title}'
    with allure.step(m):
        logging.debug(m)

但是我对这种方法有两个问题:

  1. 我无法根据此方法确定日志的级别(尽管我可以为此 report_step 方法添加另一个参数)。

  2. 在我的日志中,我使用以下格式:

    [%(asctime)s] [ %(levelname)s ] [%(filename)s] [%(funcName)s] [%(lineno)d]: %(message)s

    在这种情况下,使用 report_step 方法,所有日志行都将从相同的类名(例如我的示例中的 Report)中写入。如果需要进行调查,我需要首先找到日志行来自何处。有什么建议吗?

英文:

So In order to add steps to my allure report I am using this function:

import allure
from datetime import datetime


class Report:
    @staticmethod
    def report_step(step_title):
        with allure.step(f'[{datetime.now()}]: {step_title}'):
            pass

And I want to find a way to add logging to my code as well.

So I was thinking of add logging into this function:

def report_step(step_title):
    m = f'[{datetime.now()}]: {step_title}'
    with allure.step(m):
        logging.debug(m)

But I have 2 problems with this approach:

  1. I cannot determine log level according this method (although I can add another parameter to this report_step method)

  2. In my log I am using this format:

    '[%(asctime)s] [ %(levelname)s ] [%(filename)s] [%(funcName)s] [%(lineno)d]: %(message)s'

And In my case here with this report_step method all log lines will write from the same class name (Report in my example) And in case I will need to investigate I will need to find first from where the log lines come from.

Any suggestions ?

答案1

得分: 1

由于pytest插件非常灵活您可能不需要创建另一个上下文管理器来包装`allure.step`,只需定义一些将与之一起执行的钩子

因此基本上在我的解决方案中我创建了一个包含`allure-pytest`插件的钩子实现的本地插件本地插件钩子和固定装置可以存储在`conftest.py`或者在任何其他模块中但在`conftest.py`中使用`pytest_plugins`变量进行声明)。

首先我们需要添加一个命令行选项该选项将用于设置我们的记录器的日志级别可以使用[pytest_addoption][1]钩子完成此操作

然后我们需要将我们的插件定义为一个类该类需要pytest `config`变量并将从中定义相应的日志级别

此类还必须包含带有`@allure_commons.hookimpl`装饰器的`start_step`函数该装饰器指定它是钩子实现

最后我们需要使用[pytest_configure][2]钩子将我们的插件注册到`allure_commons.plugin_manager`

我将所有的代码都放在了`conftest.py`包括本地插件类仅供示例参考

**conftest.py**
```python
import inspect
import logging

import allure_commons
from _pytest.config import UsageError


class AllureStepLogger:
    def __init__(self, config):
        # 创建一个记录器
        self.logger = logging.getLogger(self.__class__.__name__)

        # 获取--allure-step-log-level的值
        self.level = config.option.allure_step_log_level
        if isinstance(self.level, str):
            self.level = self.level.upper()
        # 通过级别名称获取级别数
        try:
            self.level = int(getattr(logging, self.level, self.level))
        except ValueError as e:
            # Python日志不将其识别为日志级别
            raise UsageError(
                f"'{self.level}'不被识别为'{self.__class__.__name__}'的日志级别名称。请考虑传递日志级别数值。"
            ) from e

    @allure_commons.hookimpl
    def start_step(self, uuid, title, params):
        """添加一个钩子实现,以记录每个步骤"""
        # 从堆栈中获取test_*函数名
        test_name = next((frame[3] for frame in inspect.stack() if frame[3].startswith("test_")), "未知测试")
        # 使用定义的记录器和日志级别记录消息
        self.logger.log(self.level, f"{test_name}: {title}")


def pytest_configure(config):
    """如果注册了'allure_pytest'插件,则注册'allure_step_logger'插件。"""
    if config.pluginmanager.getplugin('allure_pytest'):
        allure_commons.plugin_manager.register(AllureStepLogger(config), "allure_step_logger")


def pytest_addoption(parser):
    """添加一个命令行选项--allure-step-log-level。"""
    parser.getgroup("logging").addoption(
        "--allure-step-log-level",
        dest="allure_step_log_level",
        default="debug",
        metavar="ALLURE_STEP_LEVEL",
        help="allure.step日志消息的级别,默认为'DEBUG'。"
    )

test_module.py

import allure


def test_function_1():
    with allure.step("步骤 1"):
        pass
        with allure.step("子步骤 1.1"):
            pass
        with allure.step("子步骤 1.2"):
            pass
    with allure.step("步骤 2"):
        pass


def test_function_2():
    with allure.step("步骤 1"):
        pass
    with allure.step("步骤 2"):
        pass
    with allure.step("步骤 3"):
        pass

运行pytest --alluredir=results --log-cli-level=debug命令的输出将包含我们的日志,因为--allure-step-log-level默认为debug

============================= 测试会话开始 =============================
collecting ... collected 2 items

test_module.py::test_function_1 
test_module.py::test_function_2 

============================== 2 passed in 0.08s ==============================

进程结束,退出码:0

-------------------------------- 实时日志调用 --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 步骤 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 子步骤 1.1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 子步骤 1.2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 步骤 2
通过                                                                    [ 50%]
-------------------------------- 实时日志调用 --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: 步骤 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: 步骤 2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: 步骤 3
通过                                                                    [100%]

要将记录器级别更改为INFO,只需使用--allure-step-log-level=info


<details>
<summary>英文:</summary>
As pytest plugins are very flexible you may not create another context manager to wrap `allure.step` but just define some hooks that will be executed along with it.
So basically, in my solution I create a local plugin that contains `allure-pytest` plugin&#39;s hook implementation. Local plugins (hooks and fixtures) can be stored in `conftest.py` (or any other module but with declaration in `conftest.py` using `pytest_plugins` variable).
First of all, we need to add a command line option that will be used to set a log level for our logger. It can be done with [pytest_addoption][1] hook.
Then, we need to define our plugin as a class that requires the pytest `config` variable and will define a corresponding log level from it.
Also this class must contain `start_step` function with `@allure_commons.hookimpl` decorator which specifies that it is hook implementation.
And finally, we need to register our plugin with `allure_commons.plugin_manager` using [pytest_configure][2] hook.
I put all code to `conftest.py` including local plugin class just for example.
**conftest.py**
```python
import inspect
import logging
import allure_commons
from _pytest.config import UsageError
class AllureStepLogger:
def __init__(self, config):
# Create a logger
self.logger = logging.getLogger(self.__class__.__name__)
# Get --allure-step-log-level value
self.level = config.option.allure_step_log_level
if isinstance(self.level, str):
self.level = self.level.upper()
# Get a level number by a level name
try:
self.level = int(getattr(logging, self.level, self.level))
except ValueError as e:
# Python logging does not recognise this as a logging level
raise UsageError(
&quot;&#39;{}&#39; is not recognized as a logging level name for &quot;
&quot;&#39;{}&#39;. Please consider passing the &quot;
&quot;logging level num instead.&quot;.format(self.level, self.__class__.__name__)
) from e
@allure_commons.hookimpl
def start_step(self, uuid, title, params):
&quot;&quot;&quot;Add a hook implementation to log every step&quot;&quot;&quot;
# get test_* function name from stack
test_name = next((frame[3] for frame in inspect.stack() if frame[3].startswith(&quot;test_&quot;)), &quot;Unknown test&quot;)
# log a message using defined logger and log level
self.logger.log(self.level, f&quot;{test_name}: {title}&quot;)
def pytest_configure(config):
&quot;&quot;&quot;Register `allure_step_logger` plugin if `allure_pytest` plugin is registered.&quot;&quot;&quot;
if config.pluginmanager.getplugin(&#39;allure_pytest&#39;):
allure_commons.plugin_manager.register(AllureStepLogger(config), &quot;allure_step_logger&quot;)
def pytest_addoption(parser):
&quot;&quot;&quot;Add a cmdline option --allure-step-log-level.&quot;&quot;&quot;
parser.getgroup(&quot;logging&quot;).addoption(
&quot;--allure-step-log-level&quot;,
dest=&quot;allure_step_log_level&quot;,
default=&quot;debug&quot;,
metavar=&quot;ALLURE_STEP_LEVEL&quot;,
help=&quot;Level of allure.step log messages. &#39;DEBUG&#39; by default.&quot;
)

test_module.py

import allure


def test_function_1():
    with allure.step(&quot;Step 1&quot;):
        pass
        with allure.step(&quot;Substep 1.1&quot;):
            pass
        with allure.step(&quot;Substep 1.2&quot;):
            pass
    with allure.step(&quot;Step 2&quot;):
        pass


def test_function_2():
    with allure.step(&quot;Step 1&quot;):
        pass
    with allure.step(&quot;Step 2&quot;):
        pass
    with allure.step(&quot;Step 3&quot;):
        pass

Output for pytest --alluredir=results --log-cli-level=debug will contain our logs since --allure-step-log-level is debug by default.

============================= test session starts =============================
collecting ... collected 2 items
test_module.py::test_function_1 
test_module.py::test_function_2 
============================== 2 passed in 0.08s ==============================
Process finished with exit code 0
-------------------------------- live log call --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Step 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Substep 1.1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Substep 1.2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Step 2
PASSED                                                                   [ 50%]
-------------------------------- live log call --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: Step 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: Step 2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: Step 3
PASSED                                                                   [100%]

To change logger level to INFO for example, just use --allure-step-log-level=info.

huangapple
  • 本文由 发表于 2023年2月8日 19:41:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75385288.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定