Правильно ли я пишу свои первые спецификации 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);
};
}
Решение
Контекст выглядит хорошо.Мне нравится, как вы разрешили противоречивые It
с псевдонимами.Я бы сказал, что псевдоним Moq можно улучшить.Рассмотрим что-нибудь вроде предложения.Например, Param.Is<T>
или Value.Is<T>
.
Несколько заметок с фрагментами кода, а затем вся спецификация переписана внизу.
Сценарий — ваш Subject
Сюжетом может быть сценарий из рассказа.Кроме того, он отображается вместе с отчетом о тестовом запуске (особенно приятно в отчете HTML).
[Subject("Login Page")]
Не тратьте время на именованные базовые классы с именем «With».
Создатель MSpec Аарон Дженсен вернулся от использования синтаксиса «With» вообще.Имена классов контекста не отображаются ни в каких отчетах, поэтому не тратьте время на придумывание осмысленного имени.
public abstract class MembershipContext
Данное — это имя вашего класса спецификации.
Назовите конкретный класс спецификации в честь данного в вашей истории.Тем более, что имя базового класса нигде не указывается, вы можете потерять половину контекста в отчете!Вам также следует избегать помещения имени тестируемой системы в имена контекстных классов.Это делает ваш контекст более удобным для рефакторинга тестируемой системы.
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;
}