5

My understand is that each Establish should only be executed once, but the code below shows it executing multiple times. We're nesting the classes to provide some grouping while keeping the unit tests for a Subject in one file. This seems like it is a bug.

We're using the machine.specifications.runner.resharper Reshaper extension and MSpec 0.9.1.

[Subject(typeof(string))]
internal class EstablishRunTwice {
    Establish sharedContext = () => Console.WriteLine("Shared context");

    internal class ScenarioA : EstablishRunTwice {
        Establish scenarioAContext = () => Console.WriteLine("ScenarioA context");

        internal class ScenarioAVariation1 : ScenarioA {
            Because of = () => Console.WriteLine("ScenarioAVariation1 Because");

            It it1 = () => Console.WriteLine("ScenarioAVariation1 It1");

            It it2 = () => Console.WriteLine("ScenarioAVariation1 It2");
        }

        internal class ScenarioAVariation2 : ScenarioA {
            Because of = () => Console.WriteLine("ScenarioAVariation2 Because");

            It it1 = () => Console.WriteLine("ScenarioAVariation2 It1");

            It it2 = () => Console.WriteLine("ScenarioAVariation2 It2");
        }
    }

    internal class ScenarioB : EstablishRunTwice {
        Establish context = () => Console.WriteLine("ScenarioB context");

        Because of = () => Console.WriteLine("ScenarioB Because");

        It it1 = () => Console.WriteLine("ScenarioB It1");

        It it2 = () => Console.WriteLine("ScenarioB It2");
    }
}

The result is this for ScenarioAVariation1:

Shared context
Shared context
ScenarioA context
Shared context
Shared context
ScenarioA context
ScenarioAVariation1 Because
ScenarioAVariation1 It1
ScenarioAVariation1 It2

When we were doing our own custom context specification framework using NUnit, we got around issues with the NUnit running by making sure all subclasses were abstract (in this case, EstablishRunTwice and ScenarioA would be abstract), but MSpec throws an error attempting to do so.

Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
Andy
  • 8,432
  • 6
  • 38
  • 76

2 Answers2

1

It is because you are both nesting and inheriting the test classes. Normally you might use nested classes in C# purely for organizational purposes, but it also has an effect on execution in MSpec. This might be unexpected, but does fit with its declarative style. In fact, there isn't normally a need to use inheritance at all for MSpec unless you're reusing functionality across different files.

Just remove the inheritance in your example and keep the nesting and you'll see the output as:

Shared context
ScenarioA context
ScenarioAVariation1 Because
ScenarioAVariation1 It1
ScenarioAVariation1 It2
...

This makes it easy to use common setup in the Establish of the outer class and override specific parts in inner classes. On a personal note, before I realized it worked this way, I felt like I was fighting with MSpec for test cases that relied on different setups (vs ones where different values are passed directly to the Subject in the Because).

Say you had a weather sensor thingy, you might structure it this way:

[Subject(typeof(WeatherSensor))]
class when_reading_the_sensor : WithSubject<WeatherSensor> {
  Establish context = () => { common setup }

  class with_sunny_conditions {
    Establish context = () => { setup sunny conditions }

    Because of = () => Subject.Read();

    It should_say_it_is_sunny => () => ...
    It should_return_correct_temps => () => ...
  }

  class with_rainy_conditions {
    ...
  }
}

That also reads well in the test results. Given the second test fails, it might show like this in the test tree:

  • (X) WeatherSensor, when reading the sensor with sunny conditions
    • (✔) should say it is sunny
    • (X) should return correct temps

If, as in that example, all the different conditions come purely from setup of dependencies injected into the Subject, you may even want to move the Because into the outer class. Then you can just have an Establish and some Its in the inner classes, making each test case very concise. The outer Because will still be run for each inner class after all the needed Establishes and before the Its.

ShawnFumo
  • 2,108
  • 1
  • 25
  • 14
0

That's a really confusing way to structure things - clever, but perhaps a bit too clever. I find it hard to read and understand the intent. In fact, I can't even begin to imagine what the compiler would do with that inheritance structure and therefore I can't understand the intent. I think maybe you're over-thinking this.

