Test-Driving a Web Form


By Ingemar Lundberg

How can you unit testand thereby also use TDD foryour GUI?

In this short section I'll try to give you a general idea of how you can unit test your GUI. In the process, techniques such as self-shunting [Feathers Self-Shunt] and mocking will be shown. However, the TDD process in itself is not described. Self-shunting is (very briefly) a technique where you're letting the test class also act as a stub or mock.

Background

Typical GUI code consists of a bunch of callback methods that are called as a result of something the user does with the GUI, such as pressing a button. Typically, a callback method does something with a domain object (or possibly even executes some SQL). The problem is that the callback methods get called from an environment, such as WinForms or ASP.NET, that isn't easy to hook up in your NUnit tests. You simply can't do it.

The goal is to have as little untested code as possible. The first thing to do is to move as much code as possible out of the callback methods. If the code isn't tangled in the callback methods, you've created the possibility to unit test it.

But that isn't enough. What about the state of the GUI? You know, things like if the user has selected that row, this and that button should be disabled. This will not be covered simply by moving code out of the callback methods. Don't get me wrongit is a good first step.

To take it further, we need to use the sharpest tool in our box: abstraction. We need a model of the interaction, and we need an abstraction of the form we're programming. At this point, I'll stop talking about the GUI in general terms and start talking about a form, or more specifically about an ASP.NET Web Form. However, this technique is also applicable to Windows Forms, and it is even platform neutral.

An Example

This is, in my opinion, by far the hardest part of writing about programmingfinding a problem scenario that could be used to illustrate your point without sidetracking the reader. I need an example that makes my GUI change state; in other words, something needs to be disabled when the user does something special.

In Sweden we have the tradition of only letting our kids have candy on Saturdays. Let's say that I have one box of chocolate bars, one of lollipops, and one of portion-sized peanut bags. The kids can have three goodies of their choice, but no more than two of the same kind. A simplification of the model is that we never run out of candy.

This is without doubt another silly example, but hopefully it will do its job. In Figure 11-2 you can see the end result. I could have shown you my original pencil sketch of the GUI, but this will do too. In the left pane we have all the types of candy available. It is from this pane that the user (kids of all ages) picks candy for their Saturday candy stash, shown in the right pane. You can see how you're prevented from picking another chocolate bar while you might pick a lollipop or a bag of peanuts.

Figure 11-2. Choices, limitations, and selections


The reader is kindly asked to disregard the lack of styling, which isn't covered here (and I should not express any opinions whatsoever on that subject).

Domain Model

To be able to move on with today's lesson, I need a domain model. It is shown in Figure 11-3.

Figure 11-3. The Domain Model


I've tested this model separately. OkToAdd() checks the business rules I stated previously. It's OK to add three items, but only two of the same type (CandyType). This isn't rocket science.

TDD of GUI

Finally, we're coming to the point. Before we start, I'd like to remind everyone that even though we're introducing an abstraction of the form, we have a pretty concrete picture of how it is going to work, as shown in Figure 11-4.

Figure 11-4. Model of the UI


Display Candy Types

