【问题标题】:How To Get Name Of Test Cases Dynamically In conftest.py > Logger Method如何在 conftest.py > Logger 方法中动态获取测试用例的名称
【发布时间】:2021-04-30 20:06:10
【问题描述】:

我正在使用带有页面对象模型的 python、selenium 和 pytest 框架运行测试。

到目前为止,我将 logger 方法从 BaseClass 移到了 conftest.py,因为如果我想记录一些东西,我必须在每个测试用例中创建一个 log 对象。

可重现的测试用例如下:

from baseclass import BaseClass

class TestClass(BaseClass):

    def test_case1(self):
        self.log.info('testing log!')

    def test_case2(self):
        self.log.info('second log!')

我将 logger 方法抽象到 conftest.py 中,但是当我运行测试时,我在日志中获得了文件名作为 setup 而不是测试用例名称。根据其他建议(感谢 SO 社区),我将文件名作为 arg 传递给 logger() 方法,但现在它只打印第一个测试用例名称的每个日志。

# Using logger(): name = inspect.stack()[0][3]
> 2021-01-27 05:42:42,377 : INFO : setup : testing log!

# Using logger(full_name), where full_name = os.environ.get('PYTEST_CURRENT_TEST').split(' ')[0]
# Similar results when using full_name = request.node.__name__ or request.module.__name__
> 2021-01-28 02:12:39,877 : INFO : test_my_page.py::TestClass::test_case1 : testing log!
> 2021-01-28 02:12:39,877 : INFO : test_my_page.py::TestClass::test_case1 : second log!

我的 conftest.py 设置如下:

import pytest
from selenium import webdriver
import inspect
import logging

def logger():
    log_item = logging.getLogger(name)
    with open('LogFile.log', 'w+') as logFile:
        logFile.seek(0)
        logFile.truncate()
    file_handler = logging.FileHandler("LogFile.log")
    log_format = logging.Formatter("%(asctime)s : %(levelname)s : %(name)s : %(message)s")
    file_handler.setFormatter(log_format)
    log_item.addHandler(file_handler)
    log_item.setLevel(logging.DEBUG)
    return log_item


@pytest.fixture(scope='class')
def setup(request):

    driver = webdriver.Chrome(executable_path=TestData.CHROME)
    full_name = os.environ.get('PYTEST_CURRENT_TEST').split(' ')[0]

    request.cls.log = logger(full_name)
    request.cls.driver = driver

    yield
    driver.close()

我实际上想获得正在执行的测试的名称。如下:

2021-01-27 01:57:02,594:信息:test_my_page.py::TestClass::test_case1:测试日志! 2021-01-27 01:57:32,292:INFO:test_my_page.py::TestClass::test_case2:第二个日志!

如何在不将方法移回 BaseClass 的情况下让 logger 方法动态打印测试用例的名称? 我即将编写大量测试用例,并试图找到一种更好的方法,因为在每个测试用例中重复创建一个记录器对象。

我的 BaseClass 如下:

import pytest

@pytest.mark.usefixtures('setup')
class BaseClass:
    pass

【问题讨论】:

  • @MrBeanBremen 感谢您提出类似的问题,但这并不能解决我在所有日志中出现相同测试名称的问题。据我了解,conftest.py > setup() 在测试开始时只运行一次,动态获取测试用例名称一直是一个挑战。
  • 对,您的 setup 具有类范围,因此您不会从请求中获得名称 - 我错过了,抱歉。但是也有一个使用环境变量的答案(也如下面的答案所示),所以它可能仍然对您有所帮助。
  • @MrBeanBremen 我尝试使用下面提到的 Pytest 环境变量,但仍然有这个问题。我看到您建议的另一个问题的可能解决方案,从 dir 中提取所有名称: test_names = [n for n in dir(self) if n.startswith('test_')] 但是,我认为这不是最好的解决方案到此为止。

标签: python python-3.x selenium logging pytest


【解决方案1】:

这是我的解决方案:

文件temp_test.py:使用你的类名会使pytest无法识别测试用例,所以我将它重命名为TestClass。你的函数名仍然是test_case1。阅读有关pytest naming convention 的更多信息。

from baseclass import BaseClass

class TestClass(BaseClass):

    def test_case1(self):
        # log = self.logger()
        # log.info('testing log!') # old method

        self.log.info('testing log 1!') # new method

我添加了另一个测试文件z_test.py

from baseclass import BaseClass

