nspec / NSpec

A battle hardened testing framework for C# that's heavily inspired by Mocha and RSpec.
http://nspec.org/
MIT License
260 stars 57 forks source link

How to give NSpec a more effective, more BDD syntax. #71

Closed JVimes closed 10 years ago

JVimes commented 10 years ago

I'm deciding between NSpec and xBehave.net, looking at these code samples: xBehave.net, NSpec. NSpec looks great because test names can be strings with spaces, and I'll be typing a lot of them. But there are a few things that I think will confuse newcomers and/or cause people to focus on test implementation rather than behavior:

I think NSpec would be much more appealing, and "more BDD" with this syntax:

behavior["when the site already exists"] = () =>
{
    given["the account has a site"] = () => account = GetAccountWithSite();

    when["I add a site"] = () => account.AddSite("sites/1");

    then["it throws excepton"] = expect<InvalidOperationException>();
};

That's consistent, readable, and keeps the programmer focused on behavior. If you think the clauses are too wordy, their strings could be made optional (at least Given and When, since those aren't even possible now).

Is this possible to implement, and any chance of getting it as an option in a future version? Thanks!

amirrajan commented 10 years ago

I'll let @mattflo perhaps answer this one in detail. The short version is that NSpec is opinionated in that it prescribes to a context/specification style of BDD (heavily inspired by RSpec) as opposed to the Gherkin style. If you want a more Gherkin style syntax, you may want to check out SpecFlow.

Here is codereview.stackexchange question that touches on the subject you mentioned: http://codereview.stackexchange.com/questions/3712/does-this-follow-proper-xspec-style

JVimes commented 10 years ago

Thanks for the response. I really don't think my suggestion is SpecFlow/Cucumber/Gherkin style. It's not heavy and specs aren't separate from implementation.

In a nutshell: Everything stays the same except the user can use new keywords in place of the old ones and the user can annotate Before and Act statements (or not) using the same syntax as Context and It.

Benefits: Keeps users focused on behavior. They can make tests readable-at-a-glance if they want (or not).

Cons: Expands the keyword set, but only by four words.

amirrajan commented 10 years ago

Ah, in that case, you can just inherit nspec.cs and provide your own aliases. Here is an example:

public class nspec_2 : nspec
{
    public virtual Action given
    {
        get { return before; }
        set { before = value; }
    }
}
mattflo commented 10 years ago

Ha. I was about offer a similar approach. Fork and change the keywords as much as you like in https://github.com/mattflo/NSpec/blob/master/NSpec/nspec.cs

There are tradeoffs between context/spec verbiage and gherkin verbiage. Primarily context/spec focuses on efficiently modeling the idea of specifications within contexts and thus allows for nesting. The downside to this approach is the sentences are harder to read in the code. However, when you run the specs the sentences can easily be constructed so that they are very informative.

It sounds like you like the context/spec model, but just want to use different keywords. It's an interesting idea. At the end of the day gherkin and context/spec can be translated back and forth. we chose context/describe/it/before/after because that's the verbiage from rspec. And many other context/spec frameworks follow the same convention (jasmine, minutest, mocha).

I'm not sure specifically why we chose the keyword "act". It's a feature that was not found in any other context/spec frameworks at the time, so we didn't have a conventional keyword to fall back on. And to avoid confusion with gherkin (when none of the other nspec keywords are gherkin) we decided on "act". Also keep in mind the motivation for the act feature was not to make specs more readable, but to make specs more able to model a single "action" across multiple contexts and associated specs. This was a pattern we saw in the real world and wanted nspec specs to model it better. See https://github.com/mattflo/NSpec/blob/master/SampleSpecs/WebSite/describe_act.cs

mattflo commented 10 years ago

I love that you linked Dan North's blog introducing BDD. Here is a link to the posting where Dave Astels introduced rspec. Each of these two 'genesis' posts references the other's work. I've never figured out which one was posted first. But each are addressing the same problem in slightly different ways. But each under the umbrella of 'BDD'. You'll also notice that rspec became wildly popular in the ruby world while rbehave never took off.

JVimes commented 10 years ago

That takes care of the first half of my suggestion. Would it be straightforward to make the Before and Act syntax be like that of Context and It? Example:

before["the account has a site"] = () => account = GetAccountWithSite();
act["I add a site"] = () => account.AddSite("sites/1");

@mattflo, ya, I recently found BDD and I guess I'm taking a very simple, pure approach based on North's blog post: TDD but with North's constraints that keep me focused on behavior. These constraints are the given/when/then and "should" verbage. I'm only considering unit-level TDD. I'll try to give Astels' article a read soon.

mattflo commented 10 years ago

One problem with making before and act behave like that (pun intended), is the only two concepts in nspec that have names (strings that identify them) are context and example(specify/it). before and act (among others) are only Actions and thus have no names. I think the two things you are missing that you need to achieve the syntax you've laid out is:

Nameable before actions: Just a container concept that allows a name to go along with the Action.

Chainable befores: The idea that several befores can be expressed in a row and they build on each other.

