Тихим пятничным вечером я заканчивал свой рабочий день покрывая тестами реализованную логику. Названия тестов я тщательно подбирал как и рекомендовал Рой. И вдруг я осознал, что понятное развернутое название теста превратилось в ужастного монстра и совсем перестало быть понятным. И более того перестало помещаться в 120 символов на экране.
И вот я вспомнил что мне где-то встечалось понятиe Behavior-Driven Development и тесты написанные в этом стиле намного читабельнее и понятнее. По запросу «nunit bdd» гугл выдает совсем немного результатов, но этот пост навел меня на интересную мысль. Методы Given и When не дают четкого описания условия и тем более описать больше чем одно условие наглядно не получится. Я решил слегка доработать решение.
Вот, кстати, то самое имя теста:
CreateMonthlyPaymentInvoice_WhenCustomerIsRegularCompany_ShouldCreateMonthlyUsageInvoiceWithoutBankFee.
Жутко, правда?
Я внес довольно незначительные изменения в подход, описанные в статье. Добавил атрибуты Given, And и When и логику вызова методов отмеченных этими атрибутами.
namespace NUnit.Specification
{
public abstract class SpecificationBase
{
[TestFixtureSetUp]
public void SetUp()
{
Setup();
Given();
When();
}
public virtual void Setup() { }
private void Given()
{
IEnumerable<MethodInfo> publicMethods = this.GetType().GetMethods();
publicMethods.Where(m => m.GetCustomAttributes(typeof(GivenAttribute), false).Count() != 0)
.ToList()
.ForEach(m => m.Invoke(this, new object[0]));
publicMethods.Where(m => m.GetCustomAttributes(typeof(AndAttribute), false).Count() != 0)
.ToList()
.ForEach(m => m.Invoke(this, new object[0]));
}
private void When()
{
this.GetType()
.GetMethods()
.Where(m => m.GetCustomAttributes(typeof(WhenAttribute), true).Count() != 0)
.ToList()
.ForEach(m => m.Invoke(this, new object[0]));
}
[TestFixtureTearDown]
public void TearDown()
{
Teardown();
}
public virtual void Teardown() { }
}
public class SpecificationAttribute : TestFixtureAttribute { }
public class GivenAttribute : Attribute { }
public class AndAttribute : Attribute { }
public class WhenAttribute : Attribute { }
public class ThenAttribute : TestAttribute { }
}
Теперь тесты стали намного более читабельными и гибкими.
[Specification]
public class Create_invoice_for_regular_customer : SpecificationBase
{
private Customer _customer;
private List<Transaction> _transactions;
private XmlDocument _invoice;
public override void Setup()
{
// General context setup like
DateTimeProvider.Current = new Mock<DateTimeProvider>().Object;
Mock.Get(DateTimeProvider.Current).Setup(m => m.GetCurrentTime()).Returns(new DateTime(2012, 10, 26));
}
[Given]
public void Customer_is_regular_company()
{
_customer= new Customer () { Name = "Jedy" } };
}
[And]
public void Set_of_transactions_representing_monthly_usage()
{
_transactions = new[]
{
new Transaction() { Amount = 25 },
new Transaction() { Amount = 16 },
new Transaction() { Amount = 32 },
}.ToList();
}
[When]
public void Creating_monthly_usage_document()
{
_invoice = DocumentCreator.CreateMonthlyPaymentInvoice(_customer, _transactions);
}
[Then]
public void Document_should_contain_transactions_and_not_contain_bank_fee()
{
var expectedInvoice = new ExpectedInvoice();
Assert.That(_invoice, Is.EqualTo(expectedInvoice));
}
public override void Teardown()
{
DateTimeProvider.Current = new DateTimeProvider();
}
}
Если такая штука окажется полезной я попробую создать NuGet пакет для нее.
Спасибо за внимание тем, кто дочитал и хороших выходных.
Автор: firestarter