8

Когда я использую Moq непосредственно издеваться IBuilderFactory и инстанцирует BuilderService себя в модульном тесте, я могу пройти тест на прохождение который проверяет, что Create() метод IBuilderFactory называется ровно один раз.Почему Autofixture w/AutoMoqCustomization перестает жаловаться на отсутствие безпараметрического конструктора при закрытии класса?

Однако, когда я использую Autofixture с AutoMoqCustomization, замораживание издеваться над IBuilderFactory и инстанцировании BuilderService с fixture.Create<BuilderService>, я получаю следующее исключение:

System.ArgumentException: Невозможно создать экземпляр прокси-сервер class: OddBehaviorTests.CubeBuilder. Не удалось найти конструктор без параметров . Имя параметра: constructorArguments

Если я CubeBuilder запечатано (в лице, заменив его запечатанный класс SealedCubeBuilder, который вызывается IBuilderFactoryForSealedBuilder.Create(), тест проходит с использованием AutoFixture с AutoMoqCustomization, без исключения, брошенного

Am. Я пропустил что-то? Поскольку я получаю прохождение тестов с использованием Moq напрямую, я считаю, что это связано с Autofixture и/или AutoMoqCustomization.Это желаемое поведение? Если да, то почему?

Для воспроизведения я использую:

using Moq; 
using Ploeh.AutoFixture; 
using Ploeh.AutoFixture.AutoMoq; 
using Xunit; 

Вот четыре теста, иллюстрирующие поведение:

public class BuilderServiceTests { 
    [Fact] 
    public void CubeBuilderFactoryCreateMethodShouldBeCalled_UsingMoq() { 
     var factory = new Mock<IBuilderFactory>(); 
     var sut = new BuilderService(factory.Object); 
     sut.Create(); 
     factory.Verify(f => f.Create(), Times.Once()); 
    } 
    [Fact] 
    public void CubeBuilderFactoryCreateMethodShouldBeCalled_UsingAutoFixture() { 
     var fixture = new Fixture().Customize(new AutoMoqCustomization()); 
     var factory = fixture.Freeze<Mock<IBuilderFactory>>(); 
     var sut = fixture.Create<BuilderService>(); 
     sut.Create(); // EXCEPTION THROWN!! 
     factory.Verify(f => f.Create(), Times.Once()); 
    } 
    [Fact] 
    public void SealedCubeBuilderFactoryCreateMethodShouldBeCalled_UsingMoq() { 
     var factory = new Mock<IBuilderFactoryForSealedBuilder>(); 
     var sut = new BuilderServiceForSealedBuilder(factory.Object); 
     sut.Create(); 
     factory.Verify(f => f.Create(), Times.Once()); 
    } 
    [Fact] 
    public void SealedCubeBuilderFactoryCreateMethodShouldBeCalled_UsingAutoFixture() { 
     var fixture = new Fixture().Customize(new AutoMoqCustomization()); 
     var factory = fixture.Freeze<Mock<IBuilderFactoryForSealedBuilder>>(); 
     var sut = fixture.Create<BuilderServiceForSealedBuilder>(); 
     sut.Create(); 
     factory.Verify(f => f.Create(), Times.Once()); 
    } 
} 

Вот необходимые классы:

public interface IBuilderService { IBuilder Create(); } 
public class BuilderService : IBuilderService { 
    private readonly IBuilderFactory _factory; 
    public BuilderService(IBuilderFactory factory) { _factory = factory; } 
    public IBuilder Create() { return _factory.Create(); } 
} 
public class BuilderServiceForSealedBuilder : IBuilderService { 
    private readonly IBuilderFactoryForSealedBuilder _factory; 
    public BuilderServiceForSealedBuilder(IBuilderFactoryForSealedBuilder factory) { _factory = factory; } 
    public IBuilder Create() { return _factory.Create(); } 
} 

public interface IBuilderFactoryForSealedBuilder { SealedCubeBuilder Create(); } 
public interface IBuilderFactory { CubeBuilder Create(); } 
public interface IBuilder { void Build(); } 

public abstract class Builder : IBuilder { 
    public void Build() { } // build stuff 
} 

public class CubeBuilder : Builder { 
    private Cube _cube; 
    public CubeBuilder(Cube cube) { _cube = cube; } 
} 

public sealed class SealedCubeBuilder : Builder { 
    private Cube _cube; 
    public SealedCubeBuilder(Cube cube) { _cube = cube; } 
} 

public class Cube { } 

ответ

11

Если вы посмотрите на трассировку стека, вы заметите, что, за исключением происходит глубоко внутри Moq. AutoFixture - это самоуверенная библиотека, и одно из ее мнений заключается в том, что nulls являются недопустимыми возвращаемыми значениями. По этой причине, AutoMoqCustomization настраивает все Mock экземпляры, как это:

mock.DefaultValue = DefaultValue.Mock; 

(среди прочего). Таким образом, вы можете воспроизвести тест неисправного полностью без AutoFixture:

[Fact] 
public void ReproWithoutAutoFixture() 
{ 
    var factory = new Mock<IBuilderFactory>(); 
    factory.DefaultValue = DefaultValue.Mock; 
    var sut = new BuilderService(factory.Object); 
    sut.Create(); // EXCEPTION THROWN!! 
    factory.Verify(f => f.Create(), Times.Once()); 
} 

Самое странное в том, что она до сих пор, кажется, работает с закрытыми классами. Это, однако, не совсем верно, а скорее происходит в тестах ОП, которые составляют False Negatives.

Рассмотрим этот Characterization Test из Moq:

[Fact] 
public void MoqCharacterizationForUnsealedClass() 
{ 
    var factory = new Mock<IBuilderFactory>(); 
    factory.DefaultValue = DefaultValue.Mock; 
    Assert.Throws<ArgumentException>(() => factory.Object.Create()); 
} 

Moq правильно бросает исключение, так как это было предложено создать экземпляр CubeBuilder, и он не знает, как это сделать это, потому что CubeBuilder не имеет конструктор по умолчанию, и нет Setup рассказывает о том, как обращаться с вызовами до Create.

(В отрыве от иронии заключается в том, что AutoFixture идеально в состоянии создать экземпляр CubeBuilder, но в Moq нет точки расширяемости, которая позволяет AutoFixture войти и взять на себя поведение создания экземпляра объекта по умолчанию Moq).

Теперь рассмотрит этот тест Характеризации когда тип возвращаемого запечатан:

[Fact] 
public void MoqCharacterizationForSealedClass() 
{ 
    var factory = new Mock<IBuilderFactoryForSealedBuilder>(); 
    factory.DefaultValue = DefaultValue.Mock; 
    var actual = factory.Object.Create(); 
    Assert.Null(actual); 
} 

оказывается, что в этом случае, несмотря на то, были неявно сказал не возвращать null, Moq делает это в любом случае.

Моя теория состоит в том, что происходит на самом деле в том, что в MoqCharacterizationForUnsealedClass выше, чем factory.DefaultValue = DefaultValue.Mock; на самом деле означает, что Moq создает макет из CubeBuilder - другими словами, он динамически выделяет класс, который получает от CubeBuilder , Однако, когда его просят создать mock SealedCubeBuilder, он не может, поскольку он не может создать класс, полученный из закрытого класса.

Вместо того чтобы бросать исключение, оно возвращает null. Это противоречивое поведение, и I've reported this as a bug in Moq.

+0

Спасибо за расшифровку этого! Смешанные эмоции, когда ответ оказывается ошибкой в ​​библиотеке, которую я использую ... Я случайно наткнулся на это, потому что некоторые из моих классов XXXBuilder были запечатаны, а некоторые нет. Обнаружен аргумент о том, следует ли запечатывать классы в отсутствие веской причины не запечатывать или делать наоборот. Похоже, что классы уплотнения могут вызвать трение на модульные испытания - позаботиться о том, чтобы поделиться мнением по этому вопросу? – Jeff

+1

Понятие о том, что закрытые классы боятся тестируемости, возникает (я думаю) из Java, где все члены виртуальны по умолчанию. В C# это не так, поэтому вопрос становится менее важным. В любом случае, если вы предпочитаете композицию над наследованием, это вряд ли имеет значение. FWIW, F #, такие как записи, компилируются в запечатанные классы, но код F # может быть очень подвержен тестированию. В конечном счете, если вы будете следовать TDD, у вас будет тестовый код :) –

+0

Я должен уточнить, что закрытые классы, по-видимому, вредят тестируемости, когда издевательская структура в использовании не может издеваться над закрытыми классами, но ее просят сделать это. Затем остается выбирать между реализацией определенной функции доступности, [как заявляют известные люди] (http://stackoverflow.com/a/2165287/533958), делая класс незапечатанным просто потому, что * mocking tool * нуждается в этом и сохраняет класс запечатанным, поэтому не нужно разрабатывать для наследования. Тем не менее, в отношении разработки TDD. – Jeff

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