Python 如何重用 Mock 以避免多次写入 mock.patch?
Python how to reuse a Mock to avoid writing mock.patch multiple times?
给出如下代码:
import flask
import time
app = flask.Flask(__name__)
def authorize():
print('starting authorize io')
time.sleep(1)
print('done authorize io')
class BlockingIo():
def __init__(self, n):
self.n = n
def do(self):
print('starting blocking io')
time.sleep(1)
print('ending blocking io')
@app.route('/', methods=['GET'])
@app.route('/<int:n>/', methods=['GET'])
def foo(n=1):
authorize()
b = BlockingIo(n)
b.do()
return str(n), 200
#app.run(port=5000)
我希望能够为 GET /n/
编写多个测试,每个测试模拟 authorize
和 BlockingIO(n)
:
app.testing = True
testapp = app.test_client()
import unittest
from unittest import mock
mock.patch('__main__.authorize')
class TestBlockingIo(unittest.TestCase):
@mock.patch('__main__.authorize')
@mock.patch('__main__.BlockingIo.do')
def test_1(self, m, m2):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
@mock.patch('__main__.authorize')
@mock.patch('__main__.BlockingIo.do')
def test_2(self, m, m2):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
unittest.main()
但是,我不想一遍又一遍地写出@mock.patch
装饰器。
我知道我们可以使用 class 装饰器,我可以子 class 以获得更多的可重用性:
@mock.patch('__main__.authorize')
@mock.patch('__main__.BlockingIo.do')
class TestBlockingIo(unittest.TestCase):
def test_1(self, m, m2):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
def test_2(self, m, m2):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
但是,这会强制 class 中的所有测试函数为每个模拟采用一个额外的参数。如果我在 class 中有不需要 BlockingIo
或 authorize
模拟的测试怎么办?
我想我想要的是一种执行以下操作的方法:
m = mock.something('__main__.authorize')
m2 = mock.something('__main__.BlockingIo.do')
class TestBlockingIo(unittest.TestCase):
def test_1(self):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
def test_2(self):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
如何重复使用我的 @mock.patch('__main__.authorize')
和 @mock.patch('__main__.BlockingIo.do')
以避免重复测试?
您可以使用 patches 并在 setUp
块中重复使用它们。
补丁很好,当你完成测试时,你可以 "unpatch" 东西,这意味着你不会永远嘲笑东西,因为其他一些测试可能需要 运行 在真实的代码。
在上面的link上,你会看到如下一段代码:
>>> class MyTest(TestCase):
... def setUp(self):
... patcher = patch('package.module.Class')
... self.MockClass = patcher.start()
... self.addCleanup(patcher.stop)
...
... def test_something(self):
... assert package.module.Class is self.MockClass
...
它工作正常,但我真的不喜欢为每个补丁调用 patch()
、start()
和 addCleanup()
。
您可以轻松地将其纳入可在测试中重用的基础 class classes:
class PatchMixin:
def patch(self, target, **kwargs):
p = mock.patch(target, **kwargs)
p.start()
self.addCleanup(p.stop)
class TestBlockingIo(unittest.TestCase, PatchMixin):
def setUp(self):
self.patch('__main__.authorize')
self.patch('__main__.BlockingIo.do')
def test_1(self):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
def test_2(self):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
为避免补丁测试方法的额外参数,您可以使用 patch
的 new
参数,例如:
@mock.patch('__main__.authorize', new=lambda: None)
documentation有点隐藏:
If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.
也可以 re-use 修补对象(这有时在很多地方重复修补 hard-to-remember 模块时很有用):
mocked_authorize = mock.patch('__main__.authorize', new=lambda: None)
@mocked_authorize
def test_authorize():
pass
@mocked_authorize
class TestBlockingIo(unittest.TestCase):
def test_1(self):
...
给出如下代码:
import flask
import time
app = flask.Flask(__name__)
def authorize():
print('starting authorize io')
time.sleep(1)
print('done authorize io')
class BlockingIo():
def __init__(self, n):
self.n = n
def do(self):
print('starting blocking io')
time.sleep(1)
print('ending blocking io')
@app.route('/', methods=['GET'])
@app.route('/<int:n>/', methods=['GET'])
def foo(n=1):
authorize()
b = BlockingIo(n)
b.do()
return str(n), 200
#app.run(port=5000)
我希望能够为 GET /n/
编写多个测试,每个测试模拟 authorize
和 BlockingIO(n)
:
app.testing = True
testapp = app.test_client()
import unittest
from unittest import mock
mock.patch('__main__.authorize')
class TestBlockingIo(unittest.TestCase):
@mock.patch('__main__.authorize')
@mock.patch('__main__.BlockingIo.do')
def test_1(self, m, m2):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
@mock.patch('__main__.authorize')
@mock.patch('__main__.BlockingIo.do')
def test_2(self, m, m2):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
unittest.main()
但是,我不想一遍又一遍地写出@mock.patch
装饰器。
我知道我们可以使用 class 装饰器,我可以子 class 以获得更多的可重用性:
@mock.patch('__main__.authorize')
@mock.patch('__main__.BlockingIo.do')
class TestBlockingIo(unittest.TestCase):
def test_1(self, m, m2):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
def test_2(self, m, m2):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
但是,这会强制 class 中的所有测试函数为每个模拟采用一个额外的参数。如果我在 class 中有不需要 BlockingIo
或 authorize
模拟的测试怎么办?
我想我想要的是一种执行以下操作的方法:
m = mock.something('__main__.authorize')
m2 = mock.something('__main__.BlockingIo.do')
class TestBlockingIo(unittest.TestCase):
def test_1(self):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
def test_2(self):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
如何重复使用我的 @mock.patch('__main__.authorize')
和 @mock.patch('__main__.BlockingIo.do')
以避免重复测试?
您可以使用 patches 并在 setUp
块中重复使用它们。
补丁很好,当你完成测试时,你可以 "unpatch" 东西,这意味着你不会永远嘲笑东西,因为其他一些测试可能需要 运行 在真实的代码。
在上面的link上,你会看到如下一段代码:
>>> class MyTest(TestCase):
... def setUp(self):
... patcher = patch('package.module.Class')
... self.MockClass = patcher.start()
... self.addCleanup(patcher.stop)
...
... def test_something(self):
... assert package.module.Class is self.MockClass
...
它工作正常,但我真的不喜欢为每个补丁调用 patch()
、start()
和 addCleanup()
。
您可以轻松地将其纳入可在测试中重用的基础 class classes:
class PatchMixin:
def patch(self, target, **kwargs):
p = mock.patch(target, **kwargs)
p.start()
self.addCleanup(p.stop)
class TestBlockingIo(unittest.TestCase, PatchMixin):
def setUp(self):
self.patch('__main__.authorize')
self.patch('__main__.BlockingIo.do')
def test_1(self):
r = testapp.get('/1/')
self.assertEquals(r.data, b'1')
def test_2(self):
r = testapp.get('/2/')
self.assertEquals(r.data, b'2')
为避免补丁测试方法的额外参数,您可以使用 patch
的 new
参数,例如:
@mock.patch('__main__.authorize', new=lambda: None)
documentation有点隐藏:
If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.
也可以 re-use 修补对象(这有时在很多地方重复修补 hard-to-remember 模块时很有用):
mocked_authorize = mock.patch('__main__.authorize', new=lambda: None)
@mocked_authorize
def test_authorize():
pass
@mocked_authorize
class TestBlockingIo(unittest.TestCase):
def test_1(self):
...