Я бы избавился от класса GUIEventListener. Мне кажется это излишним.
Поскольку представление знает, когда нажимается кнопка, пусть представление делится своими знаниями со всем миром:
public interface IView
{
void DisplayMessage(string message);
void AddButtonClickHandler(Action handler);
}
FileSaver еще проще:
public interface IFileSaver
{
Boolean SaveFileWithRandomLine();
}
Ради интереса, давайте создадим интерфейс для контроллера:
public interface IController
{
}
И реализация контроллера:
public class Controller : IController
{
public Controller(IView view, IFileSaver fileSaver)
{
}
}
ОК, давайте напишем тесты (я использую NUnit и Moq):
[TestFixture]
public class ControllerTest
{
private Controller controller;
private Mock<IFileSaver> fileSaver;
private Mock<IView> view;
private Action ButtonClickAction;
[SetUp]
public void SetUp()
{
view = new Mock<IView>();
//Let's store the delegate added to the view so we can invoke it later,
//simulating a click on the button
view.Setup((v) => v.AddButtonClickHandler(It.IsAny<Action>()))
.Callback<Action>((a) => ButtonClickAction = a);
fileSaver = new Mock<IFileSaver>();
controller = new Controller(view.Object, fileSaver.Object);
//This tests if a handler was added via AddButtonClickHandler
//via the Controller ctor.
view.VerifyAll();
}
[Test]
public void No_button_click_nothing_happens()
{
fileSaver.Setup(f => f.SaveFileWithRandomLine()).Returns(true);
view.Verify(v => v.DisplayMessage(It.IsAny<String>()), Times.Never());
}
[Test]
public void Say_it_worked()
{
fileSaver.Setup(f => f.SaveFileWithRandomLine()).Returns(true);
ButtonClickAction();
view.Verify(v => v.DisplayMessage("It worked!"));
}
[Test]
public void Say_it_failed()
{
fileSaver.Setup(f => f.SaveFileWithRandomLine()).Returns(false);
ButtonClickAction();
view.Verify(v => v.DisplayMessage("It failed!"));
}
}
Я думаю, что тесты довольно ясны, но я не знаю, знаете ли вы Moq.
Полный код для контроллера может выглядеть следующим образом (я просто собрал его в одну строку, но это не обязательно, конечно):
public class Controller : IController
{
public Controller(IView view, IFileSaver fileSaver)
{
view.AddButtonClickHandler(() => view.DisplayMessage(fileSaver.SaveFileWithRandomLine() ? "It worked!" : "It failed!"));
}
}
Как вы можете видеть, таким образом вы можете протестировать контроллер, и у нас даже нет возможности запустить View или FileSaver. Используя интерфейсы, они не должны знать друг друга.
Представление ничего не знает (за исключением того, что кто-то может быть проинформирован о нажатии кнопки), это максимально возможный дамп. Обратите внимание, что никакие события не загрязняют интерфейс, но если вы собираетесь реализовать View в WinForms, ничто не мешает вам использовать события внутри View-реализации. Но никто снаружи не должен знать, и поэтому нам не нужно проверять это.
FileSaver просто сохраняет файлы и сообщает, произошел ли сбой или нет. Он не знает о контроллерах и представлениях.
Контроллер собирает все вместе, не зная о реализациях. Он просто знает контракты. Он знает о View и FileSaver.
С этим дизайном мы просто тестируем поведение контроллера. Мы спрашиваем: «Если кнопка была нажата, было ли представление информировано о том, что оно должно отображать эту информацию?» и так далее. Если хотите, вы можете добавить дополнительные тесты, чтобы проверить, был ли метод сохранения на FileSaver вызван контроллером.
Хорошим ресурсом по этой теме является Джереми Миллер * «Построй свою собственную серию CAB»