class Test2(BaseClass):

    def test_case2(self):
        # log = self.logger()
        # log.info('testing log!') # old method

        self.log.info('testing log 2!') # new method

    def test_case3(self):
        # log = self.logger()
        # log.info('testing log!') # old method

        self.log.info('testing log 3!') # new method

文件:conftest.py

import os
import pytest
import logging


def logger(name):
    log_item = logging.getLogger(name)
    with open('LogFile.log', 'a') as logFile:
        logFile.seek(0)
        # logFile.truncate()
    file_handler = logging.FileHandler("LogFile.log")
    log_format = logging.Formatter("%(asctime)s : %(levelname)s : %(name)s : %(message)s")
    file_handler.setFormatter(log_format)
    log_item.addHandler(file_handler)
    log_item.setLevel(logging.DEBUG)
    return log_item

@pytest.fixture(scope='function')
def setup(request):
    # full name: 'class name::function name'
    full_name = os.environ.get('PYTEST_CURRENT_TEST').split(' ')[0]
    # full_name = request.module.__name__ # print the function name
    # full_name = request.node.name # print the class name
    request.cls.log = logger(full_name)
    yield
    print('close')

conftest.py,我修改了一些:

  • logger 函数现在将具有 name 参数。您的测试设置将提供此名称。
  • os.environ.get('PYTEST_CURRENT_TEST').split(' ')[0] 将提供设置全名,在这种情况下,输出将为 2021-01-27 14:00:23,436 : INFO : temp_test.py::TestClass::test_case1 : testing log!
  • full_name = request.module.__name__ 将仅提供函数名称:test_case1
  • full_name = request.node.name 将仅提供类名:TestClass

我认为最好使用PYTEST_CURRENT_TEST env var,并用它进行字符串操作。


更新您对未生成其他测试名称的评论:

  • setup() 需要在函数范围内,因为对于您的每个测试,您都希望将测试放入函数中。如果你像以前一样将它用作class,它只会在第一个测试函数开始时调用一次设置。在我的示例中,它只会输出两次test_case2
  • open('LogFile.log', 'w+') 应该是 open('LogFile.log', 'a')a 模式将追加文本,w 模式将删除所有内容,因此只会保存您最后一个测试用例的日志。
  • logFile.truncate() 也会删除除最后一个测试用例的日志之外的所有内容。

我的输出:

2021-01-28 15:22:58,540 : INFO : temp_test.py::TestClass::test_case1 : testing log 1!
2021-01-28 15:22:58,543 : INFO : z_test.py::Test2::test_case2 : testing log 2!
2021-01-28 15:22:58,545 : INFO : z_test.py::Test2::test_case3 : testing log 3!

玩转这些线,你会得到你需要的。

另一个提示:运行测试时,您需要创建一个单独的报告文件夹,其中包含日志文件,并以日期、小时、分钟...等随机命名。使用像LogFile.log 这样的通用名称会导致文本追加,也许LogFile_2021_01_28_152101.log 更好,你的选择。

【讨论】:

  • 感谢您的回复。我尝试按照您的建议使用 Pytest env var,现在我在每个日志中看到相同的测试用例名称,而不是动态获取测试用例的名称。 request.method 不起作用。也没有将记录器方法移动到 setup() 本身,因为它有 (scope='class') 有什么办法可以解决这个问题?
  • 我用更多测试代码示例和输出更新了答案。问题在于setup() 的范围,截断和写入模式。您将需要使用示例代码以适应您的最佳状态。如果对您有帮助,请接受答案:)。
  • 感谢 @jackblk 无法将 setup() 的范围设置为起作用。我不希望浏览器在每次测试拆卸方法后关闭。我没有提供完整的代码,只在我的示例中给出了必要的块。因此,logger() 和 setup(scope='class') 中的 'w+' 方法。此外,为简单起见,此处的日志文件名也保持简短,并且我在此示例中删除了报告目录。我赞成您的回答,因为它很有帮助。
  • 啊,所以你是用 Selenium 来测试的吧?在这种情况下,还有另一种方法可以实现您想要的。您的设置应该只进行浏览器设置,不要在 setup() 中附加您的记录器。将您的 request.cls.log = logger(full_name) 移动到另一个函数,例如 function_logger()。用@pytest.fixture(scope='function', autouse=True) 装饰它,让它在每个功能中自动使用。这对你有用:)。
  • 这非常有效。不得不进行一些修改和文档帮助。谢谢! ^_^
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-16
  • 2016-08-15
  • 1970-01-01
  • 2013-08-31
  • 2012-11-10
相关资源
最近更新 更多