模拟 __init__() 进行单元测试

问题描述 投票:0回答:7

我有课:

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__
方法,但我没有在 documentation 中找到任何看起来有用的东西。

python sqlite unit-testing mocking
7个回答
73
投票

您可以简单地对数据库类进行子类化并对其进行测试,而不是进行模拟:

class TestingDatabaseThing(DatabaseThing):
     def __init__(self, connection):
          self.connection = connection

并实例化 that 类而不是

DatabaseThing
进行测试。方法仍然相同,行为仍然相同,但现在所有使用
self.connection
的方法都使用您测试提供的连接。


23
投票

如果您想在初始化类时返回模拟,请模拟 __new__ 方法,而不是 init

new创建新实例并init初始化它,但只能返回None。

如果您模拟 new,它可以返回一个模拟,您可以断言以模拟测试中的实例创建。

@mock.patch('Car.__new__')
def test_create_car(self, mock_Car):
    mock_inst = mock.MagickMock()
    mock_Car.return_value = mock_inst

    create_car()

    # Assert class was called
    mock_Car.assert_called_once()
    # __new__ is called with actual class as first arg
    mock_Car.assert_called_with(Car) 

    # Assert instance method is called as expected
    mock_inst.set_miles.assert_called_with(0)

13
投票

您应该使用

mock
包来模拟类的方法
__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


4
投票

方法一:子类化

请参考@Martijn Pieters的回答。

方法2:控制反转

一个长期的解决方案是让客户端创建连接并将其交给

DatabaseThing
使用。使用单一职责原则,我认为
DatabaseThing
不应该负责在这种情况下建立连接。

这种技术减少了依赖性并为您提供了更多的灵活性,例如您可以设置一个连接池,并为

DatabaseThing
的每个新实例提供池中的连接,而无需更改
DatabaseThing
中的任何内容。


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)      

这样做的好处是可以将代码与正在使用的数据库技术稍微解耦。


1
投票

考虑到

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

嘲笑的例子:

模块A.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')

测试.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')

0
投票

@mock.patch('汽车.new') def test_create_car(self,mock_Car): 模拟安装 = 模拟.MagickMock() 模拟_Car.return_value = 模拟_inst

create_car()

# Assert class was called
mock_Car.assert_called_once()
# __new__ is called with actual class as first arg
mock_Car.assert_called_with(Car) 

# Assert instance method is called as expected
mock_inst.set_miles.assert_called_with(0)

考虑到 ibm_db_dbi 和 lite 共享相同的接口,这应该可以解决问题:

导入模拟 将 sqlite3 导入为 lite

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() I have a class:

类DatabaseThing(): def init(self, dbName, 用户, 密码): self.connection = ibm_db_dbi.connect(dbName, 用户, 密码) 我想测试这个类,但使用测试数据库。所以在我的测试课中我正在做这样的事情:

导入 sqlite3 作为精简版 导入单元测试 从 DatabaseThing 导入 *

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 方法,但我没有在文档中找到任何看起来有用的东西。

pythonsqlite单元测试模拟

© www.soinside.com 2019 - 2024. All rights reserved.