I’ve read about Test Driven Development, and after trying it out myself I have noticed a difference in the code that I write, even if I am not using it for a particular project. My classes have become simpler, my functions more atomic. I write more for the Open Closed principle, that my classes are closed for modification, but open for extension, and I find my projects are more organized and development seems to be quicker even, as the base classes get written quicker, and then additional features drop right into place as if they were expected, rather than the spaghetti code that I used to eventually end up with.
There is a difference between Unit tests and other kinds of tests. I have heard other developers say that they can’t test because most of their code is dependent upon some specific data layer, or web UI, and I think they might not know that Integration Testing is different than Unit testing. This was what I meant about changing my writing style. TDD prompted me to write smaller more abstract classes, with unit testable atomic functions, and then to write application specific interfaces and more specific inherited classes.
In some cases, though, your tests cross the line, and you need integration testing, but there are several Mocking libraries out there now that make it easy to do this. The main ones all appear to be similar. Here is a simple project where I used Moq to fake my Data Service. The Project had a few components. The end user UI was WPF, and was built with MVVM.
Because these UI related elements are decoupled from the actual UI in MVVM, I can unit test them. Here are some tests for the MainViewModel, which takes a user in Constructor Dependency Injection.
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void CanRestartServiceTest()
{
MainViewModel_Accessor target = new MainViewModel_Accessor(AdminUser);
Assert.IsTrue(target.CanRestartService());
target = new MainViewModel_Accessor(ReadonlyUser);
Assert.IsFalse(target.CanRestartService());
}
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void CanLoadAllTablesTest()
{
MainViewModel_Accessor target = new MainViewModel_Accessor(AdminUser);
Assert.IsTrue(target.CanLoadAllTables());
target._backgroundmanager.IsRunning = true;
Assert.IsFalse (target.CanLoadAllTables());
target = new MainViewModel_Accessor(ReadonlyUser);
Assert.IsFalse(target.CanLoadAllTables());
}
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void StartProgressBarTest()
{
MainViewModel_Accessor target = new MainViewModel_Accessor(AdminUser);
target.StartProgressBar();
Assert.IsTrue(target.ProgressVisibility == Visibility.Visible);
Assert.IsTrue(target.ProgressValue == 0);
}
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void BM_TimerFinishedTest()
{
MainViewModel_Accessor target = new MainViewModel_Accessor(AdminUser);
TimerFinishedEventArgs e = new TimerFinishedEventArgs();
e.Message = "TickTock";
target.BM_TimerFinished(new object(), e);
Assert.IsTrue(target.ProgressVisibility == Visibility.Hidden);
Assert.IsTrue(target.Dialog.Visibility == Visibility.Visible);
Assert.IsTrue(target.Dialog.Title.Contains("Exceeded"));
}
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void HandleErrorTest()
{
MainViewModel_Accessor target = new MainViewModel_Accessor(AdminUser);
target.HandleError(new Exception("Test Exception"));
Assert.IsTrue(target.Error.Visibility == Visibility.Visible);
Assert.IsTrue(target.Error.Message.Contains("Test Exception"));
}
I created all of my authorization features as properties on the VM, and then exposed Enabled in the UI to them, so I can test if the VM is Enabled to Admin users or Readonly users, as I do in CanRestartServiceTest. I also disable certain controls through this mechanism when running certain background jobs, and CanGetTablesTest tests that. I have a progressbar control, also with its own VM, and hook into it and expose a property called ProgressVisibility in the Main VM, so the StartProgressBarTest can test that it is working. This UI runs several Background jobs and I wrote a custom BackgroundJobManager class to manage all of them. BM_TimerFinishedTest tests against one of the behaviors of the manager as it is implemented in the VM. And HandleErrorTest tests against the DialogErrorVM I am using in the Main VM. So with MVVM it is possible to write unit tests of a sort for your UI components.
So, it’s great that I can in some way test the UI plumbing, but most of what this app does is interact with a windows service, a DTS package through a COM wrapper, and a Web service, and MVVM doesn’t help me test these integration layer features. In the past, since I have written each of these as interfaces, I would have to write test classes for each of these. These would be just stubs, returning dummy data. And that is fine, but with a Mocking library, you no longer have to write all those test classes. You can usually write up your mocked class and dummy results in a line or two. And there is a load of generic flexibility built into it.
Here I am setting up a mocked object based on my IDataService interface, with one instantiated method, StageAcorn(Datetime) which I set to take Any DateTime as an argument. My MVVM method takes an IDataService injected as an argument, and so I can now test my method, without writing any IDataService test stub.
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void StageTablesTest()
{
MainViewModel_Accessor target = new MainViewModel_Accessor(AdminUser);
var mock = new Moq.Mock<AUM.DataServiceReference.IDataService>();
mock.Setup<string>(f => f.StageAcorn(Moq.It.IsAny<DateTime>())).Returns("DTSER is Success");
target.StageTables(mock.Object);
Assert.IsTrue(target.Dialog.Visibility == Visibility.Visible);
Assert.IsTrue(target.Dialog.Message.Contains("Success"));
Assert.IsFalse(target.IsInError);
}
Here are a couple other similar, super simple tests also using mocked interfaces…
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void GetSavePathTest()
{
var mock = new Moq.Mock<IFileDialogService>();
mock.Setup(f => f.GetSaveFolder()).Returns("testfolderpath");
Assert.AreEqual("testfolderpath", MainViewModel_Accessor.GetSavePath(mock.Object));
}
[TestMethod()]
[DeploymentItem("AUM.exe")]
public void RestartServiceTest()
{
MainViewModel_Accessor target = new MainViewModel_Accessor(AdminUser);
var mock = new Moq.Mock<AUM.RestarterServiceReference.IRestarterService>();
mock.Setup(f => f.StopAcornAppService()).Returns(true);
mock.Setup(f => f.StartAcornAppService()).Returns(true);
target.RestartService(mock.Object);
Assert.IsTrue(target.ProgressVisibility == Visibility.Visible);
bool running = true; int testtimeout = 0;
while (running)
{
if (target.ProgressVisibility == Visibility.Hidden) running = false;
System.Threading.Thread.Sleep(100);
if (testtimeout++ > 200)
{
Assert.Inconclusive("Test Timeout");
running = false;
}
}
Assert.IsFalse(target.IsInError);
}
These are very simple examples, and not every method on every class is meant to be unit tested, but they show how easy it is to get started with MS testing projects.