5

我的理解是每个Establish应该只执行一次,但下面的代码显示它执行多次。我们嵌套类以提供一些分组,同时将主题的单元测试保存在一个文件中。这似乎是一个错误。

我们正在使用 machine.specifications.runner.resharper Reshaper 扩展和 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");
    }
}

ScenarioAVariation1 的结果是这样的:

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

当我们使用 NUnit 做我们自己的自定义上下文规范框架时,我们通过确保所有子类都是抽象的(在这种情况下,EstablishRunTwice 和 ScenarioA 将是抽象的)来解决 NUnit 运行的问题,但是 MSpec 尝试这样做会引发错误.

4

2 回答 2

1

这是因为您既嵌套又继承了测试类。通常,您可能会在 C# 中纯粹出于组织目的使用嵌套类,但它也会对 MSpec 中的执行产生影响。这可能是出乎意料的,但确实符合其声明式风格。事实上,除非您在不同文件中重用功能,否则通常根本不需要对 MSpec 使用继承。

只需删除示例中的继承并保留嵌套,您将看到输出为:

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

这使得在外部类的建立中使用通用设置和覆盖内部类中的特定部分变得容易。就个人而言,在我意识到它以这种方式工作之前,我觉得我正在与 MSpec 争夺依赖于不同设置的测试用例(与将不同值直接传递给因中的主题的测试用例相比)。

假设您有一个天气传感器,您可以这样构建它:

[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 {
    ...
  }
}

这在测试结果中也很好读。鉴于第二个测试失败,它可能会在测试树中显示如下:

  • (X) WeatherSensor,在晴天条件下读取传感器时
    • (✔) 应该说是晴天
    • (X) 应该返回正确的温度

如果像那个例子一样,所有不同的条件完全来自注入到 Subject 的依赖项的设置,您甚至可能希望将因为移动到外部类中。然后你可以只在内部类中有一个建立和一些它,使每个测试用例非常简洁。在所有需要的建立之后和其之前,外部因为仍然会为每个内部类运行。

于 2021-12-14T16:42:45.523 回答
0

这是一种非常令人困惑的构建事物的方式——聪明,但也许有点聪明了。我发现很难阅读和理解其意图。事实上,我什至无法想象编译器会用这种继承结构做什么,因此我无法理解其意图。我想也许你想多了。

所以让我看看,ScenarioA不仅嵌套在 中EstablishRunTwice,而且还继承自它。这是否意味着它继承了自身的嵌套副本一直到无穷大?然后,ScenarioB继承所有这些!我的头刚刚爆炸。我对你得到令人困惑的结果并不感到惊讶。嵌套真的给了你什么?它是否使代码更具可读性或更易于维护?我不相信它确实如此。

使用 KISS 原则。正常的做事方式是将每个上下文放在自己的类中,没有嵌套;只需使用文件对相关测试进行分组,您还可以使用属性中的Concern参数[Subject]作为另一种分组方式。如果有意义的话,你可以从其他上下文继承,但是在使用 MSpec 几年之后,我慢慢得出结论,过多的继承会损害可读性并使测试代码更加粘稠,所以明智地使用继承。

更新:在反思了我认为您想要实现的目标之后,我怀疑您正在尝试重新发明行为。这可能是 MSpec 的一个记录不充分且易于理解的特性,它允许您定义一组常见的行为,这些行为以后可以应用于多个测试上下文。这听起来像你想要达到的目标吗?以下是行为示例:

[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;
    }

请注意,在行为中,字段是按名称匹配的,而不是通过任何类型的继承。所以这种行为神奇地知道我所说的“UUT”是什么意思,即使这些类没有任何关系。

于 2015-08-14T13:50:30.330 回答