单元测试简单的方法......一个健全的OOP / pythonic解决方案?

时间:2013-06-09 03:25:20

标签: python oop unit-testing

我正在尝试以良好的面向对象的方式设计和测试类似于以下的代码? (或者是一种pythonic方式?)

这是一个工厂类,决定一个人的姓名是长还是短:

class NameLengthEvaluator(object):
    def __init__(self, cutoff=10)
        self.cutoff = cutoff

    def evaluate(self, name):
        if len(self.name) > cutoff:
            return 'long'
        else:
            return 'short'

这是一个人类,对自己名字的长度有意见:

 class Person(object):
     def __init__(self, name=None, long_name_opinion=8):
         self.name = name

     def name_length_opinion(self):
         return 'My names is ' + \
                    NameLengthEvaluator(long_name_opinion).evaluate(self.name)

几个问题:

  • Person方法name_length_opinion()是否值得进行单元测试,如果是,那会是什么样的?
  • 一般来说,有没有一种很好的方法来测试具有完全外部功能的类的简单方法?

似乎对此方法的任何测试都只会重述其实现,并且测试只是存在以确认没有人触及代码。

(免责声明:代码未经测试,我是python的新手)

3 个答案:

答案 0 :(得分:2)

单元测试

  

Person方法n​​ame_length_opinion()是否值得进行单元测试?如果是,那么它会是什么样子?

你想确保它做你认为它做的事情并确保它未来不会破裂吗?如果是,请为其编写单元测试。

  

并且测试将存在以确认没有人触及代码

单元测试更多的是确保一个类符合它指定的合同。你不必为所有东西编写单元测试,但如果它是一个简单的方法,它应该是一个简单的单元测试。

重复

  

似乎对此方法的任何测试都只会重述其实现

你不应该重复算法,你应该使用用例。例如,截止值为NameLengthEvaluator的{​​{1}}应该是短名称:

  • 乔治
  • 玛丽

这些名字很长:

  • MackTheKnife
  • JackTheRipper

因此,您应该验证该方法是否正确报告了这些名称的简短性。您还应该测试截止值为10的{​​{1}}报告NameLengthEvaluator为短,其他报告为4

Throwaway Code?

如果您曾编写过一个类,然后编写了一个只运行该类的main方法,以确保它能够执行它应该执行的操作(然后当您移动到另一个类时,将该主方法抛弃) ,你已经写过单元测试了。但是不要扔掉,保存它并将其转换为单元测试,以便将来你可以确保你没有破坏任何东西。

外部代码

  

通常,有一种很好的方法来测试具有完全外部功能的类的简单方法

好吧,如果它完全是外部的那么为什么它是该类的方法呢?通常,您至少具有可以测试的某些逻辑。在这种情况下,您可以测试Mary在正确的情况下返回name_length_opinionMy names is long

答案 1 :(得分:1)

这实际上取决于该代码的生命周期。很明显,在当前状态下,该方法显然是正确的,并且单元测试更多地是它应该如何表现的规范。如果您打算在将来进行更改(例如,以某种方式重新实现NameLengthEvaluator),那么进行单元测试很有用,因为运行测试会捕获任何回归。但在这种情况下,您似乎不太可能进行任何更改,因此测试可能过多(尽管进行了良好的健全性检查)。

答案 2 :(得分:0)

通常你会在这里使用模拟。你可以创建一个模拟NameLengthEvaluator,它返回一个记录了它与之连接的对象的对象,当name_length_opinion返回时,你会检查以确保它被使用并与正确的东西连接。 / p>

例如,使用unittest.mock

from unittest.mock import MagicMock, patch

@patch('your_module.NameLengthEvaluator', autospec=True)
def test_person_name_length_opinion(NameLengthEvaluator):
    expected_result = object()
    opinion = MagicMock(name='opinion')
    opinion.__radd__.return_value = expected_result
    name_length_evaluator = MagicMock(name='name_length_evaluator')
    name_length_evaluator.evaluate.return_value = opinion
    NameLengthEvaluator.return_value = name_length_evaluator

    name = object()
    length_limit = object()

    person = Person(name, long_name_opinion=length_limit)
    result = person.name_length_opinion()

    NameLengthEvaluator.assert_called_with(length_limit)
    name_length_evaluator.evaluate.assert_called_with(name)
    opinion.__radd__.assert_called_with('My names is ')
    assert result is expected_result

然而,由于这个方法很简单,我不确定你在乎那么多。