使用pytest补丁装饰器测试ray actors远程功能

Using pytest patch decorator to test ray actors remote function

我正在尝试 运行 射线远程功能的单元测试。我正在使用 @patch 装饰器来修补远程功能。

foo.py

class Foo(object):
    def __init__(self):
        self.value = 0

    def bar(self):
        self.value = 100
        print("In original method")
        assert False

test_foo.py

from unittest.mock import patch

import pytest
import unittest
import ray

from tests.foo import Foo


@pytest.fixture
def ray_fixture():
    print("Initializing ray")
    if not ray.is_initialized():
        ray.init()
    yield None
    print("Terminating ray")
    ray.shutdown()


def fake_bar(self):
    print("In fake method")
    assert True


@pytest.mark.usefixtures("ray_fixture")
class FooTestCase(unittest.TestCase):
    """Test cases for Foo module"""

    @patch("foo.Foo.bar", new=fake_bar)
    def test_bar(self):
        Foo().bar()

    @patch("foo.Foo.bar", new=fake_bar)
    def test_bar_remote(self):
        foo_actor = ray.remote(Foo).remote()
        obj_ref = foo_actor.bar.remote()
        ray.get(obj_ref)

测试 test_bar 通过,test_bar_remote 失败。 如果我使用 ray.init(local_mode=True) 那么两个测试都会通过。由于其他限制,我无法使用 local_mode=True

我们如何使用@patch 修补 ray actor 的远程方法?

这是一个替代方案。 Subclass Foo 与 stubbed/mocked 实现并在 ray 中使用它。这样, Foo class 将是完整的,你只会更新那些需要被模拟的,例如方法 bar().

test_foo.py

...
class FooStub(Foo):
    def bar(self, *args, **kwargs):
        print("In another fake method")
        assert True

        # Optionally, you can also call the real method if you want. You may update the arguments as needed.
        # super().bar(*args, **kwargs)

@pytest.mark.usefixtures("ray_fixture")
class FooTestCase(unittest.TestCase):
    ...
    def test_bar_remote(self):
        foo_actor = ray.remote(FooStub).remote()
        obj_ref = foo_actor.bar.remote()
        ray.get(obj_ref)
...

输出

$ pytest -q -rP
..
================================================================================================= PASSES ==================================================================================================
__________________________________________________________________________________________ FooTestCase.test_bar ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------------------
Initializing ray
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
In fake method
---------------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------------
Terminating ray
_______________________________________________________________________________________ FooTestCase.test_bar_remote _______________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------------------
Initializing ray
---------------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------------
Terminating ray
2 passed, 1 warning in 5.03s

我发现了一个下面的 hacky 方法,它涉及更改原始函数(并检查 env 变量以提供测试实现)

import os
class Foo(object):
    def __init__(self):
        self.value = 0

    def bar(self):
        self.value = 100
        if os.environ.get['TEST'] == 'True':
           print("In fake method")
           assert True
        else:    
           print("In original method")
           assert False
runtime_env = {"env_vars": {"TEST": "True"}}
    
ray.remote(Foo) .options(runtime_env=runtime_env) .remote()