Scenario-Based Unit Testing: Organization
A friend and I recently came across the idea of scenario-based unit testing, which we think is a great alternative to the test-case-per-class methodology. It's especially attractive because it helps consolidate much of our setup code and reduces the time it takes to create tests. But creating a new test fixture for each scenario gives us a new problem: How should we organize these scenario test fixtures?
If we create a new test fixture for each scenario, we will be creating a lot of fixtures. Where do we put them? In one single mammoth of a file? Or should we create a new file for each scenario, and place that file in a folder with all the other test fixtures?
The reason I ask is because there is great potential for creating a big headache for ourselves when we have to maintain our code later, but开发者_C百科 we won't be able to tell it's a headache until after the damage has been done.
Scenario-based testing is great and there are certainly a lot of options on how to organize your tests.
Nested Classes
A key take away from this style of testing is that we're actually using nested classes to represent our scenarios. We create a root class that represents the container for all the tests and then each scenario is a derived class.
[TestFixture]
public class MyClassSpecs
{
// common helper methods here
// additional specs/scenarios here...
[TestFixture]
public class WhenTheViewIsLoaded : MyClassSpecs
{
[Test]
public void EnsureThatTheButtonIsEnabled()
{
/* etc */
}
}
}
Within NUnit, the subclasses are flattened out and the root and nested class name appear as part of the Fixture name:
MyClassSpecs+WhenTheViewIsLoaded
> EnsureThatTheButtonIsEnabled
In my projects, I typically create one class file per subject and keep all the scenarios as nested classes. I use Visual Studio's outlining to collapse the nested classes. In this fashion, I can see all the scenarios rather quickly.
File Per Scenario
One of the interesting points in Dave's post is that he's using partial classes. He's done so because it makes navigating within Resharper easier, but it also means that fixtures can broken down into different files.
MyClassSpecs.Scenario1.cs
MyClassSpecs.Scenario2.cs
Splitting the fixtures into multiple files may be a matter of taste, but it's a very interesting idea. One area where I could see this falling apart is the naming convention used for the files, especially if the scenario names are long or need to change.
Personally, I would only split into multiple files if it warranted it.
File Per Testing Strategy
While I'm personally not in favor of splitting my fixtures into multiple files, one area I can see this making a lot of sense is if you have different testing strategies. For example, consider two testing strategies where one set of unit tests use Mock objects as dependencies and another set using concrete dependencies.
MySpecs.UsingMocks.cs
MySpecs.Integration.cs
A word of advise on using subclasses for test fixtures -- readability of these tests is the main concern -- don't get carried away with abstracting the details away from the test, and don't create test fixtures that use several levels of inheritance and you'll be fine.
What you're describing sounds to me a lot like BDD - Behavior Driven Development. Semantically, you talk about specifications, rather than tests. And your specs are written as a series of scenarios, often written in a format like Given-When-Then (GWT for short). This means Given the system is in some state When an action is performed Then _an observable change occurs. There are many implementations to handle this. My current favorite is mspec (machine specifications), a framework that works over NUnit or XUnit.Net. Others include Specflow, and RSpec (a ruby based implementation).
One of the nice things about MSpec, is that each spec (test) is constructed of delegates that define the initial state, the action and the expected results (asserts). This means that you can define each of these once in a helper class, and call them as needed, mixing and matching, which you could never do with an inheritance hierarchy.
Hope this helps, Assaf.
精彩评论