For a little background, I'm a contributor to SquishIt and decided that I should start cleaning up the mess of unit tests (my updated version of one set of tests vs. original set of tests). After tackling only part of the "rewrite", I figured it would be a good opportunity to learn, try, and understand BDD style testing to hopefully help improve the unit tests being written and ran. I decided to go with NSpec after taking a look at some of the choices out there.
My problem is simply trying to break the tests down into the appropriate "groupings" that logically make sense and "conform" to xSpec. Here is what I've done so far and passes all tests as expected:
using NSpec;
using SquishIt.Framework;
using SquishIt.Framework.JavaScript;
namespace SquishIt.Tests
{
class describe_JavaScriptBundle : nspec
{
public JavaScriptBundle bundle;
public void AddTest1JS()
{
bundle.Add("test.js");
}
}
class when_I_create_a_bundle : describe_JavaScriptBundle
{
void before_each()
{
if (bundle == null)
{
bundle = Bundle.JavaScript();
}
}
void and_add_no_files()
{
it["GroupBundles containskey default"] = () => bundle.GroupBundles.ContainsKey("default").is_true();
it["has no Assets"] = () => bundle.GroupBundles["default"].Assets.Count.Is(0);
}
}
class then_I_add_a_file : when_I_create_a_bundle
{
void before_each()
{
AddTest1JS();
}
void it_should_contain_only_one_file()
{
bundle.GroupBundles["default"].Assets.Count.Is(1);
}
}
class then_I_add_the_same_file : then_I_add_a_file
{
void it_should_still_contain_only_one_file()
{
bundle.GroupBundles["default"].Assets.Count.Is(1);
}
}
}
How can I improve this or am I already on the right track?
-
1\$\begingroup\$ I usually shy away from inheritance until I have significant nesting inside of my method contexts. Your tests communicate intent and test behavior. That's really all that matters in a good test suite :-) \$\endgroup\$Amir– Amir2011年08月08日 18:49:55 +00:00Commented Aug 8, 2011 at 18:49
2 Answers 2
Here is how I've written most of my NSpec tests, I usually start a context out with an act. This dry's up the specification. If I find myself doing too many "acts", I end up breaking it up into separate specifications.
class describe_JavaScriptBundle : nspec
{
JavaScriptBundle bundle;
void AddTest1JS()
{
bundle.Add("test.js");
}
void before_each()
{
bundle = Bundle.JavaScript();
}
void adding_a_bundle()
{
context["no files added"] = () =>
{
it["GroupBundle contains default key"] = () =>
bundle.GroupBundles.ContainsKey("default").is_true();
it["has no Assets"] () =>
bundle.GroupBundles["default"].Assets.Count.Is(0);
};
context["adding one file"] = () =>
{
act = () => AddTest1JS();
it["contains the file"] = () =>
bundle.GroupBundles["default"].Assets.Count.Is(1);
context["adding the same file again"] = () =>
{
act = () => AddTest1JS();
it["should still only contain one file"] = () =>
bundle.GroupBundles["default"].Assets.Count.Is(1);
};
};
}
}
I've just started out using NSpec myself last night. I really like how expressive it can be, though I see how easy it is to get into a bit of a class inheritance mess (I certainly did last night! :).
Here's my suggested way of coding your test:
public class describe_JavaScriptBundle : nspec
{
protected JavaScriptBundle bundle;
private void CreateBundle()
{
if (bundle == null)
{
bundle = Bundle.JavaScript();
}
}
private void after_each()
{
bundle = null;
}
public void Bundle_Contains_No_Files()
{
it["When I create a new bundle"] = this.CreateBundle;
it["And bundle contains no files"] = () => { };
it["Then bundle will contain default key"] = () => { };
it["And bundle will have no assets"] = () => { };
}
public void Bundle_Contains_One_File()
{
it["When I create a new bundle"] = this.CreateBundle;
it["And bundle contains one file"] = () => bundle.Add("test.js");
it["Then bundle should have one asset"] = () => { };
}
public void Add_Same_File_To_Bundle()
{
it["When I create a new bundle"] = this.CreateBundle;
it["And bundle contains one file"] = () => bundle.Add("test.js");
it["When I add the same file to the bundle"] = () => bundle.Add("test.js");
it["Then bundle should have one asset"] = () => { };
}
}
The key here is the output you get:
describe JavaScriptBundle <- Feature
Bundle Contains No Files <- Scenario
When I create a new bundle
And bundle contains no files
Then bundle will contain default key
And bundle will have no assets
Bundle Contains One File <- Scenario
When I create a new bundle
And bundle contains one file
Then bundle should have one asset
Add Same File To Bundle <- Scenario
When I create a new bundle
And bundle contains one file
When I add the same file to the bundle
Then bundle should have one asset
Notice how I try to describe the feature and then each scenario. This way I'm able to visually see how each scenario it tested. By no means am I saying that having inheritance in wrong, it's just in your case I don't think the tests are complicated enough to warrant this.
So fill in the blanks for the tests and give it a whirl. Let me know if it works out for you.
Cheers. Jas.
-
\$\begingroup\$ NSpec is geared toward Context/Specification. While you can do Gherkin style testing in NSpec, it's better suited for nesting context. I've added an example that shows how you can nest context, hopefully it doesn't hurt the readability of the specification. \$\endgroup\$Amir– Amir2011年08月08日 18:44:10 +00:00Commented Aug 8, 2011 at 18:44
-
1\$\begingroup\$ On a side note, it's worth noting that NSpec doesn't guarantee that you'll get the same instance of the NSpec test across "it" blocks. \$\endgroup\$Amir– Amir2011年08月08日 18:55:51 +00:00Commented Aug 8, 2011 at 18:55
-
\$\begingroup\$ Thanks for the tips. In your second comment, is there documentation which explains this further? What is meant by "....same instance of the NSpec test..."? Cheers. \$\endgroup\$Jason Evans– Jason Evans2011年08月10日 20:29:41 +00:00Commented Aug 10, 2011 at 20:29
-
\$\begingroup\$ NSpec manages an instance of your test class per specify/it. In context/spec, "it" and "specify" blocks are meant to only assert the given state of a system (not change it). Manipulating state in those blocks is not recommended. As Matt and I tighten down on isolating specification blocks, it'll cause any state defined in these blocks to be reset. Also, there is no guarantee that it and specify blocks will run in the order they have been defined. \$\endgroup\$Amir– Amir2011年08月16日 18:44:17 +00:00Commented Aug 16, 2011 at 18:44