I started including unit testing as part of my development routine four years ago. Like a lot of developers, I struggled at first to know what to test and how to make my code more testable. In the .NET space the common approach is to use a lot of interfaces and dependency injection to help keep your code loosely coupled and easier to test.
Using C# and both MSTest and NUnit, I began by writing tests that targeted specific methods. Although this was a good start, I found that it was difficult to maintain the tests because any change in feature implementation required many test changes. Also, the tests were not necessarily proving that any particular feature was working correctly, just that part of the implementation was tested.
A few months later I ran across an example of some unit tests that were written with a different approach. Instead of writing tests aimed at specific methods in the code, the tests instead took the form of “When a user performs some action, then some result should occur”. The tests were written to model the behavior and user interaction of the application, not the developer’s implementation. This immediately clicked with me as a much more meaningful way to write tests.
Still using MSTest and NUnit, I started writing tests to fit the behavior of the application. The way I tried to do this in C# was to name the test class with a sentence-like context description – something like:
public class when_the_user_submits_an_application
and then the individual test methods in the class would be named to match the expectation:
public void then_an_email_is_sent_to_the_administrator()
In contrast with Test Driven Development (TDD), this style of testing is known as Behavior Driven Development (BDD). The actual implementation of this functionality may be contained in a single method, several methods, or even several classes. But what the tests are verifying is that the application is working given a specific context and behavior. Depending on the test runner that is used to run the tests, you can even get the output to look like sentences which describe the application requirements.
I found the BDD approach in general to work very well. However, it soon felt like the tools (MSTest and NUnit) were working against me. Trying to name your classes and methods as sentences with underscores felt clunky (and ReSharper was always complaining…). Also, for any non-trivial test context the class and test method naming became very awkward.
Last year I worked on a Ruby on Rails application and was introduced to RSpec – which is a testing framework specifically designed to work with BDD. For naming the contexts and tests the Ruby language was more natural to work with. But what was also key was the ability to write nested contexts. This reduced the number of test classes and made the context setup much easier.
After working with RSpec, doing BDD in C# was much less fun. Then I ran across NSpec. NSpec is a BDD testing framework for .NET that is built on the NUnit assertion library. It closely resembles the RSpec framework used in Ruby on Rails. http://nspec.org/.
NSpec borrows heavily from the RSpec terminology with before methods, contexts, and it blocks. Contexts and tests are named with strings so you are not constrained by C# class name rules. And, like RSpec, NSpec allows you to have nested contexts.
Having worked with RSpec before, I found it very easy to get started with NSpec. There are numerous code samples on http://nspec.org/, so I won’t recreate any here.
A word of caution – if you are not familiar with C# lambda expressions – take the time to get familiar. NSpec makes heavy use of lambda expressions. The other drawback I see right now is the lack of test runner tooling. You can execute the tests from the NuGet Package Manager Console which is essentially a command window. I have also run them using the ReSharper test runner, but that does not recognize each individual test, just the class that contains them.
Despite the tooling drawback I have so far found NSpec to be much better to work with than MSTest or NUnit for BDD.