在这种情况下,使用 MSpec 如何避免上下文/类爆炸?

发布于 2024-11-03 11:49:48 字数 996 浏览 2 评论 0原文

我喜欢 mspec。它非常适合以易于与非技术人员沟通的方式提供关键示例,但有时我发现它提供了不必要的冗长,特别是类的爆炸。

以下面的例子为例。

我想模拟国际象棋中马棋子的运动。假设骑士不靠近任何其他棋子或棋盘边界,骑士可以有 8 种可能的动作,我想涵盖这些可能性中的每一种,但坦率地说,我懒得编写 8 个单独的规范(8 个类别)。我知道我可以很聪明地处理行为和继承,但是当我想涵盖 8 个有效的动作时,我不知道如何在没有 8 个 因为 的情况下做到这一点,因此因此需要 8 个单独的类。

使用 mspec 覆盖这些场景的最佳方法是什么?

一些代码。

public class Knight
{
    public string Position {get; private set;}

    public Knight(string startposition)
    {
         Position = startposition;
    }

    public void Move
    {
          // some logic in here that allows a valid move pattern and sets positions
    }


}

我可能会做什么。

[Subject(typeof(Knight),"Valid movement")]
public class when_moving_the_knight
{
     Establish that = () => knight =new Knight("D4");
     Because of = ()=> knight.Move("B3");
     It should_update_position = ()=> knight.Position.ShouldEqual("B3");
     It should_not_throw;
     /// etc..    
} 

但不是8次。

I love mspec. It is great for providing key examples in way that is easy to communicate with non technical people but sometimes I find it provides an unnecessary verbosity, specifically an explosion of classes.

Take the following example.

I want to model the movement of a knight piece in chess. Assuming the knight is not near any other piece or the boundaries of the board there are 8 possible moves that knight can have I want to cover each of these possibilities but to be frank I am too lazy to write 8 separate specifications (8 classes). I know that I can be clever with behaviours and inheritance but as I want to cover the 8 valid moves I cannot see how I can do it with out 8 becauses so therefore 8 separate classes.

What is the best way to cover these scenarios with mspec?

Some code.

public class Knight
{
    public string Position {get; private set;}

    public Knight(string startposition)
    {
         Position = startposition;
    }

    public void Move
    {
          // some logic in here that allows a valid move pattern and sets positions
    }


}

What I might do.

[Subject(typeof(Knight),"Valid movement")]
public class when_moving_the_knight
{
     Establish that = () => knight =new Knight("D4");
     Because of = ()=> knight.Move("B3");
     It should_update_position = ()=> knight.Position.ShouldEqual("B3");
     It should_not_throw;
     /// etc..    
} 

But not 8 times.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

旧话新听 2024-11-10 11:49:48

老实说,我无法告诉您在 MSpec 中做到这一点的最佳方法。但当我在类似的情况下使用 MSpec 时,我遇到了类似的类爆炸问题。不知道大家有没有尝试过RSpec。在 RSpec 中,上下文和规范是在可执行代码的范围内构建的。这意味着您可以创建一个数据结构,对其进行迭代,并使用一个代码块创建多个上下文和规范。当您试图指定基于数学的事物的行为方式(质因数、井字棋、国际象棋等)时,这变得特别方便。可以在一组给定值和期望值的每个成员中指定单一的行为模式。

此示例是用 NSpec 编写的,NSpec 是一个模仿 RSpec 的 C# 上下文/规范框架。我故意留下了一个失败的规范。我只是沿着这个套路走得足够远,找到了使用迭代的地方。失败的规范迫使您解决简单实现的缺点。

这是质因数 kata 的另一个示例: http://nspec.org/#dolambda

输出:

describe Knight
  when moving 2 back and 1 left
    when a knight at D4 is moved to B3
      knight position should be B3
    when a knight at C4 is moved to A3
      knight position should be A3 - FAILED - String lengths are both 2. Strings differ at index 0., Expected: "A3", But was: "B3", -----------^

**** FAILURES ****

describe Knight. when moving 2 back and 1 left. when a knight at C4 is moved to A3. knight position should be A3.
String lengths are both 2. Strings differ at index 0., Expected: "A3", But was: "B3", -----------^
   at ChessSpecs.describe_Knight.<>c__DisplayClass5.<when_moving_2_back_and_1_left>b__4() in c:\Users\matt\Documents\Visual Studio 2010\Projects\ChessSpecs\ChessSpecs\describe_Knight.cs:line 23

2 Examples, 1 Failed, 0 Pending

代码:

using System.Collections.Generic;
using NSpec;

class describe_Knight : nspec
{
    void when_moving_2_back_and_1_left()
    {
        new Each<string,string> { 
            {"D4", "B3"},
            {"C4", "A3"},
        }.Do( (start, moveTo) =>
        {
            context["when a knight at {0} is moved to {1}".With(start,moveTo)] = () =>
            {
                before = () =>
                {
                    knight = new Knight(start);
                    knight.Move(moveTo);
                };
                it["knight position should be {0}".With(moveTo)] = () => knight.Position.should_be(moveTo);
            };
        });
    }
    Knight knight;
}

class Knight
{
    public Knight(string position)
    {
        Position = position;
    }

    public void Move(string position)
    {
        Position = "B3";
    }

    public string Position { get; set; }
}