So let me see, ScenarioA is not only nested in EstablishRunTwice, but also inherits from it. Does that mean it inherits nested copies of itself all the way down to infinity? And then, ScenarioB inherits from all of that! My head has just exploded. I'm not surprised you get confusing results. What does that nesting really give you? Does it make the code more readable or easier to maintain? I'm not convinced it does.

Use the KISS principle. The normal way of doing things is to put each context in its own class, no nesting; just use files to group related tests, and you can also use the Concern argument in the [Subject] attribute as another way of grouping. You can inherit from other contexts if that makes sense, but after working with MSpec for a couple of years, I'm slowly coming to the conclusion that too much inheritance can harm readability and make the test code more viscous, so use inheritance wisely.

Update: Having reflected on what I think you are trying to achieve for a bit longer, I suspect you are trying to re-invent behaviours. This is perhaps a poorly documented and understood feature of MSpec, which lets you define a set of common behaviours that can later be applied in multiple test contexts. Does that sound like what you are trying to achieve? Here's an example of behaviours:

[Behaviors]
internal class DenebRightAscension
    {
    It should_have_20_hours_ = () => UUT.Degrees.ShouldEqual(20u);
    It should_have_41_minutes = () => UUT.Minutes.ShouldEqual(41u);
    It should_have_59_seconds = () => UUT.Seconds.ShouldEqual(59u);
    protected static Bearing UUT;
    }


[Subject(typeof(HourAngle), "sexagesimal")]
internal class when_converting_hour_angle_to_sexagesimal
{
    Because of = () =>
    {
        RaDeneb = 20.6999491773451;
        UUT = new Bearing(RaDeneb);
    };

    Behaves_like<DenebRightAscension> deneb;

    protected static Bearing UUT;
    static double RaDeneb;
}

[Subject(typeof(Bearing), "sexagesimal")]
internal class when_converting_to_sexagesimal
    {
    Because of = () =>
        {
        RaDeneb = 20.6999491773451;
        UUT = new Bearing(RaDeneb);
        };

    Behaves_like<DenebRightAscension> deneb;

    protected static Bearing UUT;
    static double RaDeneb;
    }

Note that in behaviours fields are matched by name, not by any kind of inheritance. So the behaviour magically knows what I mean by 'UUT' even though the classes are in no way related.

Tim Long
  • 13,508
  • 19
  • 79
  • 147
  • I haven't had time to go through your answer, but this seems way off base "Does that mean it inherits nested copies of itself all the way down to infinity?" No... the ultimate base class of EstablishRunsTwice is object, which then inherits nothing else. The nesting is just to organize things a bit. – Andy Aug 14 '15 at 16:03
  • @Andy - the fact that you're having to explain it to me is sort of my point :) I find that I am completely unable to reason about what I think the compiler should do with that code. What does it do, for example, if I create an instance of `ScenarioA.ScenarioA.ScenarioA.ScenarioA.ScenarioAVariation1();`? That's valid syntax and it builds... what the hell does it do? – Tim Long Aug 14 '15 at 16:48
  • Well I think the compiler would just call ScenarioAVariation1, would it not? It doesn't end up being recursive or anything as its static. Anyway, for MSpec, my expectation is that it would see ScenarioAVariation1 and flatten all the Establish, It, and Because delegates into one test, executing each Establish once as well as the Because once, and all the It delegates once. – Andy Aug 14 '15 at 18:35
  • To your answer, the Behaviors look interesting, I didn't know about them, but they look geared towards the It delegates. The inheritance is really just about sharing common behavior, such as common Establish or even an common Because. I can un-nest the classes and that fixes Establish being called more than once, which isn't too bad, but I guess I'm curious as to why it ends up being called twice (or four times, if you have to levels of nesting). – Andy Aug 14 '15 at 18:38
  • I don't think it is two levels of nesting. You've the obvious levels, but then each of the classes inherits it's parent, so you have a whole other level of nesting inside each nested class. – Tim Long Aug 17 '15 at 14:40
  • Inheritance != nesting, and they are unrelated. The nesting doesn't impact the inheritance, and the inheritance doesn't impact the nesting. The only reason the nesting is relevant here is because MSpec breaks due to it. – Andy Aug 17 '15 at 16:36