Подтвердить что ты не робот

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__ в тестовом классе, но я не нашел ничего полезного в документации .

4b9b3361

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