Showing posts with label Testing. Show all posts
Showing posts with label Testing. Show all posts

Wednesday, 17 June 2009

BDD Everywhere - don't underestimate your target audience

I have recently moved from doing TDD to BDD, using Scott's excellent SpecUnit.Net with, I have to say, some real success.

BDD was the subject matter being discussed at May's London Altnet beers. After the talk I was discussing with some people about their approach to doing BDD. What was being presented was the idea that BDD specs are useful only at the outer layers of the application and once you get more low level, you should revert to normal style TDD tests.

I don't agree. I have been writing BDD specs for every single class I write, and have recently upgraded our build system (rock on TeamCity) to make the spec reports. Saying BDD is just for the outer layers is ignoring the fact that the users of application know a lot more about their business than you do.

I'll give an example. We are building a system for the traders to view their trades - in what is known in the financial world as a "blotter". Being a WPF application, we build the ViewModels first and then build some sort of data access component. So the requirement is that all the trades made today should be displayed on the blotter when the user refreshes it. Here is a set of specs which say that

when the user refreshes the blotter and no trades have been made today
- should display a message saying there are no trades
- refresh button should be enabled

when the user refreshes the blotter and two trades have been made today
- should be two trades displayed in the blotter
- should display the earlier trade on the first row
- should display the later trade on the second row

So now we write some tests for that, and flesh out our implementation. During this time we create an interface ITradeProvider with a method GetTradesForToday(), and use a mocking framework to stub out the calls to that method using our favourite mocking framework.

Our user/customer/business analyst would definitely find those specs useful, and may even give some feedback or changes, which is great. But what about the TradeProvider implementation? Is that worth doing it for? Some would say no, but I would say. We could start off with something like this:

when loading todays trades
- should load todays trades
- should not load trades from a previous date

That's not really saying anything useful is it. Or is it? Imagine you show this to your users and/or business analysts. They're gonna ask:

"What on earth does that mean? When loading today's trades, should load today's trades? It doesn't make sense! We're a bank, we have loads of trade systems, which one are you getting the trades from?"

"Oh right, well I was going to get them from the MYZ team's Oracle database which we got the trades from on the last project I worked on"

"Ok, that's fine, but that's only going to work for trades outside of Asia. For the trades on the Asian stockmarkets, we're going to have to get the data from the AYN team located in Singapore. They have a web interface for looking at the trades and I think they provide some sort of service for you to retrieve it from"

"Oh OK, thanks! I'll update the specs and get back to you to review them again!"

You may think this is a fairly contrived example, but it's the sort of thing you will come up with time and time again. As developers, we move from project to project all the time, and we often make assumptions based on previous projects. Users, on the other hand, are getting you to build a system to provide value to their business. They are likely to know it a lot better than you! Business Analysts especially have a very detailed knowledge of all the internal systems and what their responsibilities are (though they may not have the kind of deep technical knowledge you have of communications protocols, etc).

So think twice about abandoning BDD for the lower layers of the system. Write the specs, share them, invite everyone you possibly can to review them. The earlier problems are found in a system, the cheaper they are to fix. That's been proven through countless studies.

Monday, 19 May 2008

MVC Architecting Silverlight Applications Part 3 - Testing the ViewModel

In Part 2 of this series, I built a small class used to model our Login page - LoginViewModel.

In this post, I will demonstrate how to unit test this class to show that it behaves as expected. This is pretty essential as we want to be sure the class works properly before handing it over to our designer to build a view for!

I will be using TestDriven.NET's new Silverlight unit testing support which I blogged about earlier. This is unbelievably simple - just download and install the project template and then create a new Silveright NUnit Project called Silversocial.Client.Modules.Test

Building some test doubles


Before we can start unit testing our LoginViewModel, we need to fake out the dependencies. Given that there currently exists no mocking framework for Silverlight ( are you listening Ayende? ;-> ) we will manually build some stubs for our dependencies and then reuse them in our tests.

