【问题标题】:pytest - Patching a class does not work, calls class insteadpytest - 修补类不起作用,而是调用类
【发布时间】:2019-04-09 16:59:24
【问题描述】:

不知道为什么,但这是我的代码 sn-p :

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 返回值作为 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

【问题讨论】:

    标签: python mocking pytest patch


    【解决方案1】:

    您需要修补正在测试的模块中的符号“A”,即“B”。

    当你做@mock.patch("full.path.A")时,应该是:

    @mock.patch("full.path.to.B.A")
    

    现在模块B 中的符号A 已用您的模拟进行修补。

    【讨论】:

    • 这样做时我得到&lt;class full.path.to.B &gt;does not have the attribute A
    • 如果您发布 A 和 B 所在的文件夹结构以及实际的导入/补丁可能会有所帮助
    【解决方案2】:

    你是不是缺少文件本身的名称?

    @mock.patch( 'namespacename.mylibname.stats_collector.mylibname_stats_collector.StatsCollector')
    

    【讨论】:

      【解决方案3】:

      我写这篇文章的时候你可能已经猜到了。

      经验法则:不要在定义它们的地方修补类或函数,而是在使用它们的地方修补它们。

      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) 
      

      这是错误的,因为你正在修补文件中的一个类,它的定义是在哪里给出的。

      A 在 B 类的文件 b 中使用。因此,您应该修补该类。

      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"
      

      使用类似的类比:为了在文件 mydb.py 的 Db 类中测试“begin_transaction”,您需要修补 mydb.py 文件中使用的 StatsCollector 类

      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"
      

      我希望这对网络中的人有所帮助

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-08
        • 1970-01-01
        • 2021-11-21
        相关资源
        最近更新 更多