代码之家  ›  专栏  ›  技术社区  ›  user2301

Python单元测试:如何对包含数据库操作的模块进行单元测试?

  •  16
  • user2301  · 技术社区  · 7 年前

    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。

    3 回复  |  直到 7 年前
        1
  •  16
  •   uwevil    7 年前

    您可以使用 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"))
    
        2
  •  1
  •   robert    7 年前

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

        3
  •  1
  •   Jared Smith    4 年前

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

    换言之,可测试性是一个很好的一阶代理 质量 . 考虑以下因素:

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