应用层测试

时间:2018-01-23 19:46:44

标签: .net unit-testing tdd domain-driven-design nsubstitute

我想测试应用层的类,不确定什么是更好的。我有Domain Model的类是Task,例如

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方法。我无法理解我应该测试什么。假设,我写了测试

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类依赖于它们。是一个好方法?

如果这令人困惑,我很抱歉。

更新 我认为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
        }
    }
}

在进一步实施期间,我将添加任务所有者或类似的东西。

3 个答案:

答案 0 :(得分:2)

您可以测试对象的创建和正确初始化,但这里没有真正的逻辑可供测试。 如果您打算测试使用此任务的上下文,行为是什么会更好。

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

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

[编辑]

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

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

更多here

[EDIT2]

对于依赖项,您可以完全将任务的初始化委托给另一个方法/类,这样您的测试就可以完全预测并与其他依赖项隔离。 从纯粹主义的角度来看,单元测试应该在当时测试一件事并且有限的理由失败。如果您正在学习,我会建议您严格遵守此规则。

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 :(得分:2)

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

  1. 确保方法调用repository.Save并且task的内容正确
  2. 确保taskId正确
  3. 确保错误处理正常工作
  4. 对于Task对象,您可以考虑进行测试以确保构造函数正常工作,但是对Clock类进行单元测试以确保它按预期工作更为重要。

答案 2 :(得分:1)

在这里你问:

  

我有Clock和Task构造函数的测试,但似乎是正确的   CreateTask测试的执行取决于Task和Clock类。所以   test隐含地涵盖了3个类。或者我误解了吗?

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

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

  • 给定带有空参数的CreateTask()调用,然后确保按预期报告错误(例如,抛出ArgumentNullException)< / LI>
  • 给定具有良好参数的CreateTask()调用,然后确保使用适当的参数调用repository.Save()(通过使用模拟的构造函数 - 注入存储库)
  • 给定 CreateTask()调用参数失败repository.Save()调用(抛出异常),然后确保CreateTask()按预期失败(例如,重新抛出异常,抛出新的外部异常或其他预期的错误处理)
  • 鉴于成功repository.Save()次调用,然后确保CreateTask()返回预期的Guid