pytest - 修补 class 不起作用,而是调用 class
pytest - Patching a class does not work, calls class instead
不知道为什么,但这是我的代码片段:
stats_collector.py
class StatsCollector( object ) :
def __init__( self, user_id, app_id ) :
logging.info( "Stats: APP_ID = {0}, ID = {1}".format( app_id, user_id ) )
mydb.py
from namespacename.mylibname.stats_collector import StatsCollector
class Db( object ) :
# constructor/destructor
def __init__( self, dbname ) :
....
def beginTransaction( self, user_id, app_id ) :
logging.info( "Begin" )
self._stats = StatsCollector( user_id, app_id )
test_mylibname_mydb
from namespacename.mylibname.mydb import Db
from namespacename.mylibname.stats_collector import StatsCollector
@pytest.mark.mydb_temp
@mock.patch( 'namespacename.mylibname.stats_collector.StatsCollector')
def test_db_beginTransaction( mock_stats_collector ) :
db = Db( TEST_DB_NAME )
mock_stats_collector.return_value = mock.Mock()
db.beginTransaction( TEST_ID, TEST_APP_ID )
......
......
我可以在我的 stats_collector.__init__
中看到我的日志 - 我为什么要输入那个?不应该在我的 beginTransaction
中调用 StatsCollector
return 值作为 MockObject 并且我不应该看到任何日志吗?
结构如下:
tests/
├── mylibname
│ ├── test_mylibname_mydb.py
namespacename/mylibname
├── stats_collector
│ ├── mylibname_stats_collector.py
│ ├── __init__.py
├── mydb
│ ├── mylibname_mydb.py
│ ├── __init__.py
** 编辑 **
遵循评论中的建议 -
@mock.patch( 'namespacename.mylibname.mydb.StatsCollector')
def test_db_beginTransaction( mock_stats_init ) :
db = Db( TEST_DB_NAME )
db.beginTransaction( TEST_UUID, TEST_APP_ID )
print db._transaction
assert db._transaction is mock_stats_init
让我明白:
E AssertionError: assert <namespacename.mylibname.stats_collector.mylibname_stats_collector.StatsCollector object at 0x7f42d837b110> is <MagicMock name='StatsCollector' id='139925072008976'>
E + where <namespacename.mylibname.stats_collector.mylibname_stats_collector.StatsCollector object at 0x7f42d837b110> = <namespacename.mylibname.mydb.mylibname_mydb.Db object at 0x7f42d8365850>._transaction
您需要修补正在测试的模块中的符号 'A',即 'B'。
当您执行 @mock.patch("full.path.A")
时,它应该是:
@mock.patch("full.path.to.B.A")
现在,模块 B
中的符号 A
已使用您的 mock 进行了修补。
你是不是漏掉了文件本身的名称?
@mock.patch( 'namespacename.mylibname.stats_collector.mylibname_stats_collector.StatsCollector')
在我写这篇文章的时候你可能已经明白了。
经验法则:不要在定义的地方修补 classes 或函数,而是在使用它们的地方修补它们。
a.py
class A:
def exponent(self, a, b)
return a ** b
b.py
from a import A
class B:
def add_constat(a, b, c)
return A().exponent(a, b) + c
为了测试 add_constant 方法,您可能会想像
那样修补 A
TestB:
@patch('a.A.exponent', mock_a)
test_add_constant(self, mock_a)
这是错误的,因为您正在修补 class 文件中的 class 文件,其定义位于
A 在 class B 中的文件 b 中使用。因此,您应该修补那个 class。
TestB:
@patch('b.A')
test_add_constant(self, mock_a):
# mock_a is fake class of A, the return_value of mock_a gives us an instance (object) of the class(A)
instance_a = mock_a.return_value #
# we now have instance of the class i.e A, hence it is possible to call the methods of class A
instance_a.exponent.return_value = 16
assert 26 = B().add_constant(2,4,10)
我已经稍微修改了您的代码,以便它可以在我的 python 环境中运行。
stats_collector.py
class StatsCollector:
def __init__(self, user_id, app_id):
self.user_id = user_id
self.app_id = app_id
def stat(self):
return self.user_id + ':' + self.app_id
mydb.py
from stats_collector import StatsCollector
import logging
class Db:
# constructor
def __init__(self, db_name):
self.db_name = db_name
def begin_transaction(self, user_id, app_id):
logging.info("Begin")
stat = StatsCollector(user_id, app_id).stat()
if stat:
return user_id + ':' + app_id
return "wrong User"
使用类似的类比:为了测试 class 文件 mydb.py 中的“begin_transaction”,您需要修补 mydb.py 中使用的 StatsCollector class文件
test_mydb.py
from unittest.mock import patch
from unittest import TestCase
class TestDb(TestCase):
@patch('mydb.StatsCollector')
def test_begin_transaction(self, db_class_mock):
# db_class_mock is the mock of the class, it is not an instance of the DB class.
# to create an instance of the class, you need to call return_value
db_instance = db_class_mock.return_value
db_instance.stat.return_value = 1
# i prefere to do the above two lines in just one line as
#db_class_mock.return_value.stat.return_value = 1
db = Db('stat')
expected = db.begin_transaction('Addis', 'Abeba')
assert expected == 'Addis' + ':' + 'Abeba'
# set the return value of stat method
db_class_mock.return_value.stat.return_value = 0
expected = db.begin_transaction('Addis', 'Abeba')
assert expected == "wrong User"
希望对网上的人有所帮助
不知道为什么,但这是我的代码片段:
stats_collector.py
class StatsCollector( object ) :
def __init__( self, user_id, app_id ) :
logging.info( "Stats: APP_ID = {0}, ID = {1}".format( app_id, user_id ) )
mydb.py
from namespacename.mylibname.stats_collector import StatsCollector
class Db( object ) :
# constructor/destructor
def __init__( self, dbname ) :
....
def beginTransaction( self, user_id, app_id ) :
logging.info( "Begin" )
self._stats = StatsCollector( user_id, app_id )
test_mylibname_mydb
from namespacename.mylibname.mydb import Db
from namespacename.mylibname.stats_collector import StatsCollector
@pytest.mark.mydb_temp
@mock.patch( 'namespacename.mylibname.stats_collector.StatsCollector')
def test_db_beginTransaction( mock_stats_collector ) :
db = Db( TEST_DB_NAME )
mock_stats_collector.return_value = mock.Mock()
db.beginTransaction( TEST_ID, TEST_APP_ID )
......
......
我可以在我的 stats_collector.__init__
中看到我的日志 - 我为什么要输入那个?不应该在我的 beginTransaction
中调用 StatsCollector
return 值作为 MockObject 并且我不应该看到任何日志吗?
结构如下:
tests/
├── mylibname
│ ├── test_mylibname_mydb.py
namespacename/mylibname
├── stats_collector
│ ├── mylibname_stats_collector.py
│ ├── __init__.py
├── mydb
│ ├── mylibname_mydb.py
│ ├── __init__.py
** 编辑 **
遵循评论中的建议 -
@mock.patch( 'namespacename.mylibname.mydb.StatsCollector')
def test_db_beginTransaction( mock_stats_init ) :
db = Db( TEST_DB_NAME )
db.beginTransaction( TEST_UUID, TEST_APP_ID )
print db._transaction
assert db._transaction is mock_stats_init
让我明白:
E AssertionError: assert <namespacename.mylibname.stats_collector.mylibname_stats_collector.StatsCollector object at 0x7f42d837b110> is <MagicMock name='StatsCollector' id='139925072008976'>
E + where <namespacename.mylibname.stats_collector.mylibname_stats_collector.StatsCollector object at 0x7f42d837b110> = <namespacename.mylibname.mydb.mylibname_mydb.Db object at 0x7f42d8365850>._transaction
您需要修补正在测试的模块中的符号 'A',即 'B'。
当您执行 @mock.patch("full.path.A")
时,它应该是:
@mock.patch("full.path.to.B.A")
现在,模块 B
中的符号 A
已使用您的 mock 进行了修补。
你是不是漏掉了文件本身的名称?
@mock.patch( 'namespacename.mylibname.stats_collector.mylibname_stats_collector.StatsCollector')
在我写这篇文章的时候你可能已经明白了。
经验法则:不要在定义的地方修补 classes 或函数,而是在使用它们的地方修补它们。
a.py
class A:
def exponent(self, a, b)
return a ** b
b.py
from a import A
class B:
def add_constat(a, b, c)
return A().exponent(a, b) + c
为了测试 add_constant 方法,您可能会想像
那样修补 ATestB:
@patch('a.A.exponent', mock_a)
test_add_constant(self, mock_a)
这是错误的,因为您正在修补 class 文件中的 class 文件,其定义位于
A 在 class B 中的文件 b 中使用。因此,您应该修补那个 class。
TestB:
@patch('b.A')
test_add_constant(self, mock_a):
# mock_a is fake class of A, the return_value of mock_a gives us an instance (object) of the class(A)
instance_a = mock_a.return_value #
# we now have instance of the class i.e A, hence it is possible to call the methods of class A
instance_a.exponent.return_value = 16
assert 26 = B().add_constant(2,4,10)
我已经稍微修改了您的代码,以便它可以在我的 python 环境中运行。
stats_collector.py
class StatsCollector:
def __init__(self, user_id, app_id):
self.user_id = user_id
self.app_id = app_id
def stat(self):
return self.user_id + ':' + self.app_id
mydb.py
from stats_collector import StatsCollector
import logging
class Db:
# constructor
def __init__(self, db_name):
self.db_name = db_name
def begin_transaction(self, user_id, app_id):
logging.info("Begin")
stat = StatsCollector(user_id, app_id).stat()
if stat:
return user_id + ':' + app_id
return "wrong User"
使用类似的类比:为了测试 class 文件 mydb.py 中的“begin_transaction”,您需要修补 mydb.py 中使用的 StatsCollector class文件
test_mydb.py
from unittest.mock import patch
from unittest import TestCase
class TestDb(TestCase):
@patch('mydb.StatsCollector')
def test_begin_transaction(self, db_class_mock):
# db_class_mock is the mock of the class, it is not an instance of the DB class.
# to create an instance of the class, you need to call return_value
db_instance = db_class_mock.return_value
db_instance.stat.return_value = 1
# i prefere to do the above two lines in just one line as
#db_class_mock.return_value.stat.return_value = 1
db = Db('stat')
expected = db.begin_transaction('Addis', 'Abeba')
assert expected == 'Addis' + ':' + 'Abeba'
# set the return value of stat method
db_class_mock.return_value.stat.return_value = 0
expected = db.begin_transaction('Addis', 'Abeba')
assert expected == "wrong User"
希望对网上的人有所帮助