Introduction
Current tendencies and best practices in software development lead to the common pattern that a software system has to be solid, scalable and reliable. In order to comply with those three requirements, two aspects have to be considered during design phase: Code coverage and separation of concerns.
Code coverage is the metric that indicates how much of the code is covered by unit tests or, in other words, how testable is the source code. The other aspect, separation of concerns, plays close with code coverage, since, in order to have well-structured unit tests, we need to give each component of the system a single concern to deal with. In software design, the milk man cannot deliver newspapers around the neighborhood. Each component has a specific well-defined responsibility.
What does all of this has to do with user interface design? Well, user interface code often lead developers to break these two design aspects. Is a common well-known scenario where a class that represents a window form also performs business logic validations and queries the database directly. This not only violates the principle of separation of concerns but also makes unit testing of that particular class tedious, difficult and repetitive; affecting code coverage significantly.
This article intends to demostrate clear examples of both the "traditional" way and the alternative way using the Model-View-Presenter pattern to improve overall user interface design. Examples will be provided in C#, using Windows Forms. The example application is Mono-compliant, so it can be compiled and run in several platforms using the Mono Framework. An additional version for Java using Swing will be provided at the end of the article.
The Scenario
For this article, we are going to build a small application that displays a list of clients available in a data source (in this case, an array list) to the user. The user will be able to select clients from the list and view additional details like age, gender and e-mail address.
The clients will be queried from a repository which, for matters of simplicity, will return clients from a pre-defined array list.
using System.Collections.Generic;
namespace Codenough.Demos.WinFormsMVP
{
public class ClientRepository
{
private IList<Client> clients = new List<Client>()
{
new Client { Id = 1, Name = "Matt Dylan", Age = 28, Gender = "Male", Email = "mattd@none.com" },
new Client { Id = 2, Name = "Anna Stone", Age = 22, Gender = "Female", Email = "ann@none.com" }
};
public Client GetbyId(int id)
{
foreach (Client client in this.clients)
{
if (client.Id == id)
{
return client;
}
}
return null;
}
public IList<Client> FindAll()
{
return this.clients;
}
}
}
The Traditional Way
An Effective Solution
Traditional ways to develop user interfaces tend to give it all responsibility of the data it handles, including: user input, data validation, error handling and database access. The following code creates the window, containing a list box to display available clients, and a series of text boxes showing additional data related to the client selected on the list box.
using System;
using System.Windows.Forms;
namespace Codenough.Demos.WinFormsMVP
{
public partial class ClientsForm : Form
{
private readonly ClientRepository clientsRepository;
public ClientsForm()
{
this.clientsRepository = new ClientRepository();
InitializeComponent();
BindComponent();
}
private void InitializeComponent()
{
}
private void BindComponent()
{
this.closeButton.Click += OnCloseButtonClick;
this.clientsListBox.SelectedIndexChanged += OnClientsListBoxSelectedIndexChanged;
this.clientsListBox.DisplayMember = "Name";
this.clientsListBox.ValueMember = "Id";
this.clientsListBox.DataSource = this.clientsRepository.FindAll();
}
private void OnClientsListBoxSelectedIndexChanged(object sender, EventArgs e)
{
var selectedClientId = (int)this.clientsListBox.SelectedValue;
var selectedClient = this.clientsRepository.GetById(selectedClientId);
this.clientNameTextBox.Text = selectedClient.Name;
this.clientEmailTextBox.Text = selectedClient.Email;
this.clientGenderTextBox.Text = selectedClient.Gender;
this.clientAgeTextBox.Text = selectedClient.Age.ToString();
}
private void OnCloseButtonClick(object sender, EventArgs e)
{
Application.Exit();
}
}
}
First thing I would like to mention here is that this example satisfies all acceptance criteria we established for the end-product. It shows the list of clients and each time one is selected, details of that particular client are displayed in the details group box.
So, what is so bad about this code? It does what it should and all in roughly 49 lines of code. Well, as frustrating it might seem, something effective is not always efficient. The previous piece of code breaks pretty much all aspects of good design and while it visually satisfies requirements, it cannot be covered by unit testing.
Testing What Cannot Be Tested
As I mentioned at the beginning of the article, unit test code is as important as the actual business source code. Let's define a couple test cases this application needs to pass in order to comply with acceptance criteria.
The clients application should:
- Show all clients in the list box when it loads.
- Show details of the first client in the list when it loads.
- Show details of a client and show it when the user select an item in the clients list box.
Now, let's write test method stubs for these:
using NUnit.Framework;
namespace Codenough.Demos.WinFormsMVP
{
[TestFixture]
public class WhenClientsWindowLoads
{
[Test]
public void ItShouldLoadAllClients()
{
}
[Test]
public void ItShouldShowFirstClientOnListDetails()
{
}
[Test]
public void ItShouldShowClientDetailsOnListItemSelected()
{
}
}
}
When looking at these tests, we can notice they are specific to the presentation portion of the application. We are not testing data access (the ClientRepository
class) or rendering-related stuff. By using the code example provided earlier, our tests depend on the stability of the actual Windows Forms libraries and the repository classes, thus, we would have to adapt our tests for cases when the ClientsRepository
class returns unexpected data or an environment-related exception occurs when rendering the window on-screen.
We simply can't perform propper testing of the source code as it is right now. At this point, code coverage has been compromised; not even talk about separation of concerns. The ClientForm
class does everything.
But fear not. Fortunately for us, some nerds locked up in a server room under a temperature of about -5 degrees celcius already encountered themselves in this predicament and figured out a way to make things better: The Model-View-Presenter pattern.
The Model-View-Presenter Way
The Model-View-Presenter pattern was hatched in early 90's at Taligent and popularized later on by a paper written by Mike Potel, Taligent's CTO. The pattern was later used in the Smalltalk user interface framework and adapted to Java when it started gaining popularity.
In a very general scope, the MVP pattern seeks to leverage the principles of separation of concerns, leaving each part composing the user interface code with a well-defined responsibility. For example, in our clients list application, the window should only care about showing data to the user on-screen; no data-access, no business logic.
The pattern is composed of three parts:
- Model: The model contains the actual information that will be displayed to the user. It is updated as the user inputs new information.
- View: As the name implies, the View is a group of graphical components used to display the Model information to the user. It allows the user to manipulate the Model data in a graphical and easy to understand manner.
- Presenter: The presenter acts as a middle-man between the View and Model. It has the responsibility of providing the View with the data from the Model whenever its requested by the user. It also validates data comming from the View that goes into the Model and then save it to the underlying data store (a database, text file, etc.) if needed.
You might be asking yourself what is so good about separating the presentation layer of an already multi-layered software architecture in even more parts. Well, the MVP pattern allows us to fully test the presentation layer of our application regardless of the other layers; business and data-access that is.
In the case of our application, all unit tests required to comply with acceptance criteria are intended to validate user interface initialization and logic flow, task the Presenter is in charge of; window rendering is problem of the View and model data-access is responsibility of the ClientsRepository
, which we don't need to test since we are not performing integration testing.
User Interface Design Redemption
Ok, time to make things right. First thing to do is to create the Model. The Model is generally a domain object containing information the View is intended to display. In this case our View will contain a list of clients, so the Model is actually the Client class.
namespace Codenough.Demos.WinFormsMVP
{
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
public string Email { get; set; }
}
}
As you can see, in this case, the Model is a really simple class composed only of properties (plain old C# object or "POCO" for the friends). It should not contain any validation logic or calculations of any kind; it has to remain as simple as possible.
Next step is to create an interface that will serve as a contract of whatever the View is capable to do so the Presenter is aware of it, since it will be communicating with it to serve view data and receive input.
using System;
using System.Collections.Generic;
namespace Codenough.Demos.WinFormsMVP
{
public interface IClientsView
{
event Action ClientSelected;
event Action Closed;
IList<ClientModel> Clients { get; }
ClientModel SelectedClient { get; }
void LoadClients(IList<ClientModel> clients);
void LoadClient(ClientModel client);
}
}
The interface only defines the methods needed by the Presenter so it can "puppet" it by providing data, validating anything before it gets back into the database or any other event triggered by the user. Is important to notice that it is composed of events that the view concrete implementation will trigger and the Presenter will respond to them.
Ok now, to complete the View portion, we need to create the concrete implementation of the view interface. In this case is our old Windows Forms class which has gone under a strict diet to reduce its functionality to the minimum.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Codenough.Demos.WinFormsMVP
{
public partial class ClientsForm : Form, IClientsView
{
public event Action ClientSelected;
public event Action Closed;
public ClientsForm()
{
this.InitializeComponent();
this.BindComponent();
}
public IList<ClientModel> Clients
{
get { return this.clientsListBox.DataSource as IList<ClientModel>; }
}
public ClientModel SelectedClient
{
get { return this.clientsListBox.SelectedItem as ClientModel; }
}
public void LoadClients(IList<ClientModel> clients)
{
this.clientsListBox.DataSource = clients;
}
public void LoadClient(ClientModel client)
{
this.clientNameTextBox.Text = client.Name;
this.clientEmailTextBox.Text = client.Email;
this.clientGenderTextBox.Text = client.Gender;
this.clientAgeTextBox.Text = client.Age.ToString();
}
private void BindComponent()
{
this.closeButton.Click += OnCloseButtonClick;
this.clientsListBox.DisplayMember = "Name";
this.clientsListBox.SelectedIndexChanged += OnClientsListBoxSelectedIndexChanged;
}
private void OnClientsListBoxSelectedIndexChanged(object sender, EventArgs e)
{
if (this.ClientSelected != null)
{
this.ClientSelected();
}
}
private void OnCloseButtonClick(object sender, EventArgs e)
{
if (this.Closed != null)
{
this.Closed();
}
}
}
}
Now the View implementation only loads data someone else provides and fires events as the loaded data changes so anyone out there listening will be aware of the changes and will take appropiate action. As you might have already guessed, the only component aware of the View events will be the presenter. So, the next step is to implement the infamous Presenter class.
The Presenter is a component that is aware of what is happening in the View through its events, which the Presenter has to be subscribed to so it can happen. As we mentioned before, the Presenter talks to the View by using its interface instead of the concrete implementation, this for the sake of testability. Later, we are going to provide the Presenter with a "mock" instead of the actual View implementation. A mock is an personalized implementation of an interface that is provided to another object which is going to use it like it is the real thing; but this time, we are the ones controlling what the mock is returning or what it is doing when receiving data. This way we don't have to worry about environment-specific rendering issues of the View but only about the Presenter logic.
using System.Linq;
namespace Codenough.Demos.WinFormsMVP
{
public class ClientsPresenter
{
private readonly IClientsView view;
private readonly ClientRepository clientsRepository;
public ClientsPresenter(IClientsView view, ClientRepository clientsRepository)
{
this.view = view;
this.clientsRepository = clientsRepository;
var clients = clientsRepository.FindAll();
this.view.ClientSelected += OnClientSelected;
this.view.LoadClients(clients);
if (clients != null)
{
this.view.LoadClient(clients.First());
}
}
public void OnClientSelected()
{
if (this.view.SelectedClient != null)
{
var id = this.view.SelectedClient.Id;
var client = this.clientsRepository.GetById(id);
this.view.LoadClient(client);
}
}
}
}
This time around, the one handling data access through the repository class is the Presenter. The View is no longer aware of where its data is comming from; thus remaining in compliance with the MVP pattern. Also, we can notice how the Presenter reacts each time a client is selected on the list box, since it has subscribed to the View ClientSelected
event.
Now, the only thing left to do is to wire-up everything so we can run this incredibly clever application which will save many companies from now on to the future. This wiring will take place at the Main method of the application, which will instantiate the repository classes, the View and the Presenter; then finally run the application.
using System;
using System.Windows.Forms;
namespace Codenough.Demos.WinFormsMVP
{
public static class Program
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var clientsForm = new ClientsForm();
var clientsRepository = new ClientRepository();
var clientsPresenter = new ClientsPresenter(clientsForm, clientsRepository);
clientsForm.Closed += () =>
{
Application.Exit();
};
Application.Run(clientsForm);
}
}
}
If you run the application at this point, you will see the clients list box populated just like the old application, only that this time all data comming back and forth the View is going through the Presenter. You can see in the code examples how each part has a single purpose: The Model stores data, the View shows data and the Presenter provides data.
Now, to conclude this article and show the real benefit behind this pattern, let's try again and implement our unit tests so we can verify our application meets all requirements.
Meeting Acceptance Criteria
Our application code is now ready for some unit testing. We had some already defined, which we could not implement at the time simply because the code was not fit for it. But now, using the MVP pattern on all glory, we are set for it. So, for unit testing I am using NUnit and Moq. Moq is a library that allows me to create mock implementations of interfaces (in this case the repository class and the View interface) without having to create additional classes.
Our first test is called ItShouldLoadAllClients
, implying that a clients list has to be loaded on Presenter class initialization. We first create a new method called SetUp that will run right before each time a test method runs. It will create mocks for the repository class and view interface. We then proceed to create an instance of the Presenter class, which is the test subject and then we use the Verify method of the mock that will throw an exception and make the unit test fail if the specified View method (LoadClients for this particular case) was never called during Presenter initialization.
using Moq;
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Codenough.Demos.WinFormsMVP
{
[TestFixture]
public class WhenClientsWindowLoads
{
private Mock<IClientsView> clientsViewMock;
private Mock<ClientRepository> clientsRepositoryMock;
[SetUp]
public void SetUp()
{
this.clientsViewMock = new Mock<IClientsView>();
this.clientsRepositoryMock = new Mock<ClientRepository>();
}
[Test]
public void ItShouldLoadAllClients()
{
var clientsPresenter = new ClientsPresenter(clientsViewMock.Object, clientsRepositoryMock.Object);
clientsViewMock.Verify(view => view.LoadClients(It.IsAny<IList<ClientModel>>()), "Expected clients to be loaded on initialization.");
}
}
}
Of course, this test passes. A call to the LoadClients
method is done at the Presenter constructor. The following tests are a little more complicated, since we will be setting up methods of the View and repository mocks to return what we need them to return.
Our next test is ItShouldShowFirstClientOnListDetails
, which requires our presenter to load the first client on the list after initialization.
For this case, we let the SetUp
method create the respective mocks and then at our test arrange phase we use the Setup method to make the repository mock return a sample list of clients when the FindAll method is called. Finally, we verify the LoadClient method of the view was called; making this the assertion that makes the test case pass of fail.
[Test]
public void ItShouldShowFirstClientOnListDetails()
{
var clients = new List<ClientModel>()
{
new ClientModel { Id = 1, Name = "Matt Dylan", Age = 28, Gender = "Male", Email = "mattd@none.com" },
new ClientModel { Id = 2, Name = "Anna Stone", Age = 22, Gender = "Female", Email = "ann@none.com" }
};
clientsRepositoryMock.Setup(repository => repository.FindAll()).Returns(clients);
var clientsPresenter = new ClientsPresenter(clientsViewMock.Object, clientsRepositoryMock.Object);
clientsViewMock.Verify(view => view.LoadClient(clients.First()), "Expected first client to be loaded on initialization.");
}
The last test is ItShouldShowClientDetailsOnListItemSelected
and finding out the way it works will be your homework, dear reader. This is the most complicated of them since now we use Mow to raise view events.
[Test]
public void ItShouldShowClientDetailsOnListItemSelected()
{
var clients = new List<ClientModel>()
{
new ClientModel { Id = 1, Name = "Matt Dylan", Age = 28, Gender = "Male", Email = "mattd@none.com" },
new ClientModel { Id = 2, Name = "Anna Stone", Age = 22, Gender = "Female", Email = "ann@none.com" }
};
clientsRepositoryMock.Setup(repository => repository.FindAll()).Returns(clients);
clientsRepositoryMock.Setup(repository => repository.GetById(1)).Returns(clients.First());
clientsViewMock.SetupGet(view => view.SelectedClient).Returns(clients.First());
var clientsPresenter = new ClientsPresenter(clientsViewMock.Object, clientsRepositoryMock.Object);
clientsViewMock.Raise(view => view.ClientSelected += null);
clientsViewMock.Verify(view => view.LoadClient(clients.First()), "Expected first client to be loaded on initialization.");
}
Conclusion
Making code testable is not easy at all. At least not at first, since you have to think on all the possible test scenarios your code could go through. But in the long term, this is what makes testable code more robust and clean than regular code hastly thrown together.
Okay, this makes it up for it. Hope you enjoyed it. Stay tuned for more talk on patterns and good practices.
Code Examples
Further Reading
CodeProject