Mocking __init __() для унитаза
У меня есть класс:
class DatabaseThing():
def __init__(self, dbName, user, password):
self.connection = ibm_db_dbi.connect(dbName, user, password)
Я хочу протестировать этот класс, но с тестовой базой данных. Поэтому в моем тестовом классе я что-то вроде этого:
import sqlite3 as lite
import unittest
from DatabaseThing import *
class DatabaseThingTestCase(unittest.TestCase):
def setUp(self):
self.connection = lite.connect(":memory:")
self.cur = self.connection.cursor()
self.cur.executescript ('''CREATE TABLE APPLE (VERSION INT, AMNT SMALLINT);
INSERT INTO APPLE VALUES(16,0);
INSERT INTO APPLE VALUES(17,5);
INSERT INTO APPLE VALUES(18,1);
INSERT INTO APPLE VALUES(19,15);
INSERT INTO APPLE VALUES(20,20);
INSERT INTO APPLE VALUES(21,25);''')
Как я могу использовать это соединение, чем соединение из класса, который я хочу проверить? Значение используется для соединения из setUp(self)
вместо соединения из DatabaseThing
. Я не могу проверить функции без создания экземпляра класса. Я хочу как-то издеваться над методом __init__
в тестовом классе, но я не нашел ничего полезного в документации .
Ответы
Ответ 1
Вместо издевательств вы можете просто подклассифицировать класс базы данных и проверить на это:
class TestingDatabaseThing(DatabaseThing):
def __init__(self, connection):
self.connection = connection
и создайте экземпляр этого класса вместо DatabaseThing
для ваших тестов. Методы все те же, поведение по-прежнему будет одинаковым, но теперь все методы, использующие self.connection
, вместо этого используют ваше тестовое соединение.
Ответ 2
Вместо того, чтобы пытаться заменить функцию init, которая является грязной, хрупкой и взломанной, попробуйте передать функцию в свой конструктор базы данных, как показано ниже:
# Test connection creation
def connect_lite(dbName=None, user=None, password=None):
connection = lite.connect(":memory:")
cur = self.connection.cursor()
cur.executescript ('''CREATE TABLE APPLE (VERSION INT, AMNT SMALLINT);
INSERT INTO APPLE VALUES(16,0);
INSERT INTO APPLE VALUES(17,5);
INSERT INTO APPLE VALUES(18,1);
INSERT INTO APPLE VALUES(19,15);
INSERT INTO APPLE VALUES(20,20);
INSERT INTO APPLE VALUES(21,25);''')
return cur
# Production connection creation
def connect_ibm(dbName, user, password):
return ibm_db_dbi.connect(dbName, user, password)
# Your DatabaseThing becomes:
class DatabaseThing():
def __init__(self, connect, dbName, user, password):
self.connection = connect(dbName, user, password)
# In your test create a DatabaseThing
t = DatabaseThing(connect_lite, dbName, user, password)
# In your production code create a DatabaseThing
p = DatabaseThing(connect_ibm, dbName, user, password)
Это имеет побочное преимущество в том, что вы немного развязываете свой код из используемой вами технологии баз данных.
Ответ 3
Способ 1: Подкласс
Обратитесь к ответу @Martijn Pieters.
Способ 2. Инверсия управления
Долгосрочное решение заключается в том, чтобы клиент создал соединение и передал его DatabaseThing
для использования. Используя принцип единой ответственности, я не думаю, что DatabaseThing
должен нести ответственность за соединение в этом случае.
Этот метод сокращает зависимости и дает вам большую гибкость, например. вы можете настроить пул соединений и дать каждому новому экземпляру DatabaseThing
соединение из пула, не изменяя ничего в DatabaseThing
.
Ответ 4
Учитывая, что ibm_db_dbi
и lite
используют одни и те же интерфейсы, это должно сделать трюк:
import mock
import sqlite3 as lite
class DatabaseThingTestCase(unittest.TestCase):
def setUp(self):
self.patch = mock.patch('module_with_DatabaseThing_definition.ibm_db_dbi', lite)
self.patch.start()
def tearDown(self):
self.patch.stop()
т.е. ваш файл DatabaseThing
имеет имя database/things.py
, тогда патч будет выглядеть как database.things.ibm_db_dbi
.
Пример издевательств:
moduleA.py
def connection(*args):
print 'The original connection. My args', args
moduleB.py
def connection(*args):
print 'The mocked connection. My args', args
myClass.py
import moduleA
class MyClass(object):
def __init__(self):
self.connection = moduleA.connection('Test', 'Connection')
test.py
import mock
import moduleB
from myClass import MyClass
def regular_call():
MyClass()
def mocked_call():
def wrapped_connection(*args):
return moduleB.connection(':memory:')
my_mock = mock.Mock(wraps=moduleB)
my_mock.connection = wrapped_connection
with mock.patch('myClass.moduleA', my_mock):
MyClass()
MyClass()
regular_call()
mocked_call()
Запуск test.py дает:
The original connection. My args ('Test', 'Connection')
The mocked connection. My args (':memory:',)
The original connection. My args ('Test', 'Connection')
Ответ 5
Вы должны использовать mock
package, чтобы смоделировать метод __init__
класса:
from mock import patch
def test_database_thing(self):
def __init__(self, dbName, user, password):
# do something else
with patch.object(DatabaseThing, '__init__', __init__):
# assert something