Recently I’ve heard about more and more people checking out MSpec. A few days ago I got an email from a friend. He said he was having trouble with base class explosion while creating specs. Here is a snippet from his code:
public abstract class with_null_program
{
protected static Program program;
}
public abstract class with_program_and_empty_args : with_null_program
{
Establish context = () => program = new Program(new string[]{});
}
public abstract class with_list_command
{
}
[Subject("Program")]
public class when_creating : with_program_and_empty_args
{
private Because of = () => program = new Program(new string[] {});
It should_output_to_the_console =()=>
program.Out.ShouldEqual(Console.Out);
}
[Subject("Program")]
public class when_running_with_no_arguments : with_null_program
{
private static StringBuilder outputBuilder;
private Because of = () =>
{
outputBuilder = new StringBuilder();
program = new Program(new string[] {}) {Out = new StringWriter(outputBuilder)};
program.Run();
};
It should_print_usage =()=> outputBuilder.ToString().ShouldContain("USAGE");
}
[Subject("Program")]
public class when_running_without_database_args : with_list_command
{
}
And here’s my response:
Hey man, Glad to see you playing with MSpec. Definitely curious to get your feedback on the experience.
So there’s a few things I noticed about the specs. For one, you’re using the with_ pattern. I know I or Scott or someone started this… but I don’t like it now. I much more prefer to just have a base class that contains any utility/meaningless cruft my specs have. You also seem to have taken this to a bit of an extreme. Would you make a regular base class just to create a single instance variable and set it to null in the constructor? Probably not. Same stuff applies here. There’s no value in creating base classes unless they provide value. There’s no naming or understanding benefit. As a matter of fact, they *hinder* understanding more than anything. This is why I try to only put utter crap in base classes, and when it’s important crap, I put it in a method and name it something descriptive and call it in the context. If I were to rewrite these specs I *may* have a single base class called ProgramSpecs. It would probably just have the static program field, but be a place holder in case i needed any utility methods. Naming specs with the with_foo_bar implies that that name is important, which it isn’t. It’s not included in the report for a reason. Your context description should be fully encapsulated by the name of the context class.
Another thing I’m seeing… and I’m not sure if this is just circumstantial, but do you know that you can have more than one context in a class hierarchy? In other words, something like outputBuilder = new StringBuilder() would go in the subclasses establish context. They’re run in order from basiest to subbiest. The Because is *only* a way to highlight a line and say "this is the catalyst that makes the observations possible… everything else up to this point is also important, but this is the real meat". Because is actually *part* of the context. The Context is the arrange and the act (if there is an act). Here’s an example of multiple context clauses (though i have no idea why i didn’t use because here, heh): http://github.com/aaronjensen/compete/blob/master/Source/Compete.Specs/Model/Game/AggregateResultSpecs.cs
As an aside, I feel the Program class is a bit awkward. Personally, it’d make more sense if the StringBuilder was a constructor parameter and the args were parameters to the run method. That’s just my style though–i’m not saying it’s better. It would definitely make testing this less awkward. Actually, look at your context. "when running with no arguments". Your code isn’t doing what you said it would do, at least not in the simplest fashion. I think I’ll change my position to "strongly prefer" the arguments to be passed to the Run method
More often than not, when you’re feeling pain while testing it’s because your API can be improved.
You may, if you haven’t already, want to take a look at the mspec console runner specs: http://github.com/machine/machine.specifications/blob/master/Source/Machine.Specifications.ConsoleRunner.Specs/ProgramSpecs.cs
They’re quite similar to the domain you were writing specs for and may help. Yes, I used with_runner there, they’re old
![]()
Hope that helps a bit, let me know if you have any other questions or anything.
Thanks, Aaron