简体   繁体   English

如何在 pytest test_functions 之间重置 Python 运行时?

[英]How do I reset Python runtime between pytest test_functions?

I am using pytest to test code that creates a prometheus Python client to export metrics.我正在使用 pytest 来测试创建 prometheus Python 客户端以导出指标的代码。

Between the test functions the prometheus client does not reset (because it has an internal state) which screws up my tests.在测试功能之间,prometheus 客户端不会重置(因为它具有内部状态),这会搞砸我的测试。

I am looking for a way to basically get a new Pyhon runtime before all test function calls.我正在寻找一种在所有测试 function 调用之前基本上获得新的 Pyhon 运行时的方法。 That way the internal state of the prometheus client would hopefully reset to a state that it had when the Python runtime started to execute my tests.这样,prometheus 客户端的内部 state 有望重置为 Python 运行时开始执行我的测试时的 state。

I already tried importlib.reload() but that does not work.我已经尝试过importlib.reload()但这不起作用。

If you want to start each test with "clean" Prometheus client then I think best is to move it's creation and tear down to fixture with function scope (it's actually default scope), like this:如果您想使用“干净”的 Prometheus 客户端开始每个测试,那么我认为最好将其创建并拆除到带有 function scope 的夹具(它实际上是默认范围),如下所示:

@pytest.fixture(scope=function)
def prometheus_client(arg1, arg2, etc...)
#create your client here
yield client
#remove your client here

Then you define your tests using this fixture:然后你使用这个夹具定义你的测试:

def test_number_one(prometheus_client):
#test body

This way client is created from scratch in each test and deleted even if the test fails.这样客户端在每个测试中都是从头开始创建的,即使测试失败也会被删除。

Straightforward approach: memorize current metric values before the test直截了当的方法:在测试前记住当前的指标值

First of all, let me show how I believe you should work with metrics in tests (and how I do it in my projects).首先,让我展示一下我认为您应该如何在测试中使用指标(以及我如何在我的项目中这样做)。 Instead of doing resets, I keep track of the metric values before the test starts.我没有进行重置,而是在测试开始之前跟踪指标值。 After the test finishes, I collect the metrics again and analyze the diff between both values.测试完成后,我再次收集指标并分析两个值之间的差异。 Example of a django view test with a counter:带有计数器的 django 视图测试示例:

import copy
import prometheus_client
import pytest

from django.test import Client
from django.urls import reverse


def find_value(metrics, metric_name):
    return next((
        sample.value
        for metric in metrics
        for sample in metric.samples
        if sample.name == metric_name
    ), None)


@pytest.fixture
def metrics_before():
    yield copy.deepcopy(list(prometheus_client.REGISTRY.collect()))


@pytest.fixture
def client():
    return APIClient()


def test_counter_increases_by_one(client, metrics_before):
    # record the metric value before the HTTP client requests the view
    value_before = find_value(metrics_before, 'http_requests_total') or 0.0
    # do the request
    client.get('/my-view/')
    # collect the metric value again
    metrics_after = prometheus_client.REGISTRY.collect()
    value_after = find_value(metrics_after, 'http_requests_total')
    # the value should have increased by one
    assert value_after == value_before + 1.0

Now let's see what can be done with the registry itself.现在让我们看看注册表本身可以做什么。 Note that this uses prometheus-client internals and is fragile by definition - use at your own risk!请注意,这使用了prometheus-client内部结构,并且根据定义是脆弱的 - 使用风险自负!

Messing with prometheus-client internals: unregister all metricsprometheus-client内部混淆:取消注册所有指标

If you are sure your test code will invoke metrics registration from scratch, you can unregister all metrics from the registry before the test starts:如果您确定您的测试代码将从头开始调用指标注册,您可以在测试开始之前从注册表中取消注册所有指标:

@pytest.fixture(autouse=True)
def clear_registry():
    collectors = tuple(prometheus_client.REGISTRY._collector_to_names.keys())
    for collector in collectors:
        prometheus_client.REGISTRY.unregister(collector)
    yield

Beware that this will only work if your test code invokes metrics registration again, Otherwise.请注意,这仅在您的测试代码再次调用指标注册时才有效,否则。 you will effectively stop the metrics collection, For example, the built-in PlatformCollector will be gone until you explicitly register it again, eg by creating a new instance via prometheus_client.PlatformCollector() .您将有效地停止指标收集,例如,内置的PlatformCollector将消失,直到您再次明确注册它,例如通过prometheus_client.PlatformCollector()创建一个新实例。

Messing with prometheus-client internals: reset (almost) all metricsprometheus-client内部结构混淆:重置(几乎)所有指标

You can also reset the values of registered metrics:您还可以重置已注册指标的值:

@pytest.fixture(autouse=True)
def reset_registry():
    collectors = tuple(prometheus_client.REGISTRY._collector_to_names.keys())
    for collector in collectors:
        try:
            collector._metrics.clear()
            collector._metric_init()
        except AttributeError:
            pass  # built-in collectors don't inherit from MetricsWrapperBase
    yield

This will reinstantiate the values and metrics of all counters/gauges/histograms etc. The test from above could now be written as这将重新实例化所有计数器/仪表/直方图等的值和指标。上面的测试现在可以写成

def test_counter_increases_by_one(client):
    # do the request
    client.get('/my-view/')
    # collect the metric value
    metrics_after = prometheus_client.REGISTRY.collect()
    value_after = find_value(metrics_after, 'http_requests_total')
    # the value should be one
    assert value_after == 1.0

Of course, this will not reset any metrics of built-in collectors, like PlatformCollector since it scrapes the values only once at instantiation, or ProcessCollector because it doesn't store any values at all, instead reading them from OS anew.当然,这不会重置内置收集器的任何指标,例如PlatformCollector因为它在实例化时只抓取一次值,或者ProcessCollector因为它根本不存储任何值,而是从操作系统重新读取它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM