Создаем NUnit тесты в BDD стиле

в 18:41, , рубрики: .net, bdd, nunit, метки: ,

Создаем NUnit тесты в BDD стилеТихим пятничным вечером я заканчивал свой рабочий день покрывая тестами реализованную логику. Названия тестов я тщательно подбирал как и рекомендовал Рой. И вдруг я осознал, что понятное развернутое название теста превратилось в ужастного монстра и совсем перестало быть понятным. И более того перестало помещаться в 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js