【问题标题】:Python Unit Test : How to unit test the module which contains database operations?Python 单元测试:如何对包含数据库操作的模块进行单元测试?
【发布时间】:2018-04-19 23:22:43
【问题描述】:

我正在使用 pymysql 客户端库连接到真实的数据库。我在模块中有一个函数,我使用 pymysql 连接到数据库并且只执行数据库插入操作。如何在 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。

【问题讨论】:

  • 模拟数据库将是一个选项https://docs.python.org/3/library/unittest.mock.html
  • 快速评论:也许connectDB 可能不是执行查询的方法的最佳名称:)

标签: python python-2.7 unit-testing python-unittest python-unittest.mock


【解决方案1】:

您需要一系列假数据库,称为存根,它们返回硬编码值。在测试期间,这些存根被用来代替真实的数据库。我不熟悉 Python,但在 C++ 中执行此操作的一种方法是让您的对象接收数据库作为构造函数参数。在生产代码中,您使用真实的数据库参数,在测试存根中。可以这样做是因为构造函数需要一个指向公共基类的指针。即使它不是为 Python 编写的,我也建议阅读 Roy Osherove 的第一章:单元测试的艺术。这本书清楚地解释了为什么这些假数据库是存根而不是模拟。

【讨论】:

    【解决方案2】:

    您刚刚重新发现了为什么测试很重要的最令人信服的原因之一:它会告诉您什么时候设计不好。

    换句话说,可测试性是质量的良好一阶代理。考虑以下几点:

    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 相似性将特定数据库作为对象的构造函数参数。

    【讨论】:

      【解决方案3】:

      你可以使用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"))
      

      【讨论】:

      • 我的python版本是2.7。 “unittest.mock”从 Python 3.3 开始可用。
      • 你可以使用包 Mock for python 2.7:pypi.python.org/pypi/mock
      猜你喜欢
      • 2021-06-22
      • 1970-01-01
      • 1970-01-01
      • 2012-01-08
      • 2014-04-19
      • 2011-07-10
      • 1970-01-01
      • 2019-11-02
      • 1970-01-01
      相关资源
      最近更新 更多