编写仅测试函数是否被调用的单元测试是否有意义?

时间:2019-07-16 17:14:02

标签: python unit-testing testing

我有以下代码:

def task_completed(task):
    _update_status(task, TaskStatus.COMPLETED)
    successful_task(task)


def task_pending(task):
    _update_status(task, TaskStatus.PENDING)
    successful_task(task)


def task_canceled(task):
    _update_status(task, TaskStatus.CANCELED)
    process_task(task)


def successful_task(task):
    process_task(task)
    send_notification(task)


def process_task(task):
    assign_user(task)
    notify_user(task)
    cleanup(task)


def _update_status(task, status):
    task.status = status
    task.save(update_fields=['status'])

我编写了以下测试:

def test_task_completed(mocker, task):
    mock_successful_task = mocker.patch('services.successful_task')
    task_completed(task)

    assert task.status == TaskStatus.COMPLETED
    mock_successful_task.called_once_with(task)


def test_task_pending(mocker, task):
    mock_successful_task = mocker.patch('services.successful_task')
    task_pending(task)

    assert task.status == TaskStatus.PENDING
    mock_successful_task.called_once_with(task)


def test_task_canceled(mocker, task):
    mock_process_task = mocker.patch('services.process_task')
    task_pending(task)

    assert task.status == TaskStatus.CANCELED
    mock_process_task.called_once_with(task)


def test_successful_task(mocker, task):
    mock_process_task = mocker.patch('services.process_task')
    mock_send_notification = mocker.patch('notifications.send_notification')

    mock_process_task.called_once_with(task)
    mock_send_notification.called_once_with(task)


def test_process_task(mocker, task):
    mock_assign_user = mocker.patch('users.assign_user')
    mock_notify_user = mocker.patch('notifications.notify_user')
    mock_cleanup = mocker.patch('utils.cleanup')

    mock_assign_user.called_once_with(task)
    mock_notify_user.called_once_with(task)
    mock_cleanup.called_once_with(task)

您会看到test_successful_tasktest_process_task之类的一些测试只是测试是否调用了特定功能。

但是为此编写一个测试是否有意义,还是我理解有些错误并且我的单元测试很糟糕?我不知道另一种解决方案,我应该如何测试这些功能。

6 个答案:

答案 0 :(得分:4)

根据我的经验,此类测试非常脆弱,因为它们取决于实现细节。单元测试应仅与所测试方法的结果有关。理想情况下,这意味着针对返回值进行断言。如果有副作用,则可以断言那些副作用。但是然后您可能应该看看这些副作用,并找到不需要它们的其他解决方案。

答案 1 :(得分:2)

尽管确实有用途,但这并不是单元测试的确切目的。单元测试旨在通过​​测试功能和结果来提高代码质量-编写单元测试来测试所调用的每种方法的功能会更加有益。

话虽这么说,如果您有一个函数调用了另外4个函数,并且想检查它们是否在您的主要代码块中真正执行了,那么这很有意义。但是,您绝对也应该为子方法编写单元测试。

答案 2 :(得分:2)

白盒测试可用于检测某种回归或断言是否已采取特定措施。
例如,您可以验证在这种特殊情况下您没有与数据库进行交互,或者已正确调用了通知服务。

但是,缺点是更改代码时可能会更改测试,因为测试与实现紧密相关。
重构时可能会很痛苦,因为您还需要重构测试。您可能会忘记一个断言或一个步骤,然后创建一个带有回归的假阳性检验。

我只会在它有感觉并且需要它来断言具体情况时才使用它。

您可以在网络TDD上进行搜索:伦敦vs底特律。
您会发现有趣的东西。

答案 3 :(得分:1)

我会说不,它们没有用。

单元测试应该测试功能,这是一些输入,我称之为输入,这是我的结果,它符合我的期望吗?事情必须清晰可验证。

当您有一个验证方法已被调用的测试时,您真正拥有什么? 纯粹的不确定性。好的,已经调用了一个东西,但是它有什么用呢?您没有验证结果,调用的方法可以完成一百万次操作,并且您不知道它会做什么。

调用方法的代码是实现细节,并且您的单元测试不应该具有这种知识。

我们为什么要编写单元测试? -检查功能 -帮助重构

如果每次代码更改时都需要更改测试,那么您还没有真正完成进行单元测试的主要原因之一。

如果您的代码更改了,并且不再调用该方法,那又如何呢? 您现在必须去更改测试?更改为什么?如果您的下一个步骤是删除测试,那么为什么首先要这么做?

如果在接下来的6个月内需要其他人处理此问题怎么办?没有要检查的文档,看看为什么要进行测试以检查是否已调用某个方法?

最重要的是,这样的测试的值为零,并且所做的只是引入不确定性。

答案 4 :(得分:0)

是的,这很有道理。但是,我会看unittest.mock.Mock.assert_called_with

答案 5 :(得分:0)

以我的经验,是的。

设计测试时,您知道必须处理4个元素

  • 前提条件(上下文)
  • 输入
  • 输出
  • 后置条件(副作用)

我们都认为,如果被测函数的行为仅取决于输入和输出,则测试和编码会更容易,但是在某些情况下,这不会发生,尤其是当您的逻辑处理I / O和/时或它的目标是发布应用程序状态的突变。这意味着您的测试必须了解后置条件。但是什么可以确保我们满足后置条件?

选择此方法

public class UserService
{
    public void addUser(User toAdd)
}

此方法在数据库上添加一个用户;可以说,以一种更为优雅的方式将User添加到一个集合中,该集合由存储库语义抽象化。因此,该方法的副作用是调用了userRepository.save(User user)。您可以模拟此方法,并期望已使用给定参数调用了该方法,或者使测试失败

显然,只有在模拟了该方法的情况下,才能实现此目的,因此测试不受不受测试单元的行为的影响。

我认识到缺点是使测试变脆,但同时

    在tdd中,
  1. 会进行不调用模拟函数的失败测试,​​因此测试状态为“嘿,addUser依赖于UserRepository.save()!”如果您喜欢这种风格

  2. 如果从属函数接口发生更改,则测试将失败,但是我们不想经常更改接口,对吗?

  3. 在向您的方法添加依赖项之前,您需要三思而后行,这是编写更简洁代码的提示