Как бы вы избежали взрыва контекста / класса в этом случае с MSpec? - PullRequest
2 голосов
/ 27 апреля 2011

Я люблю mspec.Он отлично подходит для ключевых примеров, которые легко общаться с не техническими людьми, но иногда я нахожу, что это дает ненужное многословие, особенно взрыв классов.

Возьмите следующий пример.

Я хочу смоделировать движение фигуры рыцаря в шахматах.Предполагая, что рыцарь не находится рядом с какой-либо другой частью или границами доски, есть 8 возможных ходов, которые может быть у рыцаря. Я хочу охватить каждую из этих возможностей, но, честно говоря, мне лень писать 8 отдельных спецификаций (8 классов).Я знаю, что могу быть умным с поведением и наследованием, но поскольку я хочу охватить 8 действительных ходов, я не могу понять, как я могу сделать это без 8 because с, поэтому поэтому 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 раз.

Ответы [ 3 ]

3 голосов
/ 30 апреля 2011

Честно говоря, я не могу сказать вам лучший способ сделать это в MSpec.Но я столкнулся с подобной проблемой взрыва класса с MSpec при использовании его в подобных обстоятельствах.Я не знаю, пробовал ли ты когда-нибудь RSpec.В RSpec контексты и спецификации строятся в пределах исполняемого кода.Это означает, что вы можете создать структуру данных, выполнить итерации и создать несколько контекстов и спецификаций, используя один блок кода.Это становится особенно удобным, когда вы пытаетесь определить, как ведет себя нечто, основанное на математике (основные факторы, крестики-нолики, шахматы и т. Д.).Для каждого члена набора заданных и ожидаемых значений может быть задан единый шаблон поведения.

Этот пример написан на NSpec, структуре контекста / спецификации для C #, смоделированной после RSpec.Я намеренно оставил провал спецификации.Я просто прошел этот ката достаточно далеко, чтобы найти место для использования итерации.Сбой спецификации заставляет вас устранить недостатки наивной реализации.

Вот еще один пример простого фактора ката: 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; }
}
1 голос
/ 27 апреля 2011

Из того, что я вижу, ваш дизайн утверждает, что Рыцарь выбросил бы исключение, если бы его переместили в недопустимую позицию.В этом случае я думаю, что у вашего метода есть две разные обязанности: одна для проверки правильного хода, а другая для правильного хода или броска.Я бы предложил разделить ваш метод на две разные обязанности.

В этом конкретном случае я бы выделил метод для проверки того, является ли движение действительным или нет, а затем вызвал его из вашего метода перемещения.Примерно так:

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 для проверки допустимых позиций для данного Рыцаря (что вы можете сделать с одним классом тестирования и разными "Это"), а затем сделатьтолько один тест для метода Move, чтобы увидеть, если он потерпит неудачу, когда ему дана неправильная позиция.

1 голос
/ 27 апреля 2011

Просто используйте его так, как вы хотите.Он должен иметь возможность перемещаться отсюда туда, он должен иметь возможность перемещаться отсюда (2) туда (2) и т. Д. Очень распространенный шаблон в rspec, но не так сильно в MSpec, потому что он обычно используется слишком часто, поэтому никто никогда не говоритоб этом из-за боязни неверного направления.Это отличное место, чтобы использовать это все же.Вы описываете поведение движения Рыцаря.

Вы можете описать его еще лучше, если будете более точным в своем.Он должен быть в состоянии подняться на два вверх и вправо, он должен быть в состоянии подняться на два вверх и влево.Он не должен быть в состоянии перейти к дружественному элементу и т. Д.

Да, вам нужно поместить более одной строки кода в свой It, но это нормально.По крайней мере, на мой взгляд.

...