应用层测试

Application Layer Testing

我想测试 class 应用层,不确定哪个更好。我有 class 领域模型是任务,例如

class Task {
   private Clock clock;

   public Guid Id {get; private set;}
   public string Name {get; set;}
   public DateTime StartedDate {get; private set;}

   public Task(Guid id, string name, Clock pClock) {
       Id = id;
       Name = name;
       clock = pClock;
       StartedDate = clock.Now();
   }

}

interface Clock
{
    DateTime Now {get;}
}

所以我想测试一下 WorkManagementService 的 CreateTask 方法 class。我不明白我应该测试什么。假设,我写 test

TestCreateTaskShouldReturnTaskIdWasCreated() 
{
    Guid taskId = Guid.Empty;
    TaskRepository repository = Substitute.For<TaskRepository>();
    repsitory.Add(Arg.Do<Task>(taskArgument => taskId = taskArgument.Id));
    var service = new WorkManagementService(repository);

    var createdTaskId = service.CreateTask("task name");

    Assert.AreNotEqual(Guid.Empty, createdTaskId);
    Assert.AreEqual(taskId, createdTaskId);
}

所以,我不确定这是否是个好习惯。 CreateTask 方法使用 Task 构造函数来创建 Task 和 Clock 接口的一些实现,因此 WorkManagementService class 依赖于它们。有什么好方法吗?

如果这让您感到困惑,我深表歉意。

更新 我认为 CreateTask 方法的第一个实现可能如下。

 class WorkManegementService
{
    private TaskRepository taskRepository;

    public WorkManegementService(TaskRepository pTaskRepository)
    {
        taskRepository = pTaskRepository;
    }

    Guid CreateTask(string name)
    {
        var taskId = Guid.NewGuid();
        var task = new Task(taskId, name, new SystemClock());
        try 
        {
            taskRepository.Save(task);

            return taskId;
        }
        catch (...)
        {
            // some handling
        }
    }
}

在进一步实施过程中,我将添加 Task Owner 或类似内容。

您可以测试对象的创建和正确初始化,但这里没有要测试的实际逻辑。 如果您有意测试此任务在哪个上下文中使用,什么行为会更好。

老实说,没有必要对您的简单访问器和修改器进行单元测试。这是浪费时间,对任何人都没有帮助。测试创建的唯一原因是因为您希望对象处于特定状态...(例如游戏地图或银行帐户)。

**如果您显示 Create 方法代码,它可能会更好地概述您的方法应该做什么

[编辑]

您的测试可以验证您的保存方法是否已被调用...

repository.Received().Save(Arg.Any<Task>()); // Arg.Is if you prefer

更多here

[编辑2]

对于依赖项,您可以将任务的初始化完全委托给另一个 method/class,这样您的测试就可以完全可预测并与其他依赖项隔离。 从纯粹的角度来看,单元测试应该一次测试一件事并且失败的原因有限。如果您正在学习,我建议您严格应用此规则。

Guid CreateTask(string name)
    {
        var task = this.InitTask(name); // or factory.CreateTask(name) or you can have public function prop that you inject if you like functional way...
        try 
        {
            taskRepository.Save(task);

            return taskId;
        }
        catch (...)
        {
            // some handling
        }
    }

单元测试的主要目的是确保应用程序的业务逻辑按预期工作。您必须在示例中测试的唯一业务逻辑是:

  1. 确保方法调用 repository.Savetask 的内容正确
  2. 确保taskId正确
  3. 确保错误处理正常

至于 Task 对象,您可以考虑进行测试以确保构造函数正常工作,但更重要的是对 Clock class 进行单元测试以确保它是按预期工作。

这里你问:

I have tests for Clock and Task constructor, but it seems that correct execution of CreateTask test depends on Task and Clock classes. So the test implicitly covers 3 classes. Or do I misunderstand that?

ClockTask 构造函数测试是合适的,不需要在 CreateTask() 测试中重复。

为了汇总上述适当的指导,WorkManagementService 的正确测试如下:

  • 给定一个带有空参数的CreateTask()调用,然后确保按预期报告错误(例如,抛出一个 ArgumentNullException)
  • 给定一个带有良好参数的CreateTask()调用,然后确保使用正确的参数调用repository.Save()参数(通过使用模拟构造函数注入存储库)
  • 给定一个带有良好参数的CreateTask()调用一个失败的repository.Save()调用(抛出的异常) , then 确保 CreateTask() 按预期失败(例如,重新抛出异常、抛出新的外部异常或其他一些预期的错误处理)
  • 给定成功的repository.Save()调用,然后确保CreateTask()returns预期的Guid