It is quite easy to way manually to build reusable stubs for an interface of your choosing. I simply follow these rules:
  • Use "explicit interface implementations" for all members

  • Implement properties as normal properties with a backing field (or as automatic properties if you'd prefer)

  • Implement methods by delegating the entire method body to a public event handler with the same signature and name as the method. Set the event handler to empty delegate to avoid null checks

  • Implement events by delegating the add/remove to a public event handler with the same signature as the event. Set the event handler to empty delegate to avoid null checks


Here is the code for the stub for the IAppShell interface.

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class AppShellStub : IAppShell

    {

        private User user;

        private IView view;

 

        public User User

        {

            get { return this.user; }

        }

 

        public IView View

        {

            get { return this.view; }

        }

 

        User IAppShell.User

        {

            get { return this.user; }

            set { this.user = value; }

        }

 

        void IShell.SetView(IView view)

        {

            this.view = view;

        }

    }

}


And now the stub for the ILoginDataProvider, also following the guidelines set above:

using System;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class LoginDataProviderStub : ILoginDataProvider

    {

        private EventHandler<ValidateUserCompletedEventArgs> validateUserCompleted = delegate { };

 

        public Action<User> ValidateUser = delegate { };

 

        public void RaiseValidateUserCompleted(ValidateUserCompletedEventArgs args)

        {

            this.validateUserCompleted(this, args);

        }

 

        event EventHandler<ValidateUserCompletedEventArgs> ILoginDataProvider.ValidateUserCompleted

        {

            add { this.validateUserCompleted += value; }

            remove { this.validateUserCompleted -= value; }

        }

 

        void ILoginDataProvider.ValidateUser(User user)

        {

            this.ValidateUser(user);

        }

    }

}


We will create a "ViewStubBase" which implements the IView interface itself. This will form the base class for all other stubs of the views.

using System;

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public abstract class ViewStubBase : IView

    {

        public Action OnLoad = delegate { };

        public Action OnUnload = delegate { };

 

        void IView.OnLoad()

        {

            this.OnLoad();

        }

 

        void IView.OnUnload()

        {

            this.OnLoad();

        }

    }

}


And now the stub for the ILoginView interface. As you can see we just implement the LoginUnsuccessful method and call a public event handler which does nothing by default. The allows the test to listen to the method being called if it wants to, and can for example validate if it is called or not.

using System;

 

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class LoginViewStub : ViewStubBase, ILoginView

    {

        public Action LoginUnsuccessful = delegate { };

 

        void ILoginView.LoginUnsuccessful()

        {

            this.LoginUnsuccessful();

        }

    }

}


The other views are just empty classes for now. The first:

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class FriendListStub : ViewStubBase, IFriendListView

    {

    }

}


And the other...

namespace Silversocial.Client.Modules.Tests.Stubs

{

    public class RegisterStub : ViewStubBase, IRegisterView

    {

    }

}


The test fixture



Finally it's time to implement the test fixture, just using the standard NUnit syntax for defining the tests and setup method. The SetUp creates all the stubs, so they can be used by each test.

Note I am using the "AAA" pattern which was recently coined by Ayende for RhinoMocks 3.5 - I really like it! This is the way I have written most of my tests in the past, and it's nice to have a formal name for the stages undergone (especially when it's an alliterism)

Here are the tests:

using NUnit.Framework;

using Silversocial.Client.Modules.Tests.Stubs;

using Silverstone;

 

namespace Silversocial.Client.Modules.Tests

{

    [TestFixture]

    public class LoginViewModelTester

    {

        private LoginViewStub loginViewStub;

        private AppShellStub shellStub;

        private FriendListStub friendListStub;

        private RegisterStub registerStub;

        private LoginDataProviderStub dataProviderStub;

        private LoginViewModel viewModel;

 

        [SetUp]

        public void SetUp()

        {

            this.shellStub = new AppShellStub();

            this.friendListStub = new FriendListStub();

            this.registerStub = new RegisterStub();

            this.dataProviderStub = new LoginDataProviderStub();

            this.viewModel = new LoginViewModel(shellStub, dataProviderStub, friendListStub, registerStub);

 

            this.loginViewStub = new LoginViewStub();

            ((IViewModel) this.viewModel).SetView(this.loginViewStub);

        }

 

