Начну со второго вопроса: в MSpec есть функция, которая поможет с дублированием полей It
, но в этом сценарии я бы посоветовал не использовать ее. Эта функция называется «Поведение» и выглядит примерно так:
[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_up_button_on_a_post : SomeControllerContext
{
// Establish and Because cut for brevity.
It should_increment_the_votes_of_the_post_by_1 =
() => suggestion.Votes.ShouldEqual(1);
Behaves_like<SingleVotingBehavior> a_single_vote;
}
[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext
{
// Establish and Because cut for brevity.
It should_decrement_the_votes_of_the_post_by_1 =
() => suggestion.Votes.ShouldEqual(9);
Behaves_like<SingleVotingBehavior> a_single_vote;
}
[Behaviors]
public class SingleVotingBehavior
{
It should_not_let_the_user_vote_more_than_once =
() => true.ShouldBeTrue();
}
Любые поля, которые вы хотите установить в классе поведения, должны быть protected static
как в поведении, так и в классе контекста. Исходный код MSpec содержит другой пример .
Я не советую использовать поведение, потому что ваш пример содержит четыре контекста. Когда я думаю о том, что вы пытаетесь выразить с помощью кода в терминах «делового значения», возникают четыре разных случая:
- Пользователь голосует впервые
- Пользователь впервые голосует вниз
- Пользователь голосует во второй раз
- Пользователь голосует второй раз
Для каждого из четырех различных сценариев я бы создал отдельный контекст, который подробно описывает, как должна вести себя система. Четыре контекстных класса представляют собой много дублирующегося кода, что приводит нас к первому вопросу.
В приведенном ниже «шаблоне» есть один базовый класс с методами, которые имеют описательные имена того, что произойдет, когда вы их вызовете. Поэтому вместо того, чтобы полагаться на тот факт, что MSpec будет автоматически вызывать «унаследованные» поля Because
, вы помещаете информацию о том, что важно для контекста, прямо в Establish
. Исходя из моего опыта, это поможет вам намного позже, когда вы прочитаете спецификацию на случай, если она выйдет из строя. Вместо навигации по иерархии классов вы сразу же чувствуете, что происходит.
Что касается связанной заметки, вторым преимуществом является то, что вам нужен только один базовый класс, независимо от того, сколько различных контекстов с определенной настройкой вы получаете.
public abstract class VotingSpecs
{
protected static Post CreatePostWithNumberOfVotes(int votes)
{
var post = PostFakes.VanillaPost();
post.Votes = votes;
return post;
}
protected static Controller CreateVotingController()
{
// ...
}
protected static void TheCurrentUserVotedUpFor(Post post)
{
// ...
}
}
[Subject(typeof(SomeController), "upvoting")]
public class When_a_user_clicks_the_vote_up_button_on_a_post : VotingSpecs
{
static Post Post;
static Controller Controller;
static Result Result ;
Establish context = () =>
{
Post = CreatePostWithNumberOfVotes(0);
Controller = CreateVotingController();
};
Because of = () => { Result = Controller.VoteUp(1); };
It should_increment_the_votes_of_the_post_by_1 =
() => Post.Votes.ShouldEqual(1);
}
[Subject(typeof(SomeController), "upvoting")]
public class When_a_user_repeatedly_clicks_the_vote_up_button_on_a_post : VotingSpecs
{
static Post Post;
static Controller Controller;
static Result Result ;
Establish context = () =>
{
Post = CreatePostWithNumberOfVotes(1);
TheCurrentUserVotedUpFor(Post);
Controller = CreateVotingController();
};
Because of = () => { Result = Controller.VoteUp(1); };
It should_not_increment_the_votes_of_the_post_by_1 =
() => Post.Votes.ShouldEqual(1);
}
// Repeat for VoteDown().