【问题标题】:Python unit test mock. ValueError: The truth value of a DataFrame is ambiguousPython 单元测试模拟。 ValueError:DataFrame 的真值不明确
【发布时间】:2017-11-22 06:27:45
【问题描述】:

我正在为我的一个 python 2.7 方法编写一个单元测试用例。

在我的测试方法中,有一个方法调用将带有字符串键和 panadas 数据框的字典作为该键的值。

我想为此方法编写一个交互测试,以检查它是否使用正确的字典在内部调用该方法

def MethodUnderTest(self):
    #some code here
    externalMethod(dictionary_of_string_dataframe)
    #some code here

在单元测试中,我确实编写了我的断言来测试这样的交互

mock_externalClass.externalMethod.assert_called_once_with(dictionary_of_string_dataframe) 

我创建 dictionary_of_string_dataframe 的方式与在实际方法中创建的方式完全相同。事实上,我复制了在测试代码中执行此操作的辅助方法,只是为了确保两个字典相同。我什至在 python 控制台上调试测试方法时打印了这两个字典,它们看起来完全一样。

我使用 @patch 装饰器修补外部类,一切正常。

问题是在上面提到的断言语句中,我得到以下错误:

 mock_externalClass.externalMethod.assert_called_once_with(dictionary_of_string_dataframe)
  File "C:\Python27\lib\site-packages\mock\mock.py", line 948, in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
  File "C:\Python27\lib\site-packages\mock\mock.py", line 935, in assert_called_with
    if expected != actual:
  File "C:\Python27\lib\site-packages\mock\mock.py", line 2200, in __ne__
    return not self.__eq__(other)
  File "C:\Python27\lib\site-packages\mock\mock.py", line 2196, in __eq__
    return (other_args, other_kwargs) == (self_args, self_kwargs)
  File "C:\Python27\lib\site-packages\pandas\core\generic.py", line 953, in __nonzero__
    .format(self.__class__.__name__))
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

我确实搜索了 valueError 但没有太多帮助。 谁能告诉我这里发生了什么?

我确实检查了以下问题,但没有帮助

ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()

【问题讨论】:

  • 所以澄清一下:您有两个将字符串映射到 pandas 数据帧的字典,并且您想检查它们是否相等。您正在自己编写单元测试,即使使用相同的函数和参数创建的两个字典,您也会收到上述错误。
  • 当我运行测试用例时,它会在被测方法内部使用字符串:数据帧的字典来命中外部方法。在测试用例本身中,我使用问题中给出的语句测试此交互,并使用与被测方法中使用的相同帮助方法在 mock_externalClass.externalMethod.assert_call_once_with 的测试方法中创建 dict 值。但是,我得到一个错误。
  • 恕我直言,您采取了错误的方法。您基本上是在尝试强制您的代码通过测试,而不是让测试强制执行所需的结果。如果更改了外部方法的签名,您的测试将不会检测到代码破坏性更改,但是只有当您修复代码时,您的测试才会失败。我建议您将派生输入并调用外部方法的代码解耦到它自己的方法中并单独测试该方法,分离您的关注点。

标签: python pandas python-unittest python-unittest.mock


【解决方案1】:

我在尝试测试我编写的函数是否被某个预处理数据帧调用时也遇到了这个问题,我使用mock的call_args属性和pandastesting.assert_frame_equal解决了这个问题。

在我的例子中,我想断言作为第二个参数传递给我下面的函数score_function 的数据帧的值,该函数由更高级别的函数run_scoring 调用。因此,首先我使用[0] 检索了对模拟的方法调用的*args 部分,然后我使用[1] 获得了我的第二个位置参数(这是我想要断言的值)。

然后我可以使用pd.testing.assert_frame_equal 断言这个数据帧的值。

from unittest.mock import Mock
import pandas as pd

import my_code

...

score_function_mocked = Mock()
my_code.score_function = score_function_mocked
my_code.run_scoring()

pd.testing.assert_frame_equal(
    # [0] = *args, [0][1] = second positional arg to my function
    score_function_mocked.call_args[0][1],
    pd.DataFrame({
        'new_york': [1, 0],
        'chicago': [0, 1],
        'austin': [0, 0]
    })

)

【讨论】:

    【解决方案2】:

    这是因为 unittest.mock 使用 ==!= 在输入值之间进行比较。但是,pandas 数据帧不能进行类似的比较,而必须使用 DataFrames 的.equals 方法。

    https://github.com/testing-cabal/mock/blob/master/mock/mock.py

    一种可能的解决方法是编写您自己的单元测试,它遍历字典并使用.equals 方法在数据帧之间进行比较。

    另一个是覆盖 pandas 数据帧的 __equals__ 方法,这样当 mock 在它们之间进行比较时,它将使用正确的方法。

    【讨论】:

      【解决方案3】:

      这是我根据原始问题解决的方法。这借用了 sfogle 的回答中的一些想法,但它允许您通常测试调用的函数,无论 args 是否为 DataFrames。

      import unittest
      from unittest.mock import patch
      import pandas as pd
      
      def external_method(df):
          return df
      
      def function_to_test(df):
          external_method(df)
          return df
      
      class MyTest(unittest.TestCase):
      
          def test_the_function_to_test(self):
              my_test_df = pd.DataFrame({"a": [1, 2, 3, 4]})    
              with patch(__name__ + ".external_method") as mock_external_method:        
                  function_to_test(my_test_df)
                  mock_external_method.assert_called_once()
                  args, kwargs = mock_external_method.call_args
                  self.assertTrue(args[0].equals(my_test_df))
      

      【讨论】:

        【解决方案4】:

        我通过一个 SAME_DF 类解决了这个问题,类似于 mock 的 ANY 类

        class SAME_DF:
            def __init__(self, df: pd.DataFrame):
                self.df = df
        
            def __eq__(self, other):
                return isinstance(other, pd.DataFrame) and other.equals(self.df)
         
        def test_called_with_df():
            ....
            mock.method.assert_called_once_with(SAME_DF(pd.DataFrame({
                'name': ['Eric', 'Yoav'],
                'age': [28, 34]
            })))
        

        【讨论】:

        • 这很好地解决了我的问题,但是当测试失败时添加 repr 函数会有所帮助。 def __repr__(self):return repr(self.df)
        • 完美解决方案!发送。
        猜你喜欢
        • 2018-07-06
        • 2021-01-19
        • 1970-01-01
        • 1970-01-01
        • 2020-11-14
        • 2021-11-28
        • 2021-07-31
        • 1970-01-01
        • 2022-11-29
        相关资源
        最近更新 更多