Python单元测试:如何对包含数据库操作的模块进行单元测试?
Python Unit Test : How to unit test the module which contains database operations?
我正在使用 pymysql 客户端库连接到真实数据库。我在模块中有一个函数,我在其中使用 pymysql 连接到数据库,并且只执行数据库插入 operations.How 以在 python 中对该函数进行单元测试,而不会触及真实数据库?
import pymysql
def connectDB(self):
# Connect to the database
connection = pymysql.connect(host='localhost',
user='user',
password='passwd',
db='db')
try:
with connection.cursor() as cursor:
# Create a new record
sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)"
cursor.execute(sql, ('newuser@some.com', 'newpassword'))
connection.commit()
我的 python 版本是 2.7。
您需要一系列假数据库,称为存根,其中 return 硬编码值。在测试期间,使用这些存根代替真实数据库。我不熟悉 Python,但在 C++ 中执行此操作的一种方法是让您的对象接收数据库作为构造函数参数。在生产代码中,您使用真实的数据库参数,在测试存根中。这是可以做到的,因为构造函数需要一个指向公共基数 class 的指针。即使它不是为 Python 编写的,我还是建议阅读 Roy Osherove 的第一章:单元测试的艺术。这本书清楚地解释了为什么这些假数据库是存根而不是模拟。
您刚刚重新发现了为什么测试很重要的最令人信服的原因之一:它告诉您什么时候您的设计不好。
换句话说,可测性是 质量 的一个很好的一阶代理。考虑以下因素:
class DB(object):
def __init__(self, **credentials):
self._connect = partial(pymysql.connect, **credentials)
def query(self, q_str, params):
with self._connect as conn:
with conn.cursor() as cur:
cur.execute(q_str, params)
return cur.fetchall()
# now for usage
test_credentials = {
# use credentials to a fake database
}
test_db = DB(**test_credentials)
test_db.query(write_query, list_of_fake_params)
results = test_db.query(read_query)
assert results = what_the_results_should_be
如果您使用多个数据库,您可以使用多态性或根据 API 相似性使特定数据库成为对象的构造函数参数。
您可以使用 patch
,像这样:
from unittest.mock import patch, MagicMock
@patch('mypackage.mymodule.pymysql')
def test(self, mock_sql):
self.assertIs(mypackage.mymodule.pymysql, mock_sql)
conn = Mock()
mock_sql.connect.return_value = conn
cursor = MagicMock()
mock_result = MagicMock()
cursor.__enter__.return_value = mock_result
cursor.__exit___ = MagicMock()
conn.cursor.return_value = cursor
connectDB()
mock_sql.connect.assert_called_with(host='localhost',
user='user',
password='passwd',
db='db')
mock_result.execute.assert_called_with("sql request", ("user", "pass"))
我正在使用 pymysql 客户端库连接到真实数据库。我在模块中有一个函数,我在其中使用 pymysql 连接到数据库,并且只执行数据库插入 operations.How 以在 python 中对该函数进行单元测试,而不会触及真实数据库?
import pymysql
def connectDB(self):
# Connect to the database
connection = pymysql.connect(host='localhost',
user='user',
password='passwd',
db='db')
try:
with connection.cursor() as cursor:
# Create a new record
sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)"
cursor.execute(sql, ('newuser@some.com', 'newpassword'))
connection.commit()
我的 python 版本是 2.7。
您需要一系列假数据库,称为存根,其中 return 硬编码值。在测试期间,使用这些存根代替真实数据库。我不熟悉 Python,但在 C++ 中执行此操作的一种方法是让您的对象接收数据库作为构造函数参数。在生产代码中,您使用真实的数据库参数,在测试存根中。这是可以做到的,因为构造函数需要一个指向公共基数 class 的指针。即使它不是为 Python 编写的,我还是建议阅读 Roy Osherove 的第一章:单元测试的艺术。这本书清楚地解释了为什么这些假数据库是存根而不是模拟。
您刚刚重新发现了为什么测试很重要的最令人信服的原因之一:它告诉您什么时候您的设计不好。
换句话说,可测性是 质量 的一个很好的一阶代理。考虑以下因素:
class DB(object):
def __init__(self, **credentials):
self._connect = partial(pymysql.connect, **credentials)
def query(self, q_str, params):
with self._connect as conn:
with conn.cursor() as cur:
cur.execute(q_str, params)
return cur.fetchall()
# now for usage
test_credentials = {
# use credentials to a fake database
}
test_db = DB(**test_credentials)
test_db.query(write_query, list_of_fake_params)
results = test_db.query(read_query)
assert results = what_the_results_should_be
如果您使用多个数据库,您可以使用多态性或根据 API 相似性使特定数据库成为对象的构造函数参数。
您可以使用 patch
,像这样:
from unittest.mock import patch, MagicMock
@patch('mypackage.mymodule.pymysql')
def test(self, mock_sql):
self.assertIs(mypackage.mymodule.pymysql, mock_sql)
conn = Mock()
mock_sql.connect.return_value = conn
cursor = MagicMock()
mock_result = MagicMock()
cursor.__enter__.return_value = mock_result
cursor.__exit___ = MagicMock()
conn.cursor.return_value = cursor
connectDB()
mock_sql.connect.assert_called_with(host='localhost',
user='user',
password='passwd',
db='db')
mock_result.execute.assert_called_with("sql request", ("user", "pass"))