使用MSpec(BDD指南)干燥ASP.NET MVC控制器操作的非常类似的规范

Ser*_*eit 5 c# asp.net-mvc bdd mspec

我有两个非常相似的规范,用于两个非常相似的控制器动作:VoteUp(int id)和VoteDown(int id).这些方法允许用户向上或向下投票; 有点像StackOverflow问题的投票上/下功能.规格是:

VoteDown:

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext
{
    Establish context = () =>
    {
        post = PostFakes.VanillaPost();
        post.Votes = 10;

        session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post);
        session.Setup(s => s.CommitChanges());
    };

    Because of = () => result = controller.VoteDown(1);

    It should_decrement_the_votes_of_the_post_by_1 = () => suggestion.Votes.ShouldEqual(9);
    It should_not_let_the_user_vote_more_than_once;
}
Run Code Online (Sandbox Code Playgroud)

VoteUp:

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_down_button_on_a_post : SomeControllerContext
{
    Establish context = () =>
    {
        post = PostFakes.VanillaPost();
        post.Votes = 0;

        session.Setup(s => s.Single(Moq.It.IsAny<Expression<Func<Post, bool>>>())).Returns(post);
        session.Setup(s => s.CommitChanges());
    };

    Because of = () => result = controller.VoteUp(1);

    It should_increment_the_votes_of_the_post_by_1 = () => suggestion.Votes.ShouldEqual(1);
    It should_not_let_the_user_vote_more_than_once;
}
Run Code Online (Sandbox Code Playgroud)

所以我有两个问题:

  1. 我应该怎么去干这两个规格?它甚至是可取的还是我实际上每个控制器动作都有一个规格?我知道我应该这样做,但这感觉就像重复自己一样.

  2. 有没有办法It在同一规范中实现第二个?请注意,It should_not_let_the_user_vote_more_than_once;要求我调用controller.VoteDown(1)两次规范.我知道最简单的方法是为它创建一个单独的规范,但它会再次复制并粘贴相同的代码......

我仍然掌握着BDD(以及MSpec),很多时候我不知道应该采用哪种方式,或者BDD的最佳实践或指南是什么.任何帮助,将不胜感激.

Ale*_*roß 8

我将从您的第二个问题开始: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();
}
Run Code Online (Sandbox Code Playgroud)

您希望在行为类中断言的任何字段都必须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().
Run Code Online (Sandbox Code Playgroud)