        [Test]

        public void Login_WithoutUsernameAndPassword_CannotExecute()

        {

            Assert.IsFalse(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_WithUsernameAndPassword_CanExecute()

        {

            // Arrange

            this.viewModel.User.Username = "abc";

            this.viewModel.User.Password = "abc";

 

            // Assert

            Assert.IsTrue(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_BeforeCompleted_IsLoggingInIsTrue()

        {

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.IsTrue(this.viewModel.IsLoggingIn);

        }

 

        [Test]

        public void Login_AfterCompleted_IsLoggingInIsFalse()

        {

            // Act

            this.viewModel.Login.Execute(null);

            this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

 

            // Assert

            Assert.IsFalse(this.viewModel.IsLoggingIn);

        }

 

        [Test]

        public void Login_AfterCompleted_RaisesCanExecuteChangedEvent()

        {

            // Arrange

            bool canExecuteChangedCalled = false;

            this.viewModel.Login.CanExecuteChanged +=

                delegate { canExecuteChangedCalled = true; };

 

            // Act

            this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

 

            // Assert

            Assert.IsTrue(canExecuteChangedCalled);

        }

 

        [Test]

        public void Login_WhilstLoggingIn_CannotExecute()

        {

            // Arrange

            this.viewModel.User.Username = "abc";

            this.viewModel.User.Password = "abc";

            this.viewModel.IsLoggingIn = true;

 

            // Assert

            Assert.IsFalse(this.viewModel.Login.CanExecute(null));

        }

 

        [Test]

        public void Login_Always_CallsValidateUserOnDataProvider_WithCorrectUser()

        {

            // Arrange

            User userSentToDataProvider = null;

            this.dataProviderStub.ValidateUser +=

                u => userSentToDataProvider = u;

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.viewModel.User, userSentToDataProvider);

        }

 

        [Test]

        public void Login_WithInvalidDetails_CallsLoginUnsuccessfulOnView()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(false));

            bool loginUnsuccessfulCalledOnView = false;

            this.loginViewStub.LoginUnsuccessful +=

                () => loginUnsuccessfulCalledOnView = true;

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.IsTrue(loginUnsuccessfulCalledOnView);

        }

 

        [Test]

        public void Login_WithValidDetails_SetsFriendListViewOnShell()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(true));

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.friendListStub, this.shellStub.View);

        }

 

        [Test]

        public void Login_WithValidDetails_SetsUserOnShell()

        {

            // Arrange

            this.dataProviderStub.ValidateUser +=

                u => this.dataProviderStub.RaiseValidateUserCompleted(new ValidateUserCompletedEventArgs(true));

 

            // Act

            this.viewModel.Login.Execute(null);

 

            // Assert

            Assert.AreEqual(this.viewModel.User, this.shellStub.User);

        }

 

        [Test]

        public void Register_Always_SetsRegisterViewOnShell()

        {

            // Act

            this.viewModel.Register.Execute(null);

 

            // Assert

            Assert.AreEqual(this.registerStub, this.shellStub.View);           

        }

    }

}


And here you can see them running in the Resharper test runner - they all pass!



Now I can confidently pass my ViewModel to my designer to build a view against...

Or can I? Without a data provider, he's going to have problems creating and testing his view. This will be the subject of the next post.

Tuesday, 13 May 2008

TestDriven.NET adds NUnit support for running Silverlight tests

This is great news from Jamie Cansdale - Silverlight NUnit Projects

He has managed to get a plugin working for NUnit which will run tests compiled for Silverlight directly!

I was previously using the "in-browser" testing framework provided by Jeff Wilcox. As great as this is, it's always nice to be able to run tests directly from the IDE, and this will finally provide it for Silverlight.

So... it looks like I'm going to have to update my Silverlight samples (which I am in the middle of writing) to use this new plugin. These are taking longer than expected but will definitely be published soon - and hopefully before I have to update them again for SL Beta 2!

Oh - and it even works with the Resharper test runner!

Good work Jamie!