Tuesday, August 16, 2005

Testing View Observer Using Stubs

I recently blogged about the View Observer pattern we used at my last client. The largest obstacle in using View Observer is the claim that Testing Events is not easy. While at the client we tested View Observer 3 different ways to determine which was best. All 3 ways were good for specific reasons; however, testing View Observer seemed easiest using stubs.

The first step is creating the stub class whose state you can use for testing. Often you will want getters and setters for each property and Raise methods for each event defined in the View Interface. This can be automated with code generation.
public class StubAlbumView : IAlbumView
{
private string[] albumListBoxItems;
private string titleTextBoxText;
private string artistTextBoxText;
private string composerTextBoxText;
private bool composerTextBoxEnabled;
private bool classicalCheckBoxChecked;
private int albumListBoxSelectedIndex;

public string TitleTextBoxText
{
get { return titleTextBoxText; }
set { titleTextBoxText = value; }
}

public string[] AlbumListBoxItems
{
get { return albumListBoxItems; }
set { albumListBoxItems = value; }
}

public string ArtistTextBoxText
{
get { return artistTextBoxText; }
set { artistTextBoxText = value; }
}

public string ComposerTextBoxText
{
get { return composerTextBoxText; }
set { composerTextBoxText = value; }
}

public bool ComposerTextBoxEnabled
{
get { return composerTextBoxEnabled; }
set { composerTextBoxEnabled = value; }
}

public bool ClassicalCheckBoxChecked
{
get { return classicalCheckBoxChecked; }
set { classicalCheckBoxChecked = value; }
}

public int AlbumListBoxSelectedIndex
{
get { return albumListBoxSelectedIndex; }
set { albumListBoxSelectedIndex = value; }
}

public event UserAction ApplyButtonClick;
public event UserAction CancelButtonClick;
public event UserAction ClassicalCheckBoxCheck;
public event TextChangedUserAction ArtistTextChanged;
public event TextChangedUserAction TitleTextChanged;
public event TextChangedUserAction ComposerTextChanged;
public event IndexChangedUserAction AlbumListBoxSelectedIndexChanged;

public void RaiseApplyButtonClick()
{
ApplyButtonClick();
}

public void RaiseCancelButtonClick()
{
CancelButtonClick();
}

public void RaiseArtistTextChanged(string arg)
{
ArtistTextChanged(arg);
}

public void RaiseTitleTextChanged(string arg)
{
TitleTextChanged(arg);
}

public void RaiseComposerTextChanged(string arg)
{
ComposerTextChanged(arg);
}

public void RaiseAlbumListBoxSelectedIndexChanged(int arg)
{
AlbumListBoxSelectedIndexChanged(arg);
}

public void RaiseClassicalCheckBoxCheck()
{
ClassicalCheckBoxCheck();
}
}

The interesting thing about the observer tests is that they assert the state of the view. Therefore, the observer is unit tested based on the values of the view changing, not how it changes those values. The end result is a Observer behavioral test using the View's state that isn't tied to the implementation of the Observer.

The tests validate the state of the View after Observer creation and after each event raised from the View.

[TestFixture]
public class AlbumObserverTest
{
[Test]
public void ObserverConstructorSetsViewsValues()
{
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, createAlbums());
Assert.AreEqual(view.AlbumListBoxItems,new string[] {"Title1","Title2","Title3"});
Assert.AreEqual(view.TitleTextBoxText, "Title1");
Assert.AreEqual(view.ArtistTextBoxText, "Artist1");
Assert.AreEqual(view.ComposerTextBoxText, "Composer1");
Assert.AreEqual(view.ComposerTextBoxEnabled, true);
Assert.AreEqual(view.ClassicalCheckBoxChecked, true);
Assert.AreEqual(view.AlbumListBoxSelectedIndex, 0);
}

[Test]
public void SelectedIndexChangeUpdatesView()
{
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, createAlbums());
view.RaiseAlbumListBoxSelectedIndexChanged(1);
Assert.AreEqual(view.TitleTextBoxText, "Title2");
Assert.AreEqual(view.ArtistTextBoxText, "Artist2");
Assert.AreEqual(view.ComposerTextBoxText, "");
Assert.AreEqual(view.ComposerTextBoxEnabled, false);
Assert.AreEqual(view.ClassicalCheckBoxChecked, false);
Assert.AreEqual(view.AlbumListBoxSelectedIndex, 1);
}

[Test]
public void ClassicalCheckBoxCheckUpdatesView()
{
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, createAlbums());
view.RaiseClassicalCheckBoxCheck();
Assert.AreEqual(view.ComposerTextBoxText, "");
Assert.AreEqual(view.ComposerTextBoxEnabled, false);
Assert.AreEqual(view.ClassicalCheckBoxChecked, false);
}

[Test]
public void TitleTextChangedUpdatesView()
{
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, createAlbums());
view.RaiseTitleTextChanged("new title");
Assert.AreEqual(view.TitleTextBoxText, "new title");
}

[Test]
public void ArtistTextChangedUpdatesView()
{
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, createAlbums());
view.RaiseArtistTextChanged("new artist");
Assert.AreEqual(view.ArtistTextBoxText, "new artist");
}

[Test]
public void ComposerTextChangedUpdatesView()
{
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, createAlbums());
view.RaiseComposerTextChanged("new composer");
Assert.AreEqual(view.ComposerTextBoxText, "new composer");
}

[Test]
public void CancelUpdatesView()
{
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, createAlbums());
view.RaiseComposerTextChanged("new composer");
view.RaiseTitleTextChanged("new title");
view.RaiseArtistTextChanged("new artist");
view.RaiseClassicalCheckBoxCheck();
view.RaiseCancelButtonClick();
Assert.AreEqual(view.TitleTextBoxText, "Title1");
Assert.AreEqual(view.ArtistTextBoxText, "Artist1");
Assert.AreEqual(view.ComposerTextBoxText, "Composer1");
Assert.AreEqual(view.ComposerTextBoxEnabled, true);
Assert.AreEqual(view.ClassicalCheckBoxChecked, true);
}

[Test]
public void ApplyUpdatesModel()
{
IAlbum[] albums = createAlbums();
StubAlbumView view = new StubAlbumView();
new AlbumObserver(view, albums);
view.RaiseTitleTextChanged("new title");
view.RaiseArtistTextChanged("new artist");
view.RaiseClassicalCheckBoxCheck();
view.RaiseApplyButtonClick();
Assert.AreEqual(albums[0].Title, "new title");
Assert.AreEqual(albums[0].Artist, "new artist");
Assert.AreEqual(albums[0].Composer, "");
Assert.AreEqual(albums[0].IsClassical, false);
view.RaiseClassicalCheckBoxCheck();
view.RaiseComposerTextChanged("new composer");
view.RaiseApplyButtonClick();
Assert.AreEqual(albums[0].Composer, "new composer");
}


private IAlbum[] createAlbums()
{
return new Album[]
{
new Album("Title1",true,"Artist1","Composer1"),
new Album("Title2",false,"Artist2",""),
new Album("Title3",false,"Artist3","")
};
}
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.