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



我有以下代码:

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 个月后处理这个问题怎么办?没有文档可以检查,看看为什么有一个测试检查已经调用了方法?

最重要的是,像这样的测试具有零值,它所做的只是引入不确定性。

白盒测试可用于检测某些回归或断言已执行特定操作。
例如,您可以验证在此特定情况下是否未与数据库交互,或者是否已正确调用通知服务。

但是,缺点是更改代码时可能会更改测试,因为测试与实现密切相关。这
在重构时可能会很痛苦,因为您还需要重构测试。您可以忘记断言或步骤,并使用回归创建误报测试。

只有当它有意义并且您需要它来断言正在发生的事情时,我才会使用它。

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

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

话虽如此,如果你有一个函数调用了另外 4 个函数,并且你想检查它们是否真的在你的主代码块中执行,那么这是有道理的。但是你肯定也应该为你的子方法编写单元测试。

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

根据我的经验,是的。

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

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

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

选择此方法

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

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

实际上,只有当该方法被模拟时,才能实现这一点,因此测试不会受到未被测试单元行为的影响。

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

  1. 在TDD中,它会进行不调用模拟函数的失败测试,因此测试状态为"嘿,addUser依赖于UserRepository.save()!

  2. 如果依赖函数接口发生变化,测试就会被破坏,但我们不想经常更改接口,对吗?

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

相关内容

最新更新