2014-12-18 6 views
7

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

Я хотел бы получить некоторые отзывы от людей, более опытных по этой теме, чтобы узнать, как я могу это улучшить.

Вот код, объяснение следующим образом:

[Fact] 
public void ShouldGetItemWithSameId() 
{ 
    var fixture = new Fixture().Customize(new AutoMoqCustomization()); 
    var facade = fixture.Freeze<Mock<IDataFacade>>(); 
    facade.Setup(c => c.Get(It.IsAny<int>())).Returns((int i) => new Item { Key = i }); 

    var sut = fixture.Create<BusinessLogic>(); 
    var expected = fixture.Create<int>(); 

    Assert.Equal(expected, sut.Get(expected).Key); 
} 

Моего BusinessLogic класс принимает IDataFacade в качестве параметра конструктора, который отвечает в методе Get(int) для извлечения элемента с одинаковым идентификатором, довольно основным материалом.

Я замораживаю IDataFacade макет, и я установил его для построения объекта, соответствующего ID в It.IsAny<int>. Затем я создаю свой SUT и тестирую его. Работает отлично.

Я хотел бы понять, если я могу улучшить положение вещей, учитывая следующее:

  • Я должен проверить более сложные методы, такие как Query метод, который принимает класс, содержащий множество свойств, которые будут использоваться как фильтры для сопоставления свойств для запрашиваемого типа. В этом случае я не знаю, как правильно выполнить часть «Настройка» макета, так как я должен инициализировать все или близкие ко всем свойства возвращаемого типа, и в этом случае это не один элемент, но целая коллекция
  • установка часть чувствует неуместны, я хотел бы иметь возможность использовать его в более методов

у меня есть некоторые другие тесты с использованием Theory с AutoMoqData, но я не смог достичь этого теста (и я думаю, что более сложные) используют этот подход, поэтому я переключился на обычный Fact с вручную созданным экземпляром.

Любая помощь будет чрезвычайно оценена.

+0

Вы считали (Авто) NSubstitute - я слишком долго держался за мое «неправильное отношение к Moq». http://weareadaptive.com/blog/2014/09/30/why-nsubstitute/ –

ответ

5

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

facade.Setup(c => c.Get(It.IsAny<int>())).Returns((int i) => new Item { Key = i }); 

Все, что вам будет нужно сделать это переместить ожидаемую переменную и изменить Is.IsAny следующим образом:

var expected = fixture.Create<int>(); 
facade.Setup(c => c.Get(expected)).Returns((int i) => new Item { Key = i }); 

Я должен проверить более сложные методы, такие как метод запроса, который принимает класс, содержащий множество свойств, которые будут использоваться в качестве фильтров на соответствие свойств по типу выполняется запрос. В этом случае я не знаю, как правильно выполнить часть «Настройка» макета, так как я должен инициализировать все или близкие ко всем свойства возвращаемого типа, и в этом случае это не один элемент, но целая коллекция

Я не думаю, что вам нужно было бы инициализировать все значения возвращаемого типа. Я предполагаю, что ваш DataFacade возвращает объект (или список в этом случае)? Все, что вам нужно сделать, это убедиться, что возвращаемые объекты соответствуют ссылкам тех, которые возвращены из DataFacade, вам не нужно беспокоиться о свойствах и т. Д., Поскольку вы не тестируете строительство этих объектов, вернулся. Если я неправильно понял, и вы строите объекты в BusinessLogic, тогда это другое дело. Лично у меня не было бы бизнес-логики, зависящей от уровня данных, но это другое обсуждение. :-)

Установка часть чувствует неуместны, я хотел бы иметь возможность использовать его в более методов

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

И мой последний комментарий, обработайте свой тестовый код, как если бы вы обрабатывали ваш производственный код, если он выглядит бесполезным, делайте что-то, чтобы сделать его лучше. Тесты отлично подходят для описания поведения системы, но если их трудно читать (и поддерживать), вы теряете большую ценность.

