При создании какой-либо библиотеки классов вы должны обратить внимание на видимость ее членов и иметь четкое представление о том, что вы хотели бы показать ее пользователям, а что с другой стороны следует скрыть. Однако при написании модульных тестов для таких сборок вы, очевидно, хотите протестировать все, от внутренних элементов до внешних элементов.
Предположим, у вас есть библиотека классов с именем 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=...")]