AutoFixture 相关对象属性

AutoFixture Relating Object Properties

我正在编写一些单元测试,并且遇到了一段感觉像代码味道的代码。

基本上我有一个 Web Api。它接收传入请求 'SomeRequest':

public class SomeRequest
{
    public string Name { get; set; }
    public IEnumerable<int> RequestedDataIds { get; set; } 
}

作为我测试的一部分。我需要确保所有这些 'RequestedDataIds' 都有效。我有一个方法调用系统的另一部分 returns 有效的 'SomeData' 对象的集合。 'SomeData' 对象包含要验证的 Id:

public class SomeData
{
    public int Id { get; set; } //Id to validate against
    public string Name { get; set; }
}

我的测试系统看起来像这样:

    public void Execute(RequestA request)
    {
        var validDataIds = _someService.GetValidEntities().Select(x => x.Id).ToList();

        var invalidRequestIds = request.RequestedDataIds.Where(requestedId => !validDataIds.Contains(requestedId));

        if (invalidRequestIds.Any())
        {
            throw new SomeException("Error");
        }

        //Carry on as normal
    }

我的问题是。是否有最佳实践或任何方式让 AutoFixture 让这两个集合相互交互,以便我可以进行测试:

  1. 测试当请求id无效时抛出异常
  2. 让所有其他测试确保它们相同,这样就不会抛出异常
  3. 在这种情况下甚至应该使用 AutoFixture 吗?我可以提取逻辑并使其成为 class 的新依赖项。必须然后只测试它被称为。但是,测试依赖项 class 本身仍然会引发问题 1 和 2。

这是我目前尝试解决的问题。我就是不喜欢。

1 - 确保不同的 id 抛出异常

    [Test]
    public void Test1()
    {
        _fixture = new Fixture();

        var request = _fixture.Create<SomeRequest>();
        var dataEnitities = _fixture.CreateMany<SomeData>();

        var maxDataEntityId = dataEnitities.Select(x => x.Id).Max();

        request.RequestedDataIds = new List<int> {maxDataEntityId + 1};

        _someService.GetValidEntities.Returns(dataEnitities);

        //Ensure exception is thrown
    } 

2 - 确保 ID 相同以防止异常

    [Test]
    public void Test1()
    {
        _fixture = new Fixture();

        var request = _fixture.Create<SomeRequest>();
        var dataEnitities = _fixture.CreateMany<SomeData>();

        request.RequestedDataIds = dataEnitities.Select(x => x.Id);

        _someService.GetValidEntities.Returns(dataEnitities);

        //Ensure no exception is thrown
    }  

注意 - 此代码是实际实现的巨大缩减。因此,不必担心它 looks/is 是如何实现的。

是的,这是一种代码味道。您的测试是有效的,但它们可能很简单。 AutoFixture 是很棒的工具,但我认为在这种特定情况下您不需要它。

首先,尽量保持 Execute() 方法简单。它或多或少在 Presentation/Controller/Api 层中使用,因此尽可能保持精简。不想要任何胖控制器。

您真的只需要调用服务方法即可。

    public void Execute(RequestA  request)
    {
        _someService.ValidateEntities(request);
    }

注意方法名称。它没有 return 任何东西。 您的服务可以是这样的:

    public interface ISomeService
    {
        void ValidateEntities(RequestA request);
    }

此实现将 return 来自底层数据源或服务的 ValidDataEntities。

    var validDataIds = GetValidEntities().Select(x => x.Id);

我假设您从某种存储库或其他服务中获得了实体列表。

将您的逻辑推入更多 domain/poco 个对象。

创建一个简单的 POCO class 即 EntityValidator,它接受您的请求和 validDataId。 您可以只对这个 POCO class 进行单元测试,而无需使用 AutoFixture 或任何模拟。

public class EntityValidator
{
    public void Validate(IEnumerable<int> validEntityIds, RequestA request)
    {
        var invalidRequestIds = request.RequestedDataIds.Where(requestedId => !validEntityIds.Contains(requestedId));

        if (invalidRequestIds.Any())
        {
            throw new SomeException("Error");
        }
    }
}

您的单元测试将很简单,您可以使用测试框架方法,即 NUnit 中的 .Thorws 测试异常场景。