Правильно ли я пишу свои первые спецификации MSpec?

Я пишу свои первые спецификации 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 (некоторые части вырезаны), обратите внимание, что мне пришлось использовать псевдоним делегата MSpec It из-за конфликта с 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);
    };
}

person Community    schedule 26.07.2009    source источник
comment
Да, я вижу, у вас та же проблема с фреймворком Moq и фреймворком MSpec, что и у меня. Как это определяется в обоих. Дох. Хе-хе. +1 за вопрос, я делаю это ТОЧНО, но вижу, что на него уже давно ответили.   -  person eduncan911    schedule 09.12.2009
comment
вы можете посмотреть на NSpec (www.nspec.org). NSpec лучше справляется с конфликтами имен, такими как этот. Он обрабатывает создание контекста немного по-другому (вам не нужно создавать отдельный класс для настройки контекста).   -  person Amir    schedule 31.03.2011
comment
Ну, столкновение имен не проблема, в этом весь смысл предоставления вам псевдонимов. Но я все равно посмотрю.   -  person Sekhat    schedule 01.04.2011


Ответы (1)


Контекст выглядит хорошо. Мне нравится, как вы решили конфликтующие It с псевдонимами. Я бы сказал, что псевдоним Moq можно улучшить. Рассмотрим что-то похожее на предложение. Например, Param.Is<T> или Value.Is<T>.

Некоторые примечания с фрагментами кода, затем вся спецификация переписана внизу.

Сценарий — это ваш Subject

Сюжетом может быть Сценарий из рассказа. Кроме того, он отображается вместе с отчетом о выполнении теста (особенно хорошо в отчете HTML).

[Subject("Login Page")]

Не тратьте время на именованные базовые классы With.

Создатель MSpec, Аарон Дженсен, полностью отказался от использования синтаксиса "With". Имена контекстных классов не отображаются ни в одном отчете, поэтому не тратьте время на придумывание значимого имени.

public abstract class MembershipContext

Given - это имя вашего класса спецификации

Назовите конкретный класс спецификаций в честь данного в вашей истории. Тем более, что имя базового класса нигде не сообщается, вы можете потерять половину своего контекста в отчете! Вам также следует избегать включения имени тестируемой системы в имена классов контекста. Это делает ваши контексты более удобными для рефакторинга тестируемой системы.

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;
}
person Community    schedule 02.08.2009
comment
Не будут ли static в MembershipContext совместно использоваться всеми экземплярами чего-либо, производного от него, что вызовет состояние гонки, если тесты будут выполняться параллельно? - person Steve Dunn; 12.05.2014
comment
Это правильно, но MSpec не будет запускать спецификации параллельно. ReSharper поддерживает параллельную работу, но это достигается для каждой сборки, поэтому совместное использование не происходит внутри одного AppDomain. - person Alexander Groß; 14.05.2014