C# 我是否正确编写了我的第一个 MSpec 规范?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1184297/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 10:16:36  来源:igfitidea点击:

Am I writing my first MSpec specifications correctly?

c#bddmspec

提问by Sekhat

I'm writing my first MSpec specifications and I wanted some guidance. I left the specs in the "pending" state, but the context is filled out. Are there any improvements to be made?

我正在编写我的第一个 MSpec 规范,我需要一些指导。我将规格留在“待定”状态,但上下文已填写。是否有任何改进?

For reference, this is the story and first scenario:

作为参考,这是故事和第一个场景:

Story: "Blog admin logs in to the system"

As a blog writer
I want to be able to log in to my blog
So that I can write posts and administer my blog

Scenario: "Logs in from the login page"

Given the user enters in correct credentials for a user in the system
When the user clicks the "Login" button
Then log the user in and redirect to the admin panel with a message 
stating that he logged in correctly

And the MSpec code (some parts snipped), notice that I had to alias the MSpec Itdelegate due to a conflict with Moq.It:

和 MSpec 代码(某些部分被剪掉),请注意,It由于与以下内容发生冲突,我不得不对 MSpec委托进行别名Moq.It

using MoqIt = Moq.It;
using ThenIt = Machine.Specifications.It;

[Subject("User tries logging in")]
public class When_user_enters_valid_credentials : With_user_existing_in_membership
{
    protected static ActionResult result;

    Because of = () =>
    {
        result = loginController.Login(validUsername, validPassword);
    };

    ThenIt should_log_the_user_in;
    ThenIt should_redirect_the_user_to_the_admin_panel;
    ThenIt should_show_message_confirming_successful_login;
}

public abstract class With_user_existing_in_membership
{
    protected static Mock<ISiteMembership> membershipMock;
    protected static string validUsername;
    protected static string validPassword;
    protected static LoginController loginController;

    Establish context =()=>
    {
        membershipMock = new Mock<ISiteMembership>();
        validUsername = "ValidUsername";
        validPassword = "ValidPassword";
        //make sure it's treated as valid usernames and password
        membershipMock
            .Setup<bool>(m => m.Validate(
                MoqIt.Is<string>(s => s == validUsername), 
                MoqIt.Is<string>(s => s == validPassword)))
            .Returns(true);
        loginController = new LoginController(membershipMock.Object);
    };
}

采纳答案by Alexander Gro?

The context looks good. I like the way you solved the conflicting Itwith aliases. I would argue that the Moq alias can be improved. Consider something sentence-like. For example, Param.Is<T>or Value.Is<T>.

上下文看起来不错。我喜欢你解决It与别名冲突的方式。我认为可以改进 Moq 别名。考虑类似句子的东西。例如,Param.Is<T>Value.Is<T>

Some notes, with code snippets, then the whole spec rewritten at the bottom.

一些注释,带有代码片段,然后在底部重写了整个规范。

The Scenario is your Subject

场景是你的 Subject

The Subject can be the Scenario from the story. Plus, it gets rendered with your test run report (especially nice in the HTML report).

主题可以是故事中的场景。此外,它会与您的测试运行报告一起呈现(尤其是在 HTML 报告中)。

[Subject("Login Page")]

Don't waste time on "With" named base classes

不要在“With”命名基类上浪费时间

MSpec's creator, Aaron Jensen, has revertedfrom using the "With" syntax altogether. Context class names do not show up for any reports, so avoid spending time inventing a meaningful name.

MSpec 的创建者 Aaron Jensen已经完全不再使用“With”语法。上下文类名称不会出现在任何报告中,因此避免花时间发明一个有意义的名称。

public abstract class MembershipContext

The Given is your spec class name

Given 是您的规范类名称

Name the concrete spec class after the Given in your story. Especially since the base class name isn't reported anywhere, you could be losing half your context in the report! You should also avoid putting the name of the system under test in context class names. This makes your contexts friendlier to refactoring the system under test.

以您故事中的 Given 命名具体规范类。特别是因为基类名称未在任何地方报告,您可能会在报告中丢失一半的上下文!您还应该避免将被测系统的名称放在上下文类名称中。这使您的上下文对重构被测系统更加友好。

public class When_an_existing_user_enters_valid_credentials

Base spec classes should contain only general initialization

基本规范类应仅包含一般初始化

And are often unnecessary. They lead to separation of the Arrange and Act phases. Use a base class for common field initialization, like setting up mocked dependencies. But, you should not mock behavior in a base class. And you should not put context-specific information in the base class. In your example, the username/password. This way, you can create a second context with invalid credentials.

并且往往是不必要的。它们导致安排和行动阶段的分离。使用基类进行公共字段初始化,例如设置模拟依赖项。但是,您不应该模拟基类中的行为。并且您不应该在基类中放置特定于上下文的信息。在您的示例中,用户名/密码。这样,您可以使用无效凭据创建第二个上下文。

Establish context = () =>
{
    membership = new Mock<ISiteMembership>();
    loginController = new LoginController(membership.Object);
};

Fields in the concrete spec class should be private

具体规范类中的字段应该是私有的

It reduces the "ceremony" of the language in your test. You should place them below all of the MSpec specific delegates, as those parts of the spec tell most of the story.

它减少了测试中语言的“仪式”。您应该将它们放在所有 MSpec 特定代表的下方,因为规范的这些部分说明了大部分情况。

static ActionResult result;

The Spec Overhaul

规范大修

The spec here is an excellent example of establishing a global context MembershipContextand inheriting it in a context specific to the spec (thus, the additional Establish).

这里的规范是建立全局上下文MembershipContext并在特定于规范的上下文中继承它的一个很好的例子(因此,附加的Establish)。

[Subject("Login Page")]
public class When_an_existing_user_enters_valid_credentials : MembershipContext 
{
    Establish context = () =>
    {
        membership
            .Setup<bool>(m => m.Validate(
                Param.Is<string>(s => s == username), 
                Param.Is<string>(s => s == password)))
            .Returns(true);
    };

    Because of = () => result = loginController.Login(username, password);

    It should_log_the_user_in;
    It should_redirect_the_user_to_the_admin_panel;
    It should_show_message_confirming_successful_login;

    static ActionResult result;
    const string username = "username";
    const string password = "password";
}

public abstract class MembershipContext 
{
    Establish context = () =>
    {
        membership = new Mock<ISiteMembership>();
        loginController = new LoginController(membership.Object);
    };

    protected static Mock<ISiteMembership> membership;
    protected static LoginController loginController;
}