【问题标题】:How to avoid brittle tests in a function that has configuration options?如何避免在具有配置选项的函数中进行脆弱测试?
【发布时间】:2021-09-17 13:27:35
【问题描述】:

问题陈述

我正在尝试为一个函数构建单元测试,该函数对其接收的输入执行一系列算术运算。这些操作是在函数之外配置的(在这种情况下是在一个包常量中)。

我的问题是 我可以编写应该​​通过的测试,但是如果外部配置发生更改,测试将会中断,或者测试将主要是要测试的函数中代码的重复。强>

我能想到的编写测试的两种方式是:

  1. “假定”特定配置的测试。问题是测试很脆弱,如果我更改配置就会停止工作。 (参见下面的 test_example1)

    • 假设某些操作并应用于输入,创建预期结果
    • 调用函数进行测试
    • 将预期结果与要测试的函数返回的实际结果进行比较
  2. 我可以构建一个使用配置来计算预期结果的测试。问题是,首先,测试依赖于函数外部的配置常量,其次,测试的代码与测试的代码非常相似,感觉不对。 (参见下面的 test_example2)

    • 根据配置常量创建处理输入的预期结果
    • 调用函数进行测试
    • 将预期结果与要测试的函数返回的实际结果进行比较

您能帮我找出对执行外部配置的操作的函数进行单元测试的正确方法吗?

示例代码

import unittest
import operator

OPS = {'+':operator.add,
       '-':operator.sub}

DIRECTIVES = {'calculated_field':[('+','field1'),
                                  ('+','field2')]}

def example(input_dict):
    output_dict = {}
    
    for item,calcs in DIRECTIVES.items():
        output_dict[item] = 0
        for operation, field in calcs:
            output_dict[item] = OPS[operation](output_dict[item],input_dict[field])
        
    return output_dict    

class TestExample(unittest.TestCase):

    item1  = 'field1'
    value1 = 10
    item2  = 'field2'
    value2 = 20
    item3  = 'field3'
    value3 = 5    

    def setUp(self):
        self.input_dict = {self.item1:self.value1,
                           self.item2:self.value2,
                           self.item3:self.value3}
    
    def test_example_option1(self):
        expected_result = {'calculated_field':self.value1+self.value2}
        
        actual_result = example(self.input_dict)
        
        self.assertDictEqual(expected_result,actual_result)
        
    def test_example_option2(self):
        expected_result = {}

        for item,calcs in DIRECTIVES.items():
            expected_result[item] = 0
            for operation, field in calcs:
                expected_result[item] = OPS[operation](expected_result[item],self.input_dict[field])
                
        actual_result = example(self.input_dict)
        
        self.assertDictEqual(expected_result,actual_result)

【问题讨论】:

    标签: python unit-testing python-unittest


    【解决方案1】:

    这有点见仁见智,但不是严格依赖全局变量,我可能会让你的函数(example(),在这种情况下,我假设)允许将可选覆盖传递给它的外部数据依赖。

    例如

    def example(ops=OPS, directives=DIRECTIVES):
        ...
    

    这有两个优点:为了进行测试,您可以为此数据传递一些虚拟值(可能会比真实数据更小更简单),然后测试已知正确的输出数据。

    另一个优点是它使您的代码总体上更具可扩展性。

    如果您不想这样做,另一个示例(因为您正在使用 unittest 模块)将使用 unittest.mock.patch

    在这种情况下,您可以将测试编写为:

    class TestExample(...):
        @patch('yourmodule.OPS', TEST_OPS)
        @patch('yourmodule.DIRECTIVES', TEST_DIRECTIVES)
        def text_example_option(self):
            # call example() and test against a known--good value
            # given TEST_OPS and TEST_DIRECTIVES
    

    这本质上是一样的,但提供了一种方法来针对(临时分配的)新配置测试您的功能,而无需更改默认值。

    【讨论】:

    • 是的,正如您所期望的,绝对不要编写一个恰好重新实现被测函数的测试。那会破坏目的。
    猜你喜欢
    • 1970-01-01
    • 2013-02-26
    • 2015-12-15
    • 2010-10-02
    • 1970-01-01
    • 2014-10-21
    • 1970-01-01
    • 2012-01-30
    • 2022-10-22
    相关资源
    最近更新 更多