By using the Model-View-Presenter pattern, Windows Forms 2.0, and automatic data binding, you can create a framework for unit testing the user interface. The pattern enforces a separation of concerns that keeps the UI thin, focusing only on visualization of data and binding to a presentation model. In this article, I present a basic calculator application that leverages this architecture and demonstrates the ease of unit testing from this framework. (The complete source code for the calculator is available electronically; see "Resource Center," page 5.)
For the purposes of this article, you can view the Model-View-Presenter (MVP) pattern as a means to decouple presentation logic from the actual view itself. The goal should be a simple 1-1 mapping between properties in a presenter class and properties in a view class. For example, for each text box display, there should be a corresponding property in the presenter class. If the output requires advanced calculation or formatting to occur prior to display, the model and presenter classes should take care of the data prior to updating the view. Likewise, simple edits should immediately notify the model and presenter of an update in order to drive a new display. In many cases, as well as our simple example, the model and presenter classes can merge into a heavy model class. Because there is little code required to convert the model into presentable data, the model can handle the dual responsibilities of data storage and presentation logic.
The Calculator Application
Figure 1 illustrates a basic calculator form bound to a calculator presentation model.
Each data element for display and edit from the view is described by a property, and each button-click action is represented by a parameterless method in the calculator model. By following the MVP pattern, you ensure that the view is focused on visualizing data as well as posting and receiving updates to and from the model.
Listing One includes the code for two of the calculator model's properties. In the listing, the setter for a property notifies consumers once a change has been committed. The Calculator class implements the INotifyPropertyChanged interface from .NET 2.0 in order to support firing events for property changes. In the case of Operand2, CanDivide is updated once Operand2 is updated, so two events are fired.
public class Calculator : INotifyPropertyChanged { public decimal Operand2 { get { return _operand2; } set { if (value == _operand2) return; _operand2 = value; OnPropertyChanged("Operand2"); OnPropertyChanged("CanDivide"); } } public bool CanDivide { get { return (this.Operand2 != 0); } } ... }
For these two model properties, there are analogous properties in the calculator form; see Listing Two. These properties in the view demonstrate the concern of visualizing this state of the model.
public partial class CalculatorForm : Form { public decimal Operand2 { get { return Convert.ToDecimal(_operand2TextBox.Text); } set { _operand2TextBox.Text = value.ToString(); } } public bool CanDivide { get { return _divideButton.Enabled; } set { _divideButton.Enabled = value; } } ... }