When the form is first shown, we want to be able to fill the left pane with names of all the types of candy. It should be possible to pick from all of the sources. The test looks like this. (I'm going to take a fairly big step here based on my previous experience of this, dare I call it, pattern.)

[Test] public void FillSourcePanel() {     mdl.Fill();     mdl.Render();     // using A=NUnit.Framework.Assert     A.AreEqual(SrcPanel, "a> b> c> "); }


It doesn't even compile. I need to add Fill() and Render() to PickCandyMdl. That's easy; I just add two empty public methods, one named Fill() and one named Render(). But what is the test expressing? What am I asserting? The assertion is expressing what the "form" should look like nowin a very abstract way. The string SrcPanel is used instead of the HTML table (the left one in the form) that we're aiming for at the end.

Where does SrcPanel come from? Well, SrcPanel is a string in the test fixture (the class) that is manipulated by my self-shunted [Feathers Self-Shunt] implementation of the PickCandyView. This is the recurring pattern when I mock the view.

[TestFixture] public class PickCandyTests: PickCandyView {     void PickCandyView.SourceItem(string text, bool pickable)     {         SrcPanel += text + (pickable ? ">"             : string.Empty) + " ";     }        PickCandyMdl mdl;     string SrcPanel;     [SetUp]     protected void Setup()     {         mdl = new PickCandyMdl();         SrcPanel = string.Empty;     } ...snip...


It compiles, but it turns red. What's missing? Well, the form model, mdl of type PickCandyMdl, needs to know about its view. That's easy! I add a SetView() method to the model. (By the way, this is one of the few occasions where I use "interface implementation" the way I did with PickCandyView.SourceItem(), as in {interface name}.{member name}.)

Private or Public Implementation of Interface Methods?

Implementation of methods can only exist on classes. If a class, Foo, implements an interface, Bar, the interface's methods are a part of that class. I would in most cases implement Bar's members as public on Foo. Remember that the interface of the class itself is all its public members. Foo is a Bar, hence the members of Bar are a part of Foo. It just so happens that I implement an interface's members as private in other situations as a means to solve name collisions, as in the case of a self-shunting mock. Here I don't actually see the interfaces (PickCandyView and CandySourceRetriever) as a part of the test fixture's interface.


The form model needs to know how to obtain all Candy Sources. This could be easily fixed by just supplying the model with a list of Candy Sources. But instead I'm going to show you another little trick that improves testability in the general case. I'm letting the form model (or rather that package/assembly) own an interface representing the needed retrieve operation.

public interface CandySourceRetriever {     IList Retrieve(); }


Why Not the I-name Convention for Interfaces?

Let me ask youwhat good would it do you? This convention came from an environment where the notion of an interface itself was a convention, a rule, and not a first-class language concept (that changed, more or less, with midl). When it comes to trying to instantiate an interface (with new) the C# compiler (at least my version) treats abstract classes and interfaces as one and the same.

[View full width]

public interface Bar {} public class Foo { public static void Main() {new Bar();} } ingo.cs(2,47): error CS0144: Cannot create an instance of the abstract class or interface 'Bar'


Why do we not have an A-name convention for abstract classes? Because when you refer to a type that has methods and properties, you don't care if it is an interface, abstract class, or concrete ditto. The I-name convention seems to have been adopted by the Framework Class Library (FCL) without further consideration.

Alternative Explanation/Rambling

The I-name convention made sense in COM. COM wasn't really object-oriented, was it? It was (well, is) component-oriented. Is there a reason to treat interfaces of components as something inherently different from the component itself? Maybe, but the question to ask is if interfaces in FCL are interfaces to components or if they are the answer/compromise to the difficult (compiler) problem of multiple inheritance. If so, an interface in CLR is very close to an abstract class. There's no A-name convention for abstract classes. Drop the I-name convention; it serves no purpose. When you refer/use a type that has methods and properties, you don't care if it is an interface, abstract class, or concrete ditto.


I'll also mock this interface as a self-shunt. Of course, the form model needs to know about its retriever. With all this in account, the test fixture starts as the following:

[TestFixture] public class PickCandyTests: PickCandyView, CandySourceRetriever {     void PickCandyView.SourceItem(string text, bool pickable)     {         SrcPanel += text + (pickable ? ">"             : string.Empty) + " ";     }     IList CandySourceRetriever.Retrieve()     {         return sources;     }     PickCandyMdl mdl;     ArrayList sources;     string SrcPanel;     [SetUp] protected void Setup()     {         mdl = new PickCandyMdl();         mdl.SetView(this);         mdl.SetRetriever(this);         sources = new ArrayList();         sources.Add(new CandySource("a")); // think chocolate         sources.Add(new CandySource("b")); // ... lollipop         sources.Add(new CandySource("c"));         SrcPanel = string.Empty;     } ...snip...


Of course, the test still comes out red because we haven't implemented anything in PickCandyMdl yet. Let's do it now.

[Serializable] public class PickCandyMdl {     public void Fill()     {         _retrieved = _retriever.Retrieve();     }     public void Render()     {         foreach(CandySource s in _retrieved)             _view.SourceItem(s.CandyType, true);     }     public void SetView(PickCandyView v)     {         _view = v;     }     public void SetRetriever(CandySourceRetriever r)     {         _retriever = r;     }     [NonSerialized] PickCandyView  _view;     CandySourceRetriever _retriever;     IList _retrieved; }


As you can see, most of this is trivial. In Fill() we pick up the available candy sources and in Render() we iterate over them passing information to the view for it to display. Note that we've faked the implementation so far; the candy sources are not going to be "pickable" (the true constant) in all circumstances. SetView() and SetRetriever() are trivial. The [NonSerialized] attribute on _view is needed when you use out of process handling of the Session state in the ASP.NET application.

When we run the test now, it turns green. Hooray!

Picking Candy

We need to be able to pick candy from the source list. Let's express that as a test.

[Test] public void PickingCandy () {     mdl.Fill();     mdl.PickAt(0);     mdl.Render();     A.AreEqual(SrcPanel, "a> b> c> ");     A.AreEqual(StashPanel, "a "); }


StashPanel is of course the test fixture representation of the right-hand panel displaying what you (I mean your kids) have picked so far (a HTML table in the real form). A new method is needed, PickAt().We also need a new method on the view.

public interface PickCandyView {     void SourceItem(string text, bool pickable);     void StashItem(string text); }


PickAt() is an excellent incitement to introduce CandyStash to the form model, only hinted at as _stash in the following code:

public void PickAt(int index) {     Candy c = ((CandySource)_retrieved[index]).GrabOne();     _stash.Add(c); }


We also need to complement Render().

public void Render() {     foreach(CandySource s in _retrieved)         _view.SourceItem(s.CandyType, true);     foreach(Candy c in _stash)         _view.StashItem(c.CandyType); }


I'm sure you can implement the StashItem() method the same way I implemented SourceItem().

Running the test now takes us to green again. Before I move on, I complement my first test to assert that the StashPanel is empty (string.Empty).

Handling the Display Dynamics

When we picked two of the same candy type, that type is not supposed to be "pickable" anymore (have a look at the first figure of the application again). Let's express that as a test.

[Test] public void PickingTwoOfSameKind() {     mdl.Fill();     mdl.PickAt(0); mdl.PickAt(0);     mdl.Render();     A.AreEqual(SrcPanel, "a b> c> ");     A.AreEqual(StashPanel, "a a "); }


The stash of candy now contains two "a"s. The missing greater than symbol (>) in SrcPanel after the "a" is the key thing here. This test doesn't introduce any new methods, but it comes out red due to the shortcut implementation of Render() we made earlier. Remember the true constant? Isn't the OkToAdd() method on CandyStash exactly what we're looking for here?

public void Render() {     foreach(CandySource s in _retrieved)         _view.SourceItem(s.CandyType, _stash.OkToAdd(s.GrabOne()));     foreach(Candy c in _stash)         _view.StashItem(c.CandyType); }


I grab one from the source (s.GrabOne()) and pass it to OkToAdd() to see if it is "pickable." The CandySource is an infinite source (it is actually an object factory). In real life you'd probably rather die than waste chocolate bars like this, but this isn't real life, this is software.

If we picked three of an allowed mix, we're not allowed to pick anything else, right? Let's express that as a test, too.

[Test] public void PickingThreeMix() {     mdl.Fill();     mdl.PickAt(0); mdl.PickAt(0); mdl.PickAt(1);     mdl.Render();     A.AreEqual(SrcPanel, "a b c ");     A.AreEqual(StashPanel, "a a b "); }


This turns green right away. The OkToAdd() check for "pickable" was a stroke of genius, don't you think? We shouldn't be surprised, because it (OkToAdd()) captures the very essence of our business rule.

The Web Form Implementation

There are several ways to handle state in a Web application. In this scenario, I've disabled view state on the document form and am relying solely on the Session object on the server. I build the page upon hitting the Web form, and I rebuild it just before ending the processing (in the PreRender event) to reflect the model's current state. Remember that the model's state changes when we pick candy for our stash.

For this I've added a call to MyInit() first in the standard OnInit() method of the Web form created by Visual Studio .Net.

void MyInit() {     if (!IsPostBack)     {         _mdl = new PickCandyMdl();         Session["PickCandyMdl"] = _mdl;         _mdl.SetRetriever(CreateRetriever());         _mdl.Fill();     }     else     {         _mdl = (PickCandyMdl)Session["PickCandyMdl"];     }     _mdl.SetView(this);     MdlRender(); }


PickCandyMdl _mdl; HtmlTable _srcTbl, _stashTbl; void MdlRender() {     _srcTbl = new HtmlTable();     srcpnl.Controls.Add(_srcTbl);     _srcTbl.Width = "100%";     _stashTbl = new HtmlTable();     stashpnl.Controls.Add(_stashTbl);     _stashTbl.Width = "100%";     _mdl.Render(); } private void WebForm1_PreRender(object sender, System.EventArgs e) {     srcpnl.Controls.Clear();     stashpnl.Controls.Clear();     MdlRender(); }


The srcpnl, the candy source panel, and stashpnl, the candy stash panel, are Panels (the control in the WebControls namespace) I've added with the form designer in Visual Studio.

Of course, the form implements PickCandyView. The code is fairly simple. Any decisions made (if statements) concern appearance only, not the state of the interaction. This is crucial. The model must be responsible for the latter.

void PickCandyView.SourceItem(string text, bool pickable) {     int index = _srcTbl.Rows.Count;     HtmlTableRow tr = AddTR(_srcTbl);     HtmlTableCell td = AddTD(tr);     td.InnerText = text;     td = AddTD(tr);     if (pickable)     {         LinkButton lb = new LinkButton();         td.Controls.Add(lb);         lb.ID = "pick@" + index.ToString();         lb.Text = ">>";         lb.Click += new EventHandler(Pick_Click);     } } void PickCandyView.StashItem(string text) {     AddTD(AddTR(_stashTbl)).InnerText = text; }


And finally, here's the pick callback method:

private void Pick_Click(object sender, EventArgs e) {     _mdl.PickAt(IndexFromID(sender)); }


Yes, I trust you to read between the lines. The only possibly non-trivial thing I left for you to figure out is the CreateRetriever() method. You've seen something similar to what's needed in the test fixture.

Figure 11-5 shows the app in sort of a comic strip. The kid picks two chocolate bars followed by a bag of peanuts.

Figure 11-5. The candy-picking application


Summary

Most code for handling the GUI is in a model of the interaction. Only a small portion of trivial code is left untested. Graphical appearance is, however, not what's tested with this methodology but rather the dynamics of the GUI.

In this quite unfinished example I got away with very little handling in the form model. Wrapping the candy picked in, say, a CandyItem object is one possible development if we decided to add features for regretting a choice (return to the source).

Mocking with NMock

Let me, as a small addendum and appetizer, show you another way to mock during testing using NMock [NMock]. I'll be very brief, letting the test code speak for itself allowing you to compare it with the previous tests. Let's first have a look at the setup. Let me just point out that the test fixture class doesn't implement the interfaces PickCandyView and CandySourceRetriever as it did earlier in our self-shunting fixture.

PickCandyMdl mdl; IMock data, view; const bool Pickable=true, NotPickable=false; [SetUp] protected void Setup() {     data = new DynamicMock(typeof(CandySourceRetriever));     view = new DynamicMock(typeof(PickCandyView));     mdl = new PickCandyMdl();     mdl.SetView((PickCandyView)view.MockInstance);     mdl.SetRetriever((CandySourceRetriever)data.MockInstance);     ArrayList sources = new ArrayList();     sources.Add(new CandySource("a")); // think chocolate     sources.Add(new CandySource("b")); // ... lollipop     sources.Add(new CandySource("c"));     data.SetupResult("Retrieve", sources); }


The instantiation of DynamicMock(s) does the magic and creates objects adhering to the interfaces specified. The dynamically "coded and compiled" object of a mock is found as IMock.MockInstance. With SetupResult(), we tell the data mock to return sources when it gets called with Retrieve().

Now let's have a look at the equivalence of the first test from earlier.

[Test] public void FillSourcePanel() {     // Expectations     view.Expect("SourceItem", "a", Pickable);     view.Expect("SourceItem", "b", Pickable);     view.Expect("SourceItem", "c", Pickable);     view.ExpectNoCall("StashItem", typeof(string));     // Execution     mdl.Fill();     mdl.Render();     // Verification     view.Verify(); }


The comments in the code are there just to show you the general phases, besides setup, when testing with NMock. We declare that we're expecting SourceItem () of PickCandyView to be called three times and what values we expect. We also state that StashItem () is not to be called.

In the execution phase we, well, do the execution of the object we're testing by calling some methods on it. And finally, we ask the view mock to verify that our expectations were met during execution.

I think it is best to follow the red-green (-refactor) formula of TDD. In the previous test at least, if you forget to call Verify() you'll end up with a green test regardless of whether the expectations are met or not. If you first require a red run, you're sure that you're actually verifying (asserting) something.

Thanks, Ingo!

Again, testing is an extremely important factor and something worth preparing and adjusting for. Now we have discussed two different variations regarding the UI, but with similarities on how that can be done.

Last but not least, we will discuss some aspects regarding the data for the UI in contrast to the data in the Domain Model.

Some might feel my approach is "I have a hammer; let's find nails" when talking about mapping for the presentation services just because mapping is a popular solution for the persistence services. But I think it's very much in the spirit of DDD to focus on the core, the Domain Model, as much as possible and then create simple, flexible solutions for dealing with the infrastructural problems "around" the Domain Model, such as transforming the Domain Model into another model for another tier. That's why I think object-to-object mapping and UI mapping have a lot of potential, especially for routine tasks that are just a waste of time to hand code.

In this section, Mats Helander discusses some aspects of UI Mapping, focusing on automatic mapping between a presentation friendly model (such as a Presentation Model [Fowler PoEAA2]) and the Domain Model. Over to Mats.




Applying Domain-Driven Design and Patterns(c) With Examples in C# and  .NET
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET
ISBN: 0321268202
EAN: 2147483647
Year: 2006
Pages: 179
Authors: Jimmy Nilsson

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net