如何测试 DDD 命令处理程序中的逻辑?

Gur*_*ler 5 c# unit-testing domain-driven-design clean-architecture

我正在尝试学习 DDD 和 Clean Architecture 的一些想法,但遇到了一个问题:如何对应用程序层中的命令处理程序进行单元测试,验证它们在域对象上调用正确的方法,而不测试内部的逻辑那些域对象?假设我有以下域实体:

public class User
{
    public User(int id)
    {
        Id = id;
    }

    public int Id { get; }

    public void RemoveProfilePicture()
    {
        ...
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

我有以下简单的命令类:

public class RemoveUserProfilePictureCommand : ICommand
{
    public RemoveUserProfilePictureCommand(int userId)
    {
        UserId = userId;
    }
    public int UserId { get; }
}
Run Code Online (Sandbox Code Playgroud)

和命令处理程序(位于应用程序层):

public class Handler : ICommandHandler<RemoveUserProfilePictureCommand>
{
    private readonly IUserRepository userRepository;

    public Handler(IUserRepository userRepository)
    {
        this.userRepository = userRepository;
    }

    public void Handle(RemoveUserProfilePictureCommand command)
    {
        var user = userRepository.GetById(command.UserId);
        user.RemoveProfilePicture();
    }
}
Run Code Online (Sandbox Code Playgroud)

我想验证对的调用Handle是否会找到正确的并调用其上的User域方法。RemoveProfilePicture我发现的仅有的两个选项并不能让我满意,我正在寻找更好的解决方案。

基于域逻辑副作用的断言

显而易见的解决方案是断言域模型上的操作发生了,例如:

[Fact]
public void Handle_UserExists_ShouldRemoveProfilePicture()
{
    var user = new User(id: 555);
    repository.GetById(user.Id).Returns(user);
    var command = new RemoveUserProfilePictureCommand(user.Id);

    handler.Handle(command);

    Assert.Null(user.ProfilePicture);
} 
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是,我们根据域模型内部的逻辑进行断言,如果该逻辑比将属性设置为更复杂,ProfilePicture我们null仍然必须在命令处理程序的测试中断言其结果,即使领域逻辑已经被它自己的单元测试覆盖。问题源于应用层类与域类的紧密耦合。这导致我想到了第二个解决方案:

将应用层与域层解耦

如果该类User将实现一个接口,例如IUser,那么测试中的假存储库可能会IUser向命令处理程序返回不同的实现,以验证调用了正确的方法。这里的问题是,根据我的理解,应用程序层应该是域的薄包装,并且不应该与其分离。另外,我发现的所有示例始终使用领域对象的具体类型,并且在实体类中实现接口似乎很奇怪。


有人能找到更好的解决方案来测试此类吗?因为它不是关于某些边缘情况,而是关于几乎每个命令处理程序类,并且其中大多数比我上面给出的简单示例更复杂。

ala*_*udi 1

当我做 DDD 启发的 CQRS 项目时,我也遇到了完全相同的问题。据我所知,您还尝试开发一个 CQRS 项目,因为您正在使用命令等概念,并且坚持null在命令处理程序中返回一个值。

话虽如此,断言一个null值并不是测试任何东西。您要测试的是系统中是否发生了副作用。就您而言,您希望确保“用户个人资料图片已被删除”。

您似乎缺少的是 CQRS 的另一半:事件。当命令处理程序成功完成其事务(即副作用)时,您通常会调度一个代表该事务的事件。然后,您的单元测试将简单地断言UserProfilePictureRemoved事件已调度(或排队,具体取决于您的实现)。