Honestly, I couldn't tell you the best way to do that in MSpec. But I've experienced a similar class explosion problem with MSpec when using it in similar circumstances. I don't know if you have ever tried RSpec. In RSpec contexts and specifications are built up within the confines of executable code. What that means is you can create a data structure, iterate on it, and create several contexts and specs using one block of code. This becomes especially handy when you are trying to specify how something based in mathematics behaves (prime factors,tic tac toe, chess, etc...). A single pattern of behavior can be specified across each member of a set of given and expected values.

This example is written in NSpec, a context/spec framework for C# modeled after RSpec. I purposefully left a failing spec. I just went down this kata far enough to find a place to use iteration. The failing spec forces you to resolve the shortcomings of the naive implementation.

Here's another example of prime factor kata: http://nspec.org/#dolambda

Output:

describe Knight
  when moving 2 back and 1 left
    when a knight at D4 is moved to B3
      knight position should be B3
    when a knight at C4 is moved to A3
      knight position should be A3 - FAILED - String lengths are both 2. Strings differ at index 0., Expected: "A3", But was: "B3", -----------^

**** FAILURES ****

describe Knight. when moving 2 back and 1 left. when a knight at C4 is moved to A3. knight position should be A3.
String lengths are both 2. Strings differ at index 0., Expected: "A3", But was: "B3", -----------^
   at ChessSpecs.describe_Knight.<>c__DisplayClass5.<when_moving_2_back_and_1_left>b__4() in c:\Users\matt\Documents\Visual Studio 2010\Projects\ChessSpecs\ChessSpecs\describe_Knight.cs:line 23

2 Examples, 1 Failed, 0 Pending

Code:

using System.Collections.Generic;
using NSpec;

class describe_Knight : nspec
{
    void when_moving_2_back_and_1_left()
    {
        new Each<string,string> { 
            {"D4", "B3"},
            {"C4", "A3"},
        }.Do( (start, moveTo) =>
        {
            context["when a knight at {0} is moved to {1}".With(start,moveTo)] = () =>
            {
                before = () =>
                {
                    knight = new Knight(start);
                    knight.Move(moveTo);
                };
                it["knight position should be {0}".With(moveTo)] = () => knight.Position.should_be(moveTo);
            };
        });
    }
    Knight knight;
}

class Knight
{
    public Knight(string position)
    {
        Position = position;
    }

    public void Move(string position)
    {
        Position = "B3";
    }

    public string Position { get; set; }
}
锦欢 2024-11-10 11:49:48

只需按照您想要的方式使用它即可。它应该能够从这里移动到那里,它应该能够从这里(2)移动到那里(2),等等。在 rspec 中非常常见的模式,但在 MSpec 中并不常见,因为它通常被过度使用,所以没有人谈论因为害怕引导错误的方向。不过,这是一个使用它的好地方。您正在描述骑士移动的行为。

您可以通过更具体地描述它来更好地描述它。它应该能够向上移动两个并向右移动一个,它应该能够向上移动两个并向左移动一个。它不应该能够移动到友好的部分,等等。

是的,您需要在 It 中放置不止一行代码,但这没关系。至少在我看来。

Just use the Its the way you want to. It should be able to move from here to there, it should be able to move from here(2) to there(2), etc. Very common pattern in rspec but not so much in MSpec because it's generally overused so no one ever talks about it for fear of guiding the wrong way. This is a great spot to use this though. You're describing the behavior of the Knight's moving.

You can describe it even better by being more specific in your Its. It should be able to move up two and to the right one, it should be able to move up two and to the left one. It should not be able to move onto a friendly piece, etc.

Yes, you'll need to put more than one line of code in your It, but that's OK. At least in my opinion.

余生共白头 2024-11-10 11:49:48

据我所知,您的设计表明,如果骑士移动到无效位置,则会引发异常。在这种情况下,我认为您的方法有两个不同的职责,一个用于检查有效的移动,另一个用于执行正确的移动或投掷。我建议将您的方法分为两个不同的职责。

对于这种特定情况,我将提取一个方法来检查移动是否有效,然后从移动方法中调用它。类似这样的事情:

public class Knight
{
    internal bool CanMove(string position)
    {
        // Positioning logic here which returns true or false
    }

    public void Move(string position)
    {
        if(CanMove(position))
            // Actual code for move
        else
            // Throw an exception or whatever
    }
}

通过这种方式,您可以测试 CanMove 内部的逻辑,以测试给定 Knight 的有效位置(您可以使用单个测试类和不同的“It”来完成),然后仅对 Move 方法进行一次测试以查看如果给予无效位置时失败。

From what I see your design states that the Knight would throw an exception if moved in an invalid position. In this case I think that your method has two different responsibilities, one for checking a valid move and the other for doing the correct move or throwing. I would suggest to split your method into the two distinct responsibilities.

For this specific case I would extract a method for checking whether a move is valid or not, and then calling it from your move method. Something like that:

public class Knight
{
    internal bool CanMove(string position)
    {
        // Positioning logic here which returns true or false
    }

    public void Move(string position)
    {
        if(CanMove(position))
            // Actual code for move
        else
            // Throw an exception or whatever
    }
}

this way you could test the logic inside CanMove for testing valid positions for a given Knight (which you can do with a single test class and different "It"s), then make just one test for the Move method to see if it fails when given an invalid position.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文