对于两个非常相似的控制器操作,我有两个非常相似的规格:投票(INT ID)和投票(INT ID)。这些方法允许用户向上或向下投票; Kinda类似于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. 这两个规格我应该如何干燥?是否建议,或者我应该每个控制器操作实际上有一个规格?我知道我通常应该这样做,但这感觉就像重复了很多。

  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源代码包含 另一个例子.

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

  • 用户首次投票
  • 用户首次投票
  • 用户第二次投票
  • 用户第二次投票

对于四种不同方案中的每一个,我将创建一个单独的上下文,密切描述系统的行为方式。四个上下文类是许多重复的代码,这使我们陷入了您的第一个问题。

在下面的“模板”中,有一个基类,其方法具有描述性名称,即您称呼它们时会发生什么。因此,而不是依靠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().

其他提示

@Tomas Lycken,

我也不是MSPEC大师,但是我(尚未有限的)实践经验使我更加走向这样的事情:

public abstract class SomeControllerContext
{
    protected static SomeController controller;
    protected static User user;
    protected static ActionResult result;
    protected static Mock<ISession> session;
    protected static Post post;

    Establish context = () =>
    {
        session = new Mock<ISession>();
            // some more code
    }
}

/* many other specs based on SomeControllerContext here */

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

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

[Subject(typeof(SomeController))]
public class When_user_clicks_the_vote_up_button_on_a_post : VoteSetup
{
    Because of = () => result = controller.VoteUp(1);

    It should_increment_the_votes_of_the_post_by_1 = () => post.Votes.ShouldEqual(11);
    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 : VoteSetup
{
    Because of = () => result = controller.VoteDown(1);

    It should_decrement_the_votes_of_the_post_by_1 = () => post.Votes.ShouldEqual(9);
    It should_not_let_the_user_vote_more_than_once;
}

基本上,这是我已经拥有的,但是根据您的答案添加更改(我没有 VoteSetup 班级。)

您的答案使我朝着正确的方向发展。我仍然希望有更多答案来收集有关该主题的其他观点... :)

您可能会考虑出来的大部分重复 设置 测试。没有真正的理由将UPVote Spec从0投票而不是10投票到11到11,因此您可以拥有一个单一的设置例程。仅此单独的测试将在3行代码(如果需要手动调用设置方法...)上留下两行测试。

突然,您的测试仅包括执行操作并验证结果。而且,无论是否感觉重复,我都会强烈建议您每次测试测试一件事,仅仅是因为您想确切地知道,当您在一个月内重构某件事并运行解决方案中的所有测试时,测试会失败。

更新 (有关详细信息,请参见评论)

private WhateverTheTypeNeedsToBe vote_count_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());
};

在您的规范中:

Establish context = vote_count_context;
...

这个可以工作吗?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top