My job as a software consultant can be a little vague. Sometimes I’m coaching a team. Sometimes I’m training a company on how to use Team Foundation Server (TFS). Sometimes I’m an architect or developer. The thread that seems to go through all of these different types of work is that I’m almost always evangelizing Test-Driven Development (TDD) and unit testing. Two of the hurdles that I see again and again are 1) convincing developers to write any tests at all and 2) convincing developers who are already writing tests to re-evaluate their process and write unit tests instead of integration tests.
For both groups, I think the cause of the difficulty is some combination of inertia and fear. Inertia in that they’re comfortable doing what they do and how they do it. Fear in that they’re — like a lot of people (me included) — nervous about and resistant to change. The odd thing is that there aren’t too many developers who think that writing tests is a bad idea — they’re generally sold on the idea of writing tests — it’s more that that they don’t know how to start and, therefore, either don’t write tests or do a sub-optimal job writing their tests.
When I see teams doing Silverlight development and forgoing tests, it’s even more frustrating because they’re usually starting from scratch. It’s not like they’re sitting on a mountain of legacy Silverlight code. They’re starting clean and that’s the best time to try to nail how you write tests. If they skip the unit tests, they’re missing a huge opportunity to raise their game and make their lives a lot easier. I think there’s some kind of half-spoken, mistaken assumption that they can always come back and add tests later. Whether or not you focus on design for testability makes a huge difference in the core architecture of the application and if you don’t get that right at the beginning, it’s going to be much harder to fix later.
Disclaimer
Before I get in to my process, I’ll admit that I’m sure that my process could be improved. Unit testing and design for testability is one of those things that you work on over time. It takes practice. The tests that I’m writing now are definitely different from what I was writing a year ago. The scary thing is that 1 or 2 years ago I thought I knew what I was doing and thought I was pretty good at unit testing but now I think that what I was doing is kind of sad. So, extrapolating out from right now — right now I think I know what I’m doing and I think I’m pretty good at writing tests — I’m pretty much guaranteed in a few years to think that what I’m doing currently is kind of sad, too.
My Process: Start From The User Interface
So, assuming that I’m writing a Silverlight application, what’s my process?
Well, I almost never start with a database design. Instead, I start at the user interface. I draw some pictures on paper, on a whiteboard, or maybe even use SketchFlow. Anything to get thinking about what I need to build.
Let’s say that we’re supposed to build a page that lets us create and edit person information. It’s fairly straight-forward and has fields for Id, First Name, Last Name, and Email Address.
Why start at the user interface?
I’m starting at the user interface because 1) I instantly have something that I can show to my customer and 2) I’m avoiding getting bogged down in implementation details.
It’s important to be able to iterate with your customer quickly so that you can figure out if you’re building the right thing. Let’s say that you chose to start from the database and write toward the user interface. As you go, you’re making decisions and implementation choices about what the application is going to do and how it’s going to do it. Chances are high that your customer going to understand or even care about what you’re building when you’re at the lowest levels of the application. The *will* care about the user interface though but in order to get a user interface to show to the customer, you’re first going to go write a ton of implementation code. Next, you show the customer what you have in order to get their feedback. They’re almost definitely going to say “no that’s not what I want — go change X, Y, and Z.” Are you going to throw away all that code that you wrote that’s basically wrong? Doubtful. Instead, you’re going to pound the customer’s changes in to what you originally THOUGHT you were supposed to build rather than making it work exactly right from the beginning. Now your system design is a little bit weird. Repeat this process 50 times during the lifecycle of your project and all those pieces of “a little bit weird” add up to mountains of “a whole lot weird”.
Starting at the database also has some negative effects on your unit testing and the overall testability of the application because you’re always going to know too much about how the other layers work. Writing good unit tests is about not knowing — or at least pretending to not know — how any other pieces of the application do their job.
If you started writing from the database toward the UI. When you write that unit test for PersonDetailViewModel’s Save() method, you already know precisely how you’re going to get that saved because you’ve already written all the code to get that save working. So, since you’ve got that done, you might as well run the unit test for PersonViewModel.Save() through all that other code, too, right? Sure. Why not? When PersonViewModel.Save() gets called, it’s going to populate the Model objects, create an instance of PersonDomainService, call it’s save method, which is going to call in to some data access class and that’s going to call either the Person_Insert or Person_Update stored procedure, etc etc etc.
Here’s what you should be doing. If you’re working on unit tests for PersonDetailViewModel’s Save() method, you should be thinking about testing only what has to happen inside of PersonDetailViewModel. That probably means that you’ll get the Model populated from the ViewModel and then call some interface like IPersonDomainService to get the persistence logic kicked off. You absolutely don’t care what kind of concrete object IPersonDomainService is or how that instance of IPersonDomainService is going to get the saving done. You call it and what happens after that is not your concern. Well, that’s not quite exactly right. You call IPersonDomainService and what happens after that isn’t your concern within the context of writing the unit tests for PersonDetailViewModel. When you go write your tests for whatever implements IPersonDomainService, *then* it’s your concern but that’s another unit test class that you’ll write later…and when you write those tests, you’ll forget everything you know about the existence of the PersonDetailViewModel.
Can you spot the difference between the two approaches? One way has a lot fewer dependencies and focuses on just the logic that needs to be tested. They call them “unit tests” because they focus on small pieces (aka. “units”) of functionality. The other way has a ton of dependencies (including a dependency on a running instance of SQL Server) and runs a whole lot of code that has very little to do with the PersonViewModel.Save() method. That’s not a “unit test”…that’s an “integration test”.
In summary: starting development from the database rots your brain.
My Process: What do I need to test?
Ok. So I’m not starting from the database and I’ve got my UI sketch (see picture above). I’ve got a rough idea of what I need to build. Now I start trying to tease out the functionality that I need to write and unit test. When I’m writing tests for a ViewModel, I typically do this in two phases: UI State and UI Behavior.
First the UI State. “State” is a fancy way of saying “what are the fields that are on my UI?” Since I’m using the ViewModel pattern, any field that shows up on the UI should also exist on the ViewModel for that UI. So, what I’m really doing here is taking a rough cut at the design of my ViewModel and coming up with a list of properties on that ViewModel. (Yah. I know this process is not 100% TDD but bear with me. I’ll go back to being more TDD soon enough.)
The UI fields I’ve got on my PersonDetailViewModel are Id (int), FirstName (string), LastName (string) and EmailAddress (string).
Next I think about what the operations are going to be on the UI. Once again, since I’m using the ViewModel pattern, any operation that happens on my UI should somehow exist on my ViewModel, too. So, what I’m really doing here is taking a rough cut at the list of methods and ICommands on my ViewModel.
Almost every UI is going to need to display itself in an empty, initialized state. So, one of our operations is going to be InitializeToBlank(). Almost every page/UI in a data-centric app is going to have some kind of Load method. If it’s a list page, it’ll have a method called LoadList() or perhaps Search(). If it’s a UI for displaying a detail record, it will have a method called something like LoadById(). For now let’s focus on detail pages. So on a detail page you’ll have a Save() method. Probably a Delete() method, too. And almost definitely a Cancel() method that discards any changes and aborts a save.
So the list of operations so far is InitializeToBlank(), LoadById(), Save(), Delete(), and Cancel().
Now I start thinking about the possible variations and outcomes for each of these methods. There’s more to unit testing than just success cases — don’t forget to test the failures! So, LoadById needs a success case and also a case where the Id is not found. Save could either be creating a new record or it could be modifying an existing record. What happens if Save trying to save an incomplete or otherwise invalid record? What are the validation rules for this UI and or data type? (Answer: we don’t know yet. Keep this in the back of your mind for later.) Delete is delete. Cancel needs to roll back the view model to it’s original state so we need something that will modify the UI and then call Cancel and then we’ll verify that the rollback happens.
This is the list of unit tests that needs to be written. Sure, we might add some more but the list below is the core list. My naming convention is NameOfClassBeingTestedFixture for the [TestClass] class name and, for the methods, either NameOfClassBeingTested_MethodNameBeingTested() or NameOfClassBeingTested_MethodNameBeingTested_Description(). Therefore, I’ll have a class named PersonDetailViewModelFixture and it will (eventually) have (at least) the following 8 unit tests in it.
- PersonDetailViewModel_InitializeToBlank()
- PersonDetailViewModel_LoadById_ValidId()
- PersonDetailViewModel_LoadById_InvalidId()
- PersonDetailViewModel_Save_CreateNewPerson()
- PersonDetailViewModel_Save_ModifyExistingPerson()
- PersonDetailViewModel_Save_InvalidPersonInfoThrowsException()
- PersonDetailViewModel_Delete()
- PersonDetailViewModel_Cancel_DiscardsChangesAndRevertsToOriginal()
What’s Next?
At this point in the process, I probably just have this list of tests written down on a piece of paper, in a text file, or a OneNote notebook (BTW, I love OneNote. It’s great for writing down a bunch of messy thoughts and ideas. You get the ideas out of your head and “backed up” in OneNote and from there you can start cleaning up the ideas and making them into something a little more real.)
The next step is actually figuring out how to implement these tests and write the PersonDetailViewModel functionality. This is where we start to get back to a more Test-Driven Development (TDD) / “test-first” process. I’ll cover my TDD process and how I’d actually architect all this stuff in the second part of this blog post.
-Ben
— Want some training for your team on Test-Driven Development (TDD) and Unit Testing? Want some help unit testing and architecting your Silverlight or Windows Phone 7 application? Drop us a line at info@benday.com.
Leave a Reply