Теории AutoData с Автофиксацией с использованием ручных подделок

учитывая эту систему для тестирования:

public class MySut
{
    private readonly IHardToMockDependency _hardToMockDependency;

    public MySut(IHardToMockDependency hardToMockDependency,
                 IOtherDependency otherDependency)
    {
        _hardToMockDependency = hardToMockDependency;
    }

    public string GetResult()
    {
        return _hardToMockDependency.GetResult();
    }
}

public interface IOtherDependency { }

public interface IHardToMockDependency
{
    string GetResult();
}

и этот модульный тест:

internal class FakeHardToMockDependency : IHardToMockDependency
{
    private readonly string _result;

    public FakeHardToMockDependency(string result)
    {
        _result = result;
    }

    public string GetResult()
    {
        return _result;
    }
}

public class MyTests
{
    [Fact]
    public void GetResultReturnsExpected()
    {
        string expectedResult = "what I want";
        var otherDependencyDummy = new Mock<IOtherDependency>();
        var sut = new MySut(new FakeHardToMockDependency(expectedResult),
                            otherDependencyDummy.Object);

        var actualResult = sut.GetResult();

        Assert.Equal(expectedResult, actualResult);
    }
}

Как преобразовать его в использование AutoFixture.Xunit и AutoFixture.AutoMoq (все еще используя ручную подделку)?

в реальных тестах ручная подделка будет иметь более сложный интерфейс и поведение. Обратите внимание, что я хочу передать анонимную переменную (строку expectedResult) конструктору ручной подделки.

3 ответов


Здесь уже есть несколько хороших ответов, но я хотел бы предложить более простую альтернативу, которая включает в себя небольшое ослабление инвариантов класса FakeHardToMockDependency. Сделайте его общедоступным и предоставьте способ назначить результат, который отделен от конструктора:

public class FakeHardToMockDependency : IHardToMockDependency
{
    private string _result;

    public FakeHardToMockDependency(string result)
    {
        _result = result;
    }

    internal string Result
    {
        get { return _result; }
        set { _result = value; }
    }

    public string GetResult()
    {
        return _result;
    }
}

обратите внимание, что я добавил внутреннее свойство и удалил readonly ключевое слово из поля.

Это позволяет рефакторинг оригинальным тестом это:

[Theory, AutoMoqData]
public void GetResultReturnsExpected_AutoDataVersion(
    [Frozen(As = typeof(IHardToMockDependency))]FakeHardToMockDependency fake,
    MySut sut)
{
    var expected = "what I want";
    fake.Result = expected;

    var actual = sut.GetResult();

    Assert.Equal(expected, actual);
}

для полноты, вот код AutoMoqDataAttribute:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }
}

в зависимости от того, какие параметры вам нужно передать в ручную подделку, вы можете использовать параметризованный атрибут, похожий на встроенный AutoFixture InlineAutoDataAttribute.

учитывая эти

public interface IHardToMockDependency
{
    string Value { get; }
}

public class FakeHardToMockDependency : IHardToMockDependency
{
    private readonly string _value;

    public FakeHardToMockDependency(string value)
    {
        _value = value;
    }

    #region IHardToMockDependency Members

    public string Value
    {
        get { return this._value; }
    }

    #endregion IHardToMockDependency Members
}

создать ICustomization реализация, которая сообщает объекту fixture, как создать имплементацию :

public class FakeHardToMockDependencyCustomization : ICustomization
{
    private readonly string _value;

    public FakeHardToMockDependencyCustomization(string value)
    {
        _value = value;
    }

    #region ICustomization Members

    public void Customize(IFixture fixture)
    {
        fixture.Register<IHardToMockDependency>(() => new FakeHardToMockDependency(this._value));
    }

    #endregion ICustomization Members
}

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

далее сверните это с другими настройками, которые вы хотите использовать в CompositeCustomization:

public class ManualFakeTestConventions : CompositeCustomization
{
    public ManualFakeTestConventions(string value)
        : base(new FakeHardToMockDependencyCustomization(value), new AutoMoqCustomization())
    {
    }
}

убедитесь, что вы всегда поставить настройки в порядке от самых конкретных до самых общих, Как пояснил здесь Марк Seemann.

теперь вы создаете AutoDataAttribute реализация, которая использует эту настройку:

public class ManualFakeAutoDataAttribute : AutoDataAttribute
{
    public ManualFakeAutoDataAttribute(string value)
        : base(new Fixture().Customize(new ManualFakeTestConventions(value)))
    {
    }
}

теперь это можно использовать так же, как InlineAutoDataAttribute:

public class ManualFakeTests
{
    [Theory, ManualFakeAutoData("iksdee")]
    public void ManualFake(IHardToMockDependency fake)
    {
        Assert.IsType<FakeHardToMockDependency>(fake);
        Assert.Equal("iksdee", fake.Value);
    }
}

вы также можете ввести его в автоматически созданный Экземпляр SUT сразу же, применив [Frozen] атрибут параметра теории:

    [Theory, ManualFakeAutoData("iksdee")]
    public void SutWithManualFake([Frozen] IHardToMockDependency fake, MySut sut)
    {

    }

это создаст MySut и IHardToMockDependency экземпляр, необходимый для конструктора, для которого вы дали AutoFixture правило в FakeHardToMockDependencyCustomization, а также дать вам тот самый экземпляр, как fake переменной.

обратите внимание, что не замораживание подделки все равно даст вам правильный FakeHardToMockDependency экземпляр, а также ввести один в sut, но они будут различны, по мере того как мы регистрировали делегат фабрики в изготовлении на заказ. Замораживание экземпляра приведет к тому, что приспособление всегда будет возвращать один и тот же экземпляр для последующих запросов интерфейса.

это имеет несколько предостережений, однако:

  • у вас нет ссылки на строку, которую вы передаете в качестве параметра, поэтому вы должны иметь ее там как строковый литерал дважды. Вы можете обойти это с помощью строковых констант в тестовом классе, например.
  • в количество типов, которые могут использоваться в качестве параметров атрибутов в .NET, ограничено. Пока вам нужны только базовые типы, вы должны быть в порядке, но вызов конструкторов или тому подобное в списке параметров невозможен.
  • вы должны использовать этот атрибут только тогда, когда вам нужен экземпляр if IHardToFakeDependency; в противном случае вам всегда придется передавать строковый параметр в любом случае. Если у вас есть набор стандартных настроек, которые необходимо использовать, создайте другой атрибут, содержащий только те.
  • Если вам нужны возможности InlineAutoDataAttribute в то же время вам также нужно создать еще один атрибут, который сочетает в себе функции обоих.

в зависимости от конкретных обстоятельств, вы также можете посмотреть xUnit.net s PropertyDataAttribute, но я почти никогда не использую это.

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

если вы часто пишете код в определенном домене, который вам нужно проверить, вполне может иметь смысл создать библиотеку, содержащую настройки, атрибуты и объекты заглушки, которые всегда будут готовы к использованию, как только вы поместите его рядом с xUnit.net, AutoFixture и Moq. Я знаю, что чертовски рад, что построил свой.

О, а также: имея зависимость, которую трудно высмеять может указать на дизайн вопрос. Почему так трудно смеяться?


возможно, это не самая идиоматическая настройка Autofixture, но определенно работает:

[Fact]
public void GetResultReturnsExpected()
{
    var fixture = new Fixture()
        .Customize(new AutoMoqCustomization());

    var expectedResult = fixture.Create<string>();

    fixture.Register<IHardToMockDependency>(
        () => new FakeHardToMockDependency(expectedResult));

    var sut = fixture.Create<MySut>();

    var actualResult = sut.GetResult();

    Assert.Equal(expectedResult, actualResult);
}

если вы хотите использовать AutoData вы можете создать свой собственный AutoMoqData на основе эту большую статью где вы можете скрыть некоторые или все кастимизации крепления.

что-то типа:

public class MySutAutoDataAttribute : AutoDataAttribute
{
    public MySutAutoData()
        : base(new Fixture()
            .Customize(new AutoMoqCustomization()))
    {
        Fixture.Freeze<string>();

        Fixture.Register<IHardToMockDependency>(
            () => new FakeHardToMockDependency(Fixture.Create<string>()));
    }
}

и вы можете использовать его как:

[Theory, MySutAutoData]
public void GetResultReturnsExpected(MySut sut, string expectedResult)
{
    var actualResult = sut.GetResult();

    Assert.Equal(expectedResult, actualResult);
}

но вы должны отметить, что есть место для большого улучшения в MySutAutoDataAttribute например: не очень общий и Fixture.Freeze<string>(); может вызвать проблемы, если вы используете несколько строк в ваших тестах.