Теории 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>();
может вызвать проблемы, если вы используете несколько строк в ваших тестах.