Редактировать, получается, что это не мой последний комментарий! Если вы новичок в TDD, что я не уверен в этом, не попадайте в ловушку тестирования каждого класса в своем приложении, это распространенная картина, которая стала распространенной, и она обесценивает TDD на мой взгляд , Я написал blog post on my feelings, и Ян Купер дал superb presentation по этому вопросу.

+0

Приятная небольшая обертка для создания() ttd-toolkit для AutoFixture (https://github.com/grzesiek-galezowski/tdd-toolkit), поэтому 'var expected = fixture.Create ();' становится 'var expected = Any.Integer();' –

+0

@ robi-y обертка немного растянута; в то время как я уверен, что у него есть свое место, это не касается кучи вещей, которые хочет OP, поэтому зачем вводить путаницу двух наборов синтаксиса/конвенции. –

+0

@RubenBartelink в целом согласен, но в некоторых случаях это помогло мне достичь более четкого кода, может быть, лучшим примером является, например, Any.IntegerOtherThan (42) , который ей не нужен, конечно, просто предлагая какой-то крошечный синтаксический сахар которые иногда могут быть полезны, спасибо –

2

Некоторые основы:

Ваш тест создается экземпляр класса (и его конструктор называется) до каждого отдельного теста выполняется. например если ваш класс Test имеет три метода с [Факт] атрибут, он получает экземпляр трижды

TestFixture класс другой класс, который предназначен для инстанциирован в один раз для всех тестов в тестовом классе.

Для выполнения этой работы ваш тестовый класс должен реализовать интерфейс IUseFixture, например. выполнить элемент SetFixture()

Вы можете использовать тот же класс MyTestFixture для нескольких классов тестов.

Внутри TestFixture вы выполняете все макет-настройки.

Здесь общее расположение:

public class MyTestFixture 
{  
    public Mock<MyManager> ManagerMock; 

    public TestFixture() // runs once 
    { 
     ManagerMock.Setup(...); 
    } 
} 

public MyTestClass : IUseFixture<MyTestFixture> 
{ 
    private MyTestFixture fixture; 

    public MyTestClass() 
    { 
     // ctor runs for each [Fact] 
    } 

    public void SetFixture(MyTestFixture fixture) 
    { 
     this.fixture = fixture; 
    } 

    [Fact] 
    public void MyTest 
    { 
     // use Mock 
     fixture.ManagerMock.DoSomething() 
    } 
} 
+0

Я уже знаю все это, но я думаю, что использование интерфейса интерфейса IUseFixture поражает целью использования AutoFixture. Возможно, я ошибаюсь. –

+0

«Внутри TestFixture вы делаете все макет-настройки» - я не согласен, вы должны делать только любую настройку, которая применима ко всем тестам в этом крепеже. – DoctorMick

+0

@DoctorMick да, конечно, в TestFixture вы делаете (все) все, что вам нужно для ** всех тестов ** – DrKoch

7

В целом, оригинальное испытание выглядит хорошо. Невозможно или легко извлечь из теста Stubs and Mocks из теста, в общем виде.

То, что вы, , может, хотя и минимизирует фазу Arrange теста.Вот оригинальный тест переписана с использованием AutoFixture.Xunit «s собственный блок-тестирования DSL:

[Theory, TestConventions] 
public void ShouldGetItemWithSameId(
    [Frozen]Mock<IDataFacade> facadeStub, 
    BusinessLogic sut, 
    int expected) 
{ 
    facadeStub 
     .Setup(c => c.Get(It.IsAny<int>())) 
     .Returns((int i) => new Item { Key = i }); 

    var result = sut.Get(expected); 
    var actual = result.Key; 

    Assert.Equal(expected, actual); 
} 

Атрибут TestConventions определяется как:

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

НТН


типы образцов используются в пример:

public class Item 
{ 
    public int Key { get; set; } 
} 

public interface IDataFacade 
{ 
    Item Get(int p); 
} 

public class BusinessLogic 
{ 
    private readonly IDataFacade facade; 

    public BusinessLogic(IDataFacade facade) 
    { 
     this.facade = facade; 
    } 

    public Item Get(int p) 
    { 
     return this.facade.Get(p); 
    } 
} 
+1

Это мне нравится. Я попробую и прокомментирую позже. Благодаря :) –

Смежные вопросы