4

我有一个有几个故障点的功能:

def setup_foo(creds):
    """
    Creates a foo instance with which we can leverage the Foo virtualization
    platform.

    :param creds: A dictionary containing the authorization url, username,
                  password, and version associated with the Foo
                  cluster.
    :type creds:  dict
    """

    try:
        foo = Foo(version=creds['VERSION'],
                  username=creds['USERNAME'],
                  password=creds['PASSWORD'],
                  auth_url=creds['AUTH_URL'])

        foo.authenticate()
        return foo
    except (OSError, NotFound, ClientException) as e:
        raise UnreachableEndpoint("Couldn't find auth_url {0}".format(creds['AUTH_URL']))
    except Unauthorized as e:
        raise UnauthorizedUser("Wrong username or password.")
    except UnsupportedVersion as e:
        raise Unsupported("We only support Foo API with major version 2")

我想测试是否捕获了所有相关异常(尽管目前处理得不好)。

我有一个通过的初始测试用例:

def test_setup_foo_failing_auth_url_endpoint_does_not_exist(self):
    dummy_creds = {
        'AUTH_URL' : 'http://bogus.example.com/v2.0',
        'USERNAME' : '', #intentionally blank.
        'PASSWORD' : '', #intentionally blank.
        'VERSION'  : 2 
    }
    with self.assertRaises(UnreachableEndpoint):
        foo = osu.setup_foo(dummy_creds)

但是我怎样才能让我的测试框架相信 AUTH_URL 实际上是一个有效/可访问的 URL?

我创建了一个模拟类Foo

class MockFoo(Foo):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

我的想法是模拟调用并消除引发异常setup_foo的副作用。UnreachableEndpoint我知道如何副作用添加到Mockwith unittest.mock,但是如何删除它们?

4

2 回答 2

2

假设您的异常是从 引发的foo.authenticate(),您在这里要意识到的是,数据在您的测试中是否真的有效并不一定重要。你真正想说的是:

当这个外部方法引发某些事情时,我的代码应该根据该事情做出相应的行为。

因此,考虑到这一点,您要做的是使用不同的测试方法,通过这些方法传递应该是有效的数据,并让您的代码做出相应的反应。数据本身并不重要,但它提供了一种记录方式来显示代码应该如何处理以这种方式传递的数据。

归根结底,你不应该关心 nova 客户端如何处理你给它的数据(nova 客户端是经过测试的,你不应该关心它)。你关心的是它向你吐出什么,以及你想如何处理它,不管你给了它什么。

换句话说,为了您的测试,您实际上可以将虚拟 url 传递为:

"this_is_a_dummy_url_that_works"

为了您的测试,您可以让它通过,因为在您的 中mock,您将相应地加注。

例如。你应该在这里做的实际上是Clientnovaclient. 有了这个模拟,您现在可以在 novaclient 中操作任何调用,以便正确测试您的代码。

这实际上使我们找到了问题的根源。您的第一个例外是捕获以下内容:

except (OSError, NotFound, ClientException)

这里的问题是你现在正在捕捉ClientException. 几乎每个异常都novaclient继承自ClientException,因此无论您尝试在该异常行之外进行什么测试,都永远不会遇到这些异常。您在这里有两个选择。抓住ClientException,然后提出一个自定义异常,或者,远程ClientException,更明确(就像你已经是)。

因此,让我们删除ClientException并相应地设置我们的示例。

因此,在您的真实代码中,您现在应该将第一个异常行设置为:

except (OSError, NotFound) as e:

此外,您遇到的下一个问题是您没有正确模拟。你应该嘲笑你正在测试的地方。因此,如果您的setup_nova方法位于名为your_nova_module. 就这一点而言,你应该嘲笑它。下面的例子说明了这一切。

@patch("your_nova_module.Client", return_value=Mock())
def test_setup_nova_failing_unauthorized_user(self, mock_client):
    dummy_creds = {
        'AUTH_URL': 'this_url_is_valid',
        'USERNAME': 'my_bad_user. this should fail',
        'PASSWORD': 'bad_pass_but_it_does_not_matter_what_this_is',
        'VERSION': '2.1',
        'PROJECT_ID': 'does_not_matter'
    }

    mock_nova_client = mock_client.return_value
    mock_nova_client.authenticate.side_effect = Unauthorized(401)

    with self.assertRaises(UnauthorizedUser):
        setup_nova(dummy_creds)

因此,上面示例的主要思想是,您传递的数据无关紧要。真正重要的是您想知道当外部方法引发时您的代码将如何反应。

所以,我们的目标是实际提出一些东西,让你的第二个异常处理程序被测试:Unauthorized

此代码已针对您在问题中发布的代码进行了测试。唯一的修改是使用模块名称来反映我的环境。

于 2016-10-07T14:51:34.353 回答
0

如果你想从伪造的 url 中模拟出 http 服务器,我建议你查看HTTPretty。它在套接字级别模拟 url,因此它可以欺骗大多数 Python HTTP 库它是一个有效的 url。

我建议为您的单元测试进行以下设置:

class FooTest(unittest.TestCase):
    def setUp(self):
        httpretty.register_uri(httpretty.GET, "http://bogus.example.com/v2.0",
                       body='[{"response": "Valid"}]',
                       content_type="application/json")
    @httpretty.activate
    def test_test_case(self):
        resp = requests.get("http://bogus.example.com/v2.0")
        self.assertEquals(resp.status_code, 200)

请注意,模拟仅适用于用http.activate装饰器装饰的堆栈,因此它不会泄漏到您不想模拟的代码中的其他地方。希望这是有道理的。

于 2016-10-07T14:53:50.447 回答