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

发布于 2024-07-29 16:19:36 字数 1982 浏览 4 评论 0原文

我正在编写第一个 MSpec 规范,我需要一些指导。 我将规范保留为“待处理”状态,但上下文已填写。 有什么需要改进的地方吗?

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

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

MSpec 代码(某些部分被剪掉),请注意,由于与 Moq.It< 冲突,我必须为 MSpec 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);
    };
}

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?

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 It delegate due to a conflict with 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);
    };
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

雪化雨蝶 2024-08-05 16:19:36

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

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

场景是您的主题

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

[Subject("Login Page")]

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

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

public abstract class MembershipContext

Give 是您的规范类名称

在故事中的 Give 后面命名具体的规范类。 特别是由于基类名称没有在任何地方报告,您可能会丢失报告中一半的上下文! 您还应该避免将被测试系统的名称放在上下文类名称中。 这使得您的环境更适合重构被测系统。

public class When_an_existing_user_enters_valid_credentials

基础规范类应该只包含一般初始化

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

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

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

它减少了测试中语言的“仪式”。 您应该将它们放置在所有 MSpec 特定委托的下方,因为规范的这些部分讲述了大部分内容。

static ActionResult result;

规范大修

此处的规范是建立全局上下文 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;
}

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

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

The Scenario is your 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).

[Subject("Login Page")]

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

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

public abstract class MembershipContext

The Given is your spec class name

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.

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.

static ActionResult result;

The Spec Overhaul

The spec here is an excellent example of establishing a global context MembershipContext and inheriting it in a context specific to the spec (thus, the additional 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文