pytest — исправление класса не работает, вместо этого вызывает класс

#python #mocking #pytest #patch

#python #издевательство #pytest #исправление

Вопрос:

Не уверен, почему, но вот мой фрагмент кода :

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
  

Ответ №1:

Разве вам не хватает имени самого файла?

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

Ответ №2:

Вам нужно исправить символ «A» в тестируемом модуле, то есть «B».

Когда вы это сделаете @mock.patch("full.path.A") , это должно быть:

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

Теперь символ A в модуле B исправлен вашим макетом.

Комментарии:

1. Я получаю <class full.path.to.B >does not have the attribute A при этом

2. может быть, это поможет, если вы опубликуете структуру папок, в которой находятся A и B, и фактический импорт / исправления

Ответ №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"
  

используя аналогичную логику: для тестирования «begin_transaction» в базе данных класса файла mydb.py , вам нужно исправить класс StatsCollector, используемый в mydb.py файл

 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"
  

Я надеюсь, что это поможет кому-то в сети