【问题标题】:Testing database read/write plus analytics script in Python在 Python 中测试数据库读/写和分析脚本
【发布时间】:2017-06-01 18:23:37
【问题描述】:

我经常需要编写一个命令行脚本,该脚本将从数据库中读取数据,执行一些分析,然后将结果写回数据库。我解耦和创建单独数据层的努力通常是编写脚本load.pywrite.pydo_analytics.py,其中 load 和 write 进行数据库交互,do_analytics.py 文件是这样的:

import load
import write

def batch_classify(model_filepath='my_model.pkl'):
    with open(model_filepath, 'rb') as infile:
        model = pickle.load(infile)

    data_loader = load.DataLoader()
    data_loader.load_data()
    data_loader.clean_data()
    data = data_loader.data
    # Maybe do some more manipulations here...
    output = model.transform(data)

    data_writer = write.DataWriter()
    data_writer.write_data(output)

if __name__ == "__main__":
    # maybe would have some command line options here to pass to batch_classify
    batch_classify()   

我现在想测试一些固定的数据集,并确保分类(输出)结果符合我的预期。我现在不需要测试实际的数据库连接,所以根据一些研究,我想我想像this post 那样进行模拟,但我不确定应该模拟什么级别的对象,如何正确重构为实际测试一旦我有了模拟对象,如果这甚至是最好的方法。之前出现这种情况时,我四处寻找可以通过实际数据库中的小型固定测试表工作的解决方案,但它从来都不是优雅或干净的代码。

【问题讨论】:

    标签: python unit-testing testing mocking integration-testing


    【解决方案1】:

    在你的情况下,我会有 4 个文件。

    database_provider.py

    class DatabaseProvider(object):
        def get_data(self):
            return db.get() # Get your data
    
        def set_data(self, data):
            db.set(data) # update your data
    

    analytic_manager.py

    class AnalyticManager(object):
        def __init__(self):
            self.database_provider = DatabaseProvider()
    
        def process(self, arguments):
            # Get data from DB
            data = self.database_provider.get_data()
    
            # Do your logic here
            data = self.clean(data)
            data = self.transform(data)
    
            # Save in DB
            self.database_provider.set_data(data)
    
        def clean(self, data):
            # do cleaning
            return cleaned_data
    
        def transform(self, data):
            # do transform
            return transformed_data
    

    ma​​in.py

    if __name__ == "__main__":
        arguments = whatever
        manager = AnalyticManager(arguments)
        manager.process(arguments)
    

    test_analytic_manager.py

    import unittest
    from mock import Mock, patch
    
    class TestAnalyticManager(unittest.TestCase):
    
    
        @patch("database_provider.DatabaseProvider.get_data")
        @patch("database_provider.DatabaseProvider.set_data")
        def test_process_should_clean_and_transform_data(self, mock_set_data, mock_get_data):
            # Arranges
            arguments = whatever
            manager = AnalyticManager(arguments)
    
            mock_get_data.return_value = ["data from DB", "data2 from DB"]
    
            expected_data = ["cleaned and transformed data1", "cleaned and transformed data2"]
    
            # Acts
            manager.process(arguments)
    
            # Asserts
            mock_set_data.assert_called_once_with(expected_data)
    

    您现在可以根据需要模拟您的提供程序。 最重要的是在管理器内部而不是提供者内部执行所有逻辑。 您的 db_provider 应该只与您的数据库进行交互并将接收到的数据映射到您的 python 对象。

    Manager 和 Provider 层对于能够模拟非常重要。 单独的职责将避免出现意大利面条式代码。

    【讨论】:

    • 谢谢,这看起来不错。澄清一下,最佳实践是选择在实例化时将 DatabaseProvider 对象(无论是真实的还是模拟的)传递给 AnalyticManager?
    • 在 Python 中,您不必在单元测试中传递模拟选项。请参阅 test_analytic_manager.py。您可以在实现 DB 提供程序之前使用此类测试您的逻辑,
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多