使用 MSpec(BDD 指南)干燥 ASP.NET MVC 控制器操作的非常相似的规范
对于两个非常相似的控制器操作,我有两个非常相似的规范: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;
}
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;
}
所以我有两个问题:
我应该如何干燥这两个规格?这是可取的还是我实际上应该为每个控制器操作制定一个规范?我知道我通常应该这样做,但这感觉就像是在重复自己。
有没有办法在同一规范中实现第二个
It
?请注意,It should_not_let_the_user_vote_more_than_once;
要求我调用controller.VoteDown(1)
两次。我知道最简单的方法也是为其创建一个单独的规范,但它会再次复制并粘贴相同的代码...
我仍在掌握 BDD(和 MSpec) )并且很多时候并不清楚我应该走哪条路,或者 BDD 的最佳实践或指南是什么。任何帮助将不胜感激。
I have two very similar specs for two very similar controller actions: VoteUp(int id) and VoteDown(int id). These methods allow a user to vote a post up or down; kinda like the vote up/down functionality for StackOverflow questions. The specs are:
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;
}
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;
}
So I have two questions:
How should I go about DRY-ing these two specs? Is it even advisable or should I actually have one spec per controller action? I know I Normally should, but this feels like repeating myself a lot.
Is there any way to implement the second
It
within the same spec? Note that theIt should_not_let_the_user_vote_more_than_once;
requires me the spec to callcontroller.VoteDown(1)
twice. I know the easiest would be to create a separate spec for it too, but it'd be copying and pasting the same code yet again...
I'm still getting the hang of BDD (and MSpec) and many times it is not clear which way I should go, or what the best practices or guidelines for BDD are. Any help would be appreciated.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我将从你的第二个问题开始:MSpec 中有一个功能可以帮助复制
It
字段,但在这种情况下我建议不要使用它。该功能称为“行为”,其内容如下:您想要在行为类中断言的任何字段都需要在行为类和上下文类中受
静态保护
。 MSpec 源代码包含另一个示例。我建议不要使用行为,因为您的示例实际上包含四个上下文。当我思考您试图用代码表达“业务含义”的内容时,会出现四种不同的情况:
一种,我都会创建一个单独的上下文来密切描述系统应如何运行。四个上下文类有很多重复的代码,这让我们想到了你的第一个问题。
在下面的“模板”中,有一个基类,其中的方法具有描述性名称,说明调用它们时会发生什么。因此,您不必依赖 MSpec 自动调用“继承的”Because 字段这一事实,而是将有关对上下文重要的信息放入
Establish
中。根据我的经验,当您稍后阅读规范时,如果它失败了,这将对您有很大帮助。您无需浏览类层次结构,就能立即感受到所发生的设置。与此相关的是,第二个优点是您只需要一个基类,无论您派生多少个具有特定设置的不同上下文。
I'll start with your second question: There is a feature in MSpec that would help with the duplication of the
It
fields, but in this scenario I would advise against using it. The feature is called Behaviors and goes something like this:Any fields you want to assert on in the behavior class need to be
protected static
in both the behavior and the context class. The MSpec source code contains another example.I advise against using behaviors because your example actually contains four contexts. When I think about what you're trying to express with the code in terms of "business meaning", four different cases emerge:
For each of the four different scenarios I would create a separate context that closely describes how the system should behave. Four context classes are a lot of duplicate code, which brings us to your first question.
In the "template" below there is one base class with methods that have descriptive names of what will happen when you call them. So instead of relying on the fact that MSpec will call "inherited"
Because
fields automatically, you put information on what's important to the context right in theEstablish
. From my experience this will help you a lot later when you read a spec in case it is failing. Instead of navigating a class hierarchy you immediately get a feeling for the setup that takes place.On a related note, the second advantage is that you only need one base class, no matter how many different contexts with specific setup you derive.
@Tomas Lycken,
我也不是 MSpec 专家,但我的(到目前为止有限的)实践经验使我更倾向于这样的事情:
这基本上是我已经拥有的,但根据你的答案添加了更改(我没有没有
VoteSetup
类。)您的答案引导我走向正确的方向。我仍然希望得到更多答案来收集有关该主题的其他观点......:)
@Tomas Lycken,
I'm no MSpec guru either, but my (as of yet limited) practical experience with it leads me more towards something more like this:
Which is basically what I already had but adding changes based on your answer (I didn't have the
VoteSetup
class.)Your answer has lead me in the right direction. I'm still hoping for some more answers to gather other points of view on the subject... :)
您可能可以通过仅分解测试的设置来分解大部分重复。没有真正的理由说明为什么赞成票规范应该从 0 到 1 票而不是 10 到 11 票,因此您完全可以拥有一个单一的设置例程。仅此一点就将两个测试留在 3 行代码(或 4 行,如果您需要手动调用设置方法......)。
突然之间,您的测试只包含执行操作并验证结果。无论是否感觉重复,我强烈建议您在每个测试中测试一件事,因为您想确切地知道当您在一个月内重构某些内容并运行解决方案中的所有测试时测试失败的确切原因。
更新(详细信息请参阅评论)
并且在您的规范中:
这可以工作吗?
You could probably factor out much of the repetition by just factoring out the setup of the tests. There is no real reason why the upvote spec should go from 0 to 1 vote rather than 10 to 11, so you can very well have one single setup routine. That alone will leave both test at 3 lines of code (or 4, if you need to call the setup method manually...).
Suddenly, your tests consist only of executing the action, and verifying the results. And whether it feels repetitive or not, I would strongly advise that you test one thing per test, simply because you want to know exactly why a test fails when you refactor something in a month and run all the tests in the solution.
UPDATE (see comments for details)
And in your specification:
Could this work?