Генерация копий с помощью Autofixture - PullRequest
1 голос
/ 24 октября 2019

Я использую Autofixture для своих модульных тестов с автоматически сгенерированными данными.

Чтобы протестировать простую конечную точку контроллера (Get employee by Id), я делаю что-то похожее на это:

[Theory, AutoData]
public void GetEmployeeById_ValidId_ReturnsExpectedModel(
    EmployeeModel expectedEmployee,
    [Frozen] Mock<IEmployeeService> employeeServiceMock,
    EmployeesController sut)
{
    employeeServiceMock
        .Setup(x => x.GetEmployeeById(42))
        .Returns(expectedEmployee);

    var actual = sut.GetEmployeeById(42);

    actual.As<OkObjectResult>().Value.As<EmployeeModel>()
        .Should().BeEquivalentTo(expectedEmployee);
}

И контроллер:

[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
    var employee = employeeService.GetEmployeeById(id);
    if (employee == null)
        return NotFound("Employee not found");

    return Ok(employee);
}

В этом модульном тесте expectedEmployee автоматически генерируется со «случайными» данными. Sut (тестируемая система) настроен для генерации со всеми необходимыми зависимостями (одна из них IEmplyeeService).

Проблема с этим модульным тестом заключается в том, что, если я изменю сотрудника до его возвращенияот контроллера тест все равно пройдет (потому что он ссылается на тот же объект):

employee.SomeInternalModel.FooProperty = "Foo";
return Ok(employee);

Итак, я считаю вышеприведенный модульный тест плохим.

Чтобы выполнить модульный теств этом случае мне нужно передать отдельный объект: глубокую копию EmployeeModel:

employeeServiceMock
    .Setup(x => x.GetEmployeeById(42))
    .Returns(expectedEmployee.DeepCopy());

У меня нет времени и ресурсов для написания методов глубокого копирования для всех моих моделей.

Как легко сгенерировать идентичные модели? Я думал о посеве AutoFixture, но он не поддерживает эту функцию.

Есть ли у вас какие-либо элегантный предложения?

1 Ответ

1 голос
/ 24 октября 2019

Я думаю, вам нужно задать вопрос что вы тестируете? В вашем тестовом примере вы только проверяете, возвращает ли SUT employee, возвращенное service;ИМО не имеет значения, является ли это тем же экземпляром. Обновление свойства не должно нарушать этот тест.

Вы затрагиваете более широкую проблему, хотя в других случаях вы действительно хотите сравнить expected с actual по structural equality в этом случае вы могли бы (используя, например, xUnit s MemberData) использовать builder, который генерирует два экземпляра, когда вы вызываете его дважды:

var employee = new EmployeeModelBuilder().Build();

Такой конструктор может быть улучшен с помощью With() методов:

var employee = new EmployeeModelBuilder().With(name: "John").Build();

Или вы можете просто встроить создание этих объектов, используя new EmployeeModel {}.

Структурное равенство означает, что вам нужен объект, который переопределяет Equality членов или использоватьIEqualityComparer<> в ваших утверждениях.

Обновление

Если вы не хотите использовать пользовательские компоновщики (как вы говорите), вы можете указать AutoFixture для создания объектов с помощьюконкретные свойства устанавливаются со значениями. Если затем вы попросите его создать экземпляр дважды (один раз для вашего expected и один раз для экземпляра, возвращенного службой, введенной в ваш SUT), вы можете сравнить expected с actual в вашей Assert фазе.

    [Fact]
    public void Sut_ReturnsEmployee_FromService()
    {
        var fixture = new Fixture();
        fixture.Customize<EmployeeModel>(e => e.With(x => x.Name, "Foo"));
        var expected = fixture.Create<EmployeeModel>();

        var foundEmployee = fixture.Create<EmployeeModel>();
        var employeeServiceMock = new Mock<IEmployeeService>();
        employeeServiceMock.Setup(f => f.GetEmployeeById(42)).Returns(foundEmployee);

        var sut = new EmployeeController(employeeServiceMock.Object);

        var actual = sut.GetEmployeeById(42);

        Assert.Equal(expected.Name, actual.Name);
    }

Здесь я использовал [Fact], и утверждение сравнивает два конкретных свойства на равенство, но когда вы сравниваете структурное равенство, вы можете просто сравнить сами объекты (как упомянуто выше). Теперь вы можете проверить, что ваш SUT возвращает ожидаемый экземпляр без изменения и без использования двух ссылок на один и тот же экземпляр.

...