c# - 使用 MSpec(BDD 指南)对 ASP.NET MVC Controller 操作执行非常相似的规范

标签 c# asp.net-mvc bdd mspec

对于两个非常相似的 Controller 操作,我有两个非常相似的规范:VoteUp(int id) 和 VoteDown(int id)。这些方法允许用户投票赞成或反对一个帖子;有点像 StackOverflow 问题的投票赞成/反对功能。规范是:

投票否决:

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

投票:

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

所以我有两个问题:

  1. 我应该如何对这两个规范进行 DRY-ing?它甚至是可取的还是我应该为每个 Controller 操作制定一个规范?我知道我通常应该这样做,但这感觉像是重复了很多次。

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

我仍然掌握 BDD(和 MSpec)的诀窍,很多时候不清楚我应该走哪条路,或者 BDD 的最佳实践或指南是什么。任何帮助将不胜感激。

最佳答案

我将从您的第二个问题开始: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 源代码包含 another example .

我建议不要使用行为,因为您的示例实际上包含四个上下文。当我考虑您试图用代码在“业务含义”方面表达什么时,会出现四种不同的情况:

  • 用户第一次投票
  • 用户第一次投反对票
  • 用户第二次投票
  • 用户第二次投反对票

对于四种不同场景中的每一种,我都会创建一个单独的上下文来详细描述系统的行为方式。四个上下文类有很多重复代码,这让我们想到了您的第一个问题。

在下面的"template"中,有一个基类,其方法具有描述性名称,说明您调用它们时会发生什么。因此,您不必依赖 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().

关于c# - 使用 MSpec(BDD 指南)对 ASP.NET MVC Controller 操作执行非常相似的规范,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2834461/

相关文章:

c# - 我如何证明装箱导致将变量存储在堆而不是堆栈中?

c# - 在 CaSTLe Windsor 中将参数传递给 UsingFactoryMethod

c# - 调用 IdentityServer4 生成 System.NullReferenceException : Object reference not set to an instance of an object

c# - 通过 JQuery 访问 Controller

c# - 为什么 ASP.NET MVC 模板 View 使用 Html.DisplayFor 来显示不属于模型的数据?

BDD 目标和属性

testing - BDD 小 cucumber 脚本 : Same group of scenarions against multiple roles

c# - 数组的数组

asp.net-mvc - 如何将函数序列化为 json(使用 razor @<text>)

testing - 如何识别 cucumber 上带有数字 ID 的场景和步骤?