Логика создания конкретного объекта AutoDataAttribute вызывает все методы получения свойств один раз.

Используя AutoFixture 3.50 и xUnit.NET, кажется, что есть разница между тем, как Fixture.Create() создает конкретные объекты, и тем, как тесты AutoData Theory создают конкретные объекты.

Простой пример:

public class Foo
{
    private string prop;
    public string Prop
    {
        get
        {
             if (prop == null) { prop = "Prop"; } // Breakpoint 'A'
             return prop;
        }
    }
}

Протестируйте с помощью Fixture:

[Fact]
public void FixtureTest()
{
    var fixture = new Fixture();
    var result = fixture.Create<Foo>(); // Breakpoint 'B1'
}

Протестируйте с помощью AutoDataAttribute:

[Theory, AutoData]
public void AutoDataTest(Foo sut)
{
    var bar = 1; // Essential no-op, Breakpoint 'B2'
}

В первом тесте точка останова «B1» срабатывает, а точка останова «A» никогда не срабатывает. В последнем тесте точка останова «A» срабатывает раньше, чем точка останова «B2». Это проблематично, когда у меня есть "лениво" инициализированное свойство, не отличающееся от приведенного выше - поскольку поле поддержки свойства инициализируется до запуска теста, я не могу проверить логику инициализации.

Есть ли способ настроить AutoDataAttribute, чтобы обойти это поведение? А может быть, это баг?


person MrPiao    schedule 26.11.2017    source источник


Ответы (1)


На самом деле это не проблема AutoFixture, а скорее проблема xUnit.net, если хотите. Вы можете воспроизвести его полностью без AutoFixture следующим образом:

[Theory, ClassData(typeof(FooTestCases))]
public void ClassDataTest(Foo sut)
{
    var bar = 1; // Essential no-op, Breakpoint 'B3'
}

private class FooTestCases : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { new Foo() };
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Если вы отлаживаете этот ClassDataTest, вы также нажмете точку останова «A» до того, как вы нажмете точку останова «B3».

Причина в том, что средство запуска тестов xUnit.net хочет дать вам хорошее отображаемое имя для каждого параметризованного теста, поэтому он пытается создать удобочитаемое строковое представление всех аргументов, переданных каждому тестовому набору в [Theory].

Когда объект данных явно не переопределяет ToString, xUnit.net возвращается к чтению всех свойств и строит отображаемую строку на их основе. Вот что здесь происходит.

Я пытался найти официальную документацию об этом поведении, но лучшее, что мне удалось найти, это это. Как было предложено там, вы можете обойти проблему, переопределив ToString:

public class Foo
{
    private string prop;
    public string Prop
    {
        get
        {
            if (prop == null) { prop = "Prop"; } // Breakpoint 'A'
            return prop;
        }
    }

    public override string ToString()
    {
        return "Foo";
    }
}

Это изменение предотвращает попадание в точку останова «A» как при использовании ClassData, так и AutoData. Хотите ли вы переопределить ToString — это другой вопрос.

Если вы не хотите переопределять ToString в Foo, возможно, вы можете обойти проблему, протестировав дочерний класс для конкретного теста, который переопределяет ToString, например так:

[Theory, AutoData]
public void AutoDataTestFoo(TestFoo sut)
{
    var bar = 1; // Essential no-op, Breakpoint 'B4'
}

public class TestFoo : Foo
{
    public override string ToString()
    {
        return "Foo";
    }
}

Это также предотвращает попадание в точку останова «A».

Пока Foo не sealed, вы сможете это сделать. Если Foo равно sealed, я не могу придумать другого обходного пути, кроме как написать тест в стиле FixtureTest.

person Mark Seemann    schedule 26.11.2017
comment
Спасибо за подробный ответ! - person MrPiao; 27.11.2017