I haven't heard of xbehave. I just took a look and it is indeed a very approachable syntax. In fact, what they've implemented includes nameable, chainable befores.

Not sure if it would be straightforward or not, but maybe you should try leveraging the ActionRegister class that allows for the nspec syntax you like over in the xbehave codebase. Since they already have the plumbing for nameable, chainable befores.

JVimes commented 10 years ago

Ya, xBehave.net is accesible because of the strings for the clauses. NJasmine does an even better job by putting the keywords in the syntax at the right spot: given("the account has priveleges", () => {...}. But they nested their three steps, so that would need to be remedied.

In that gist comparison, NSpec beats xBehave.net in at least these ways. NSpec:

Anyway, thanks for reading my ideas and following my train of thought. That's really good of you guys. I need something to use tomorrow for work, so I probably won't be forking my own test framework yet but I'll keep it in mind. I'll need a proponent like Amir to hawk it online ;) That, with some Visual Studio, ReSharper, and Mighty Moose/NCrunch integration and I think I'd have a shot!

mattflo commented 10 years ago

Ha. Sounds good. If I play around with this some more I'll let you know. I'm somewhat intrigued by what I'll call the idea of sacrificing some nesting for some readability. The nesting you dislike from NJasmine is core to the context/spec idea. I'll borrow from the fairly well known arrange, act, assert verbiage. Code always runs inside some context. So, that's why modeling it with containment has incredible valuable. So, within a context, code is arranged, The system is acted upon. And some behavior asserted. rspec collapses the arrange and act. In some cases there is no value in separating them. But we found some scenarios where there is value, so we added that option.

When the specs follow this containment model, it is incredibly DRY when you want to specify some slightly nuanced variation of an existing context. It allows for context nesting, or another way to think of it is context branching. When you sacrifice nesting for readability by flattening it out as xBehave does, I think you may lose this valuable feature of context/spec.

amirrajan commented 10 years ago

@JVimes let me see if I can address a couple of your concerns.'

Here is a very large NSpec Test Suite: https://github.com/amirrajan/Oak/tree/master/Oak.Tests

We've had a number of publications in MSDN magazine along with an appearance on Hanselminutes.

As far as VS integration, NSpec ships with DebuggerShim.cs which will let you tunnel your debugging through a test fixture (I've used the successfully with NCrunch and TDDNet). There are a couple of plugins on Nuget also. That being said, I'm a huge fan of SpecWatchr and the idea of tests just running in the background.

You may also want to look at this VS Plugin.

JVimes commented 10 years ago

@mattflo, Oops, I meant the call-nesting syntax of NJasmine, not context nesting. I should have been clearer. Their implementation dictated their syntax, making code go diagonally and leaving lots of trailing });'s. NSpec already does it better:

NJasmine - Due to auto-indent, chaining calls makes code go diagonal. Fighting the auto-indenter or turning it off isn't a good solution. Also note the growing list of closing brackets/parens (reminds me of Lisp).

given("the account has a site", () => { account = GetAccountWithSite();
    when("I add another site", () => { account.AddSite("sites/1");
        then("the site is not added", () => { // However NJasmine does exception checking
        });
    });
});

NSpec - Nice and readable. No diagonal syntax or Lisp-ey parens.

before = () => account = GetAccountWithSite();
act = () => account.AddSite("sites/1");
it["throws excepton"] = expect();

I think I'm fine with NSpec's context model. I just like how North's BDD uses certain verbage and test-naming to keep developers on track. NSpec very nearly fits the bill.

@amirrajan, Thanks! I did know about the integration and tooling :) I was just thinking of success requirements if I wrote my own test framework. It's great to see that NSpec is "here to stay".

JVimes commented 10 years ago

I'm starting to think maybe the "given" annotation text really belongs in the Context string (already available in NSpec), unlike my suggestion to annotate the Before lines. I'll try some NSpec implementation in my project and report back for posterity.

mattflo commented 10 years ago

"Given" is useful for introducing a context but it's even more appropriate for setting one up.

-Matt

On Aug 13, 2014, at 11:56 AM, JVimes notifications@github.com wrote:

I'm starting to think maybe the "given" annotation text really belongs in the Context string (already available in NSpec), unlike my suggestion to annotate the Before lines. I'll try some NSpec implementation in my project and report back for posterity.

— Reply to this email directly or view it on GitHub.

JVimes commented 10 years ago

That's true. I'm trying to avoid repetition, since I can't think of what to name a Before statement other than its Context's name. It seems a Context is conceptually defined by its setup, aka what its Before statement does. So, naming one is naming the other.

amirrajan commented 10 years ago

@JVimes did ya get a chance to try out the VS Extension?

JVimes commented 10 years ago

@amirrajan, yes, I've been using that :) I love that it allows easy debugging and navigation from results to code. The author is working on getting it to navigate to lambda steps when double clicking them in the test explorer. I think this plugin is very important to the NSpec ecosystem (without it, I would have overlooked NSpec).