我有以下代码:
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_task
和test_process_task
之类的一些测试只是测试是否调用了特定功能。
但是为此编写一个测试是否有意义,还是我理解有些错误并且我的单元测试很糟糕?我不知道另一种解决方案,我应该如何测试这些功能。
答案 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)。您可以模拟此方法,并期望已使用给定参数调用了该方法,或者使测试失败
显然,只有在模拟了该方法的情况下,才能实现此目的,因此测试不受不受测试单元的行为的影响。
我认识到缺点是使测试变脆,但同时
会进行不调用模拟函数的失败测试,因此测试状态为“嘿,addUser依赖于UserRepository.save()!”如果您喜欢这种风格
如果从属函数接口发生更改,则测试将失败,但是我们不想经常更改接口,对吗?
在向您的方法添加依赖项之前,您需要三思而后行,这是编写更简洁代码的提示