在调用参数之一的成员函数时如何对函数进行单元测试
How to unit test a function when it is calling a member function of one of the argument
我想对 Python 中的以下函数进行单元测试:
def get_params(env, secret_fetcher):
try:
url = env['API_URL']
except KeyError:
raise
try:
key = secret_fetcher.get_secret('CLIENT-KEY')
secret = secret_fetcher.get_secret('CLIENT-SECRET')
except:
raise
return url, key, secret
它从环境中读取一个参数,同时使用 KeyVault class secret_fetcher
的对象从 Key Vault 中检索另外两个参数。我在我的主要功能中调用它,如下所示:
secret_fetcher = SecretFetcher(vault_url)
url, key, secret = get_params(os.environ, secret_fetcher)
我正在为此功能编写单元测试。对于 env
我在测试中使用了字典。但是,我该如何处理其成员函数在要测试的函数内部被调用的函数的第二个参数?
class TestApp():
def test_get_params(self):
env = {'WrongField': 'http://test.com/123'}
<mock secret_fetcher>
self.assertRaises(KeyError, get_params, env, secret)
我嘲笑 secret_fetcher
还是 secret_fetcher.get_secret
?尤其是当 get_secret returns 不同的值被喂养它自己的不同参数时。我是否应该模拟 class SecretFetcher
并实现一个 get_secret
函数 returns 期望输出这两个不同值的参数?
如果你只是打算按原样测试异常,那么在这个阶段模拟 secret_fetcher
参数基本上是无关紧要的,因为一个简单的 None
值就可以了,因为它永远不会被触及,但这里有一个开始的例子:
# include the `get_param` function by import or inline here
import unittest
from unittest.mock import Mock
class TestApp(unittest.TestCase):
def test_get_params_missing_url(self):
env = {'missing': 'fail'}
secret_fetcher = Mock()
with self.assertRaises(KeyError):
get_params(env, secret_fetcher)
(请注意,我更喜欢使用 assertRaises
作为上下文管理器,以确保以更自然的方式编写函数调用;请注意 with
块中的第一个异常将防止后续代码在该块中执行,因此建议 assertRaises
上下文管理器中只包含一个逻辑表达式,或者至少是最后一行;即一次只能测试一个异常)
运行这一项测试:
$ python -m unittest demo.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
但是,鉴于在单元测试上下文中使用模拟背后的理论是使用最少的外部依赖项(即不使用其他真实模块、方法或 classes 来测试代码; 这使得只针对相关单元进行测试),使用 unittest.mock.Mock
和朋友提供的其他功能可以简化这个目标。
如果提供了正确的 env
,您可能希望确保使用正确的参数调用 get_secret
并返回预期的结果。还测试错误处理是否按预期处理。可以附加到上面 TestApp
class 的其他方法:
def test_get_params_success(self):
env = {'API_URL': 'https://api.example.com'}
def get_secret(arg):
return arg
secret_fetcher = Mock()
secret_fetcher.get_secret.side_effect = get_secret
url, key, secret = get_params(env, secret_fetcher)
self.assertEqual(url, 'https://api.example.com')
self.assertEqual(key, 'CLIENT-KEY')
self.assertEqual(secret, 'CLIENT-SECRET')
# Test that the secret_fetcher.get_secret helper was called
# with both arguments
secret_fetcher.get_secret.assert_any_call('CLIENT-KEY')
secret_fetcher.get_secret.assert_any_call('CLIENT-SECRET')
self.assertEqual(
secret_fetcher.get_secret.call_args[0], ('CLIENT-SECRET',))
def test_get_params_failure(self):
env = {'API_URL': 'https://api.example.com'}
secret_fetcher = Mock()
secret_fetcher.get_secret.side_effect = ValueError('wrong value')
with self.assertRaises(ValueError):
get_params(env, secret_fetcher)
# Test secret_fetcher.get_secret helper was only called with
# the first CLIENT-KEY argument
# Python 3.8 can check secret_fetcher.get_secret.call_args.args
self.assertEqual(
secret_fetcher.get_secret.call_args[0], ('CLIENT-KEY',))
正在测试:
$ python -m unittest demo.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
请注意,虽然我对您的 SecretFetcher
class 所做或拥有的信息绝对为零,但三个测试用例一起测试了提供的 get_params
函数以确保其行为如下预期的,包括测试它应该如何使用 secret_fetcher.get_secret
,它按预期处理错误,并且所有提供的测试用例都测试了 [中的每个 代码行=45=]问题中提供的示例。
希望这可以作为一个综合示例,说明如何使用模拟来满足单元测试的目标。
我想对 Python 中的以下函数进行单元测试:
def get_params(env, secret_fetcher):
try:
url = env['API_URL']
except KeyError:
raise
try:
key = secret_fetcher.get_secret('CLIENT-KEY')
secret = secret_fetcher.get_secret('CLIENT-SECRET')
except:
raise
return url, key, secret
它从环境中读取一个参数,同时使用 KeyVault class secret_fetcher
的对象从 Key Vault 中检索另外两个参数。我在我的主要功能中调用它,如下所示:
secret_fetcher = SecretFetcher(vault_url)
url, key, secret = get_params(os.environ, secret_fetcher)
我正在为此功能编写单元测试。对于 env
我在测试中使用了字典。但是,我该如何处理其成员函数在要测试的函数内部被调用的函数的第二个参数?
class TestApp():
def test_get_params(self):
env = {'WrongField': 'http://test.com/123'}
<mock secret_fetcher>
self.assertRaises(KeyError, get_params, env, secret)
我嘲笑 secret_fetcher
还是 secret_fetcher.get_secret
?尤其是当 get_secret returns 不同的值被喂养它自己的不同参数时。我是否应该模拟 class SecretFetcher
并实现一个 get_secret
函数 returns 期望输出这两个不同值的参数?
如果你只是打算按原样测试异常,那么在这个阶段模拟 secret_fetcher
参数基本上是无关紧要的,因为一个简单的 None
值就可以了,因为它永远不会被触及,但这里有一个开始的例子:
# include the `get_param` function by import or inline here
import unittest
from unittest.mock import Mock
class TestApp(unittest.TestCase):
def test_get_params_missing_url(self):
env = {'missing': 'fail'}
secret_fetcher = Mock()
with self.assertRaises(KeyError):
get_params(env, secret_fetcher)
(请注意,我更喜欢使用 assertRaises
作为上下文管理器,以确保以更自然的方式编写函数调用;请注意 with
块中的第一个异常将防止后续代码在该块中执行,因此建议 assertRaises
上下文管理器中只包含一个逻辑表达式,或者至少是最后一行;即一次只能测试一个异常)
运行这一项测试:
$ python -m unittest demo.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
但是,鉴于在单元测试上下文中使用模拟背后的理论是使用最少的外部依赖项(即不使用其他真实模块、方法或 classes 来测试代码; 这使得只针对相关单元进行测试),使用 unittest.mock.Mock
和朋友提供的其他功能可以简化这个目标。
如果提供了正确的 env
,您可能希望确保使用正确的参数调用 get_secret
并返回预期的结果。还测试错误处理是否按预期处理。可以附加到上面 TestApp
class 的其他方法:
def test_get_params_success(self):
env = {'API_URL': 'https://api.example.com'}
def get_secret(arg):
return arg
secret_fetcher = Mock()
secret_fetcher.get_secret.side_effect = get_secret
url, key, secret = get_params(env, secret_fetcher)
self.assertEqual(url, 'https://api.example.com')
self.assertEqual(key, 'CLIENT-KEY')
self.assertEqual(secret, 'CLIENT-SECRET')
# Test that the secret_fetcher.get_secret helper was called
# with both arguments
secret_fetcher.get_secret.assert_any_call('CLIENT-KEY')
secret_fetcher.get_secret.assert_any_call('CLIENT-SECRET')
self.assertEqual(
secret_fetcher.get_secret.call_args[0], ('CLIENT-SECRET',))
def test_get_params_failure(self):
env = {'API_URL': 'https://api.example.com'}
secret_fetcher = Mock()
secret_fetcher.get_secret.side_effect = ValueError('wrong value')
with self.assertRaises(ValueError):
get_params(env, secret_fetcher)
# Test secret_fetcher.get_secret helper was only called with
# the first CLIENT-KEY argument
# Python 3.8 can check secret_fetcher.get_secret.call_args.args
self.assertEqual(
secret_fetcher.get_secret.call_args[0], ('CLIENT-KEY',))
正在测试:
$ python -m unittest demo.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
请注意,虽然我对您的 SecretFetcher
class 所做或拥有的信息绝对为零,但三个测试用例一起测试了提供的 get_params
函数以确保其行为如下预期的,包括测试它应该如何使用 secret_fetcher.get_secret
,它按预期处理错误,并且所有提供的测试用例都测试了 [中的每个 代码行=45=]问题中提供的示例。
希望这可以作为一个综合示例,说明如何使用模拟来满足单元测试的目标。