AutoFixture создает свойство с внутренним установщиком

Есть ли способ позволить AutoFixture создавать свойства с помощью внутреннего установщика?

Я просмотрел исходный код AutoFixture и обнаружил, что в AutoPropertiesCommand метод GetProperties проверяет, имеет ли свойство GetSetMethod() != null. С внутренним сеттером это возвращает null, если только вы не установите для аргумента ignorePublic значение true.

Конечно, проще всего было бы сделать сеттер общедоступным, но в проекте, над которым я работаю, это было бы неправильным решением.

Ниже приведен упрощенный фрагмент кода из проекта в качестве примера.

public class Dummy
{
    public int Id { get; set; }
    public string Name { get; internal set; }
}

public class TestClass
{
    [Fact]
    public void Test()
    {
        var dummy = new Fixture().Create<Dummy>();
        Assert.NotNull(dummy.Name);
    }
}

person Marco van Kimmenade    schedule 13.10.2013    source источник
comment
Почему бы не сделать сеттер общедоступным?   -  person Mark Seemann    schedule 16.10.2013


Ответы (1)


В идеале тесты не должны взаимодействовать с internal членами класса, поскольку они явно исключены из его общедоступного API. Вместо этого эти члены будут тестироваться косвенно с помощью путей кода, инициированных через общедоступный API.

Однако, если это невозможно в вашей конкретной ситуации, возможным обходным решением может быть явное присвоение значения внутренним свойствам из тестов.

Вы можете сделать это одним из двух способов:

  1. Предоставив все внутренние элементы в сборке тестовому проекту с помощью атрибута InternalsVisibleTo.
  2. Представляя модифицируемое состояние класса в определенном интерфейсе и явно реализуя его.

В вашем примере вариант 1 будет таким:

// [assembly:InternalsVisibleTo("Tests")]
// is applied to the assembly that contains the 'Dummy' type

[Fact]
public void Test()
{
    var fixture = new Fixture();
    var dummy = fixture.Create<Dummy>();
    dummy.Name = fixture.Create<string>();
    // ...
}

Вместо этого вариант 2 будет выглядеть примерно так:

public class Dummy : IModifiableDummy
{
    public string Name { get; private set; }

    public void IModifiableDummy.SetName(string value)
    {
        this.Name = value;
    }
}

[Fact]
public void Test()
{
    var fixture = new Fixture();
    var dummy = fixture.Create<Dummy>();
    ((IModifiableDummy)dummy).SetName(fixture.Create<string>());
    // ...
}

Вариант 1 реализуется довольно быстро, но имеет побочный эффект открытия всех внутренних элементов в сборке, что может быть не совсем тем, что вам нужно.
Вариант 2, с другой стороны, позволяет вам контролировать, какая часть состояния объекта должна быть представлена ​​как изменяемая, при этом сохраняя ее отдельно от собственного общедоступного API объекта.

В качестве примечания я хотел бы отметить, что, поскольку вы используете xUnit, вы можете воспользоваться преимуществами AutoFixture поддержка теорий данных, чтобы сделать ваши тесты более лаконичными:

[Theory, AutoData]
public void Test(Dummy dummy, string name)
{
    ((IModifiableDummy)dummy).SetName(name);
    // ...
}

Если вы предпочитаете установить для свойства Name известное значение, сохраняя при этом анонимность остальной части объекта Dummy, у вас также есть возможность объединить два в рамках одной теории данных:

[Theory, InlineAutoData("SomeName")]
public void Test(string name, Dummy dummy)
{
    ((IModifiableDummy)dummy).SetName(name);
    // ...
}
person Enrico Campidoglio    schedule 14.10.2013
comment
Спасибо за Ваш ответ. Я решил эту проблему, добавив пользовательский ISpecimenBuilder, который создает экземпляр Dummy, а затем перебирает все свойства и создает фикстуру для каждого из них. Это почти то же самое, что и AutoPropertiesCommand. - person Marco van Kimmenade; 16.10.2013