При создании какой-либо библиотеки классов вы должны обратить внимание на видимость ее членов и иметь четкое представление о том, что вы хотели бы показать ее пользователям, а что с другой стороны следует скрыть. Однако при написании модульных тестов для таких сборок вы, очевидно, хотите протестировать все, от внутренних элементов до внешних элементов.
Предположим, у вас есть библиотека классов с именем Base, содержащая следующие классы и интерфейсы
ICommandHandler
— который является общедоступным интерфейсомCommandHandler
— в основном его конкретная реализацияIUndoRedoStack<T>
— интерфейс, который используется только внутриUndoRedoStack<T>
— его конкретная реализация
Обратите внимание, что я программирую исключительно против взаимодействий, поскольку это является основным требованием для возможности создания необходимой изоляции для тестирования каждого компонента самостоятельно.
Тестирование классов с внутренней видимостью
Давайте внимательнее посмотрим на CommandHandler
класс
class CommandHandler : ICommandHandler { //... }
При создании теста для CommandHandler
класса вы должны действовать следующим образом
[TestClass] public class CommandHandlerTest { private CommandHandler commandHandler; [TestInitialize] public void Setup() { commandHandler = new CommandHandler(); } [TestCleanup] public void Teardown() { commandHandler = null; } [TestMethod] publiic void ShouldExecuteAGivenCommand() { //the test content } }
Когда вы выполните такой тест, он не будет скомпилирован. Лучше всего помещать тесты в отдельную DLL (я обычно называю ее так, как Base.UnitTests
будто вызывается тестируемая сборка Base
), и поэтому CommandHandler
не будет виден, так как определено, что она имеет только внутреннюю видимость. В предыдущем сообщении в блоге я уже объяснил, как преодолеть эту проблему, а именно, указав InternalsVisibleTo
атрибут в тестируемой сборке. Проверьте это сообщение в блоге для более подробной информации.
Mocking интерфейсы с внутренней видимостью с использованием Moq
Теперь CommandHandler
имеет зависимость от IUndoRedoStack<T>
class CommandHandler : ICommandHandler { public CommandHandler(IUndoRedoStack<ICommand> undoRedoStack) { //... } }
У метода CommandHandler
есть Execute(command)
метод, и предположим, что мы хотели бы проверить тот факт, что при вызове его с заданным ICommand
объектом этот конкретный объект добавляется в undoRedoStack
. Мы бы написали
[TestInitialize] public void Setup() { mockUndoRedo = new Mock<IUndoRedoStack<ICommand<<(); handler = new CommandHandler(mockUndoRedo.Object); } [TestMethod] public void ShouldAddTheCommandToTheUndoStack() { //arrange var myCommand = new MyTestCommand(); //act handler.Execute(myCommand); //assert mockUndoRedo.Verify(x =< x.AddItem(myCommand), Times.Once(), "The command should have been added to the undo stack"); }
При выполнении теста это не удается с
Сообщение: метод инициализации Base.UnitTests.Command.CommandHandlerTest, исключение.
Castle.DynamicProxy.Generators.GeneratorException:
Castle.DynamicProxy.Generators.GeneratorException: Тип Base.Command.IUndoRedoStackBase.Command.ICommand, Base, Version = 1.0.0.0, Culture = нейтральный, PublicKeyToken = null не является открытым. Невозможно создать прокси для типов, которые недоступны.
Проблема та же: библиотека Moq, которую я здесь использую для создания заглушек, не имеет видимости внутренних типов элементов, и поэтому нам нужно добавить еще один InternalsVisibleTo
атрибут специально для Moq. Наиболее интуитивно понятным было бы сделать
[assembly: InternalsVisibleTo("Moq")]
но, к сожалению, это не работает. Вместо этого вам нужно добавить
[assembly:InternalsVisibleTo("DynamicProxyGenAssembly2")]
который используется внутри Moq для генерации прокси-классов. Обратите внимание, что, как уже описано в этом сообщении в блоге, это работает, только если ваша сборка не имеет строгого имени, в противном случае вам также необходимо включить PublicKey сборки.
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=...")]