Как говорится: «Запретный плод сладок», так и у меня. Попробовав однажды писать тесты на RSpec, хочется иметь декларативный BDD DSL в каждом языке. Вот например JavaScript, имеет аналоги mocha.js, jasmine.js, etc. Но нет, мало. Хочется не просто всяких describe-ов или it-ов а еще и ленивых переменных, я имею введу subject и let.
На первый взгляд глупо! Внутренний голос кричит «Зачем?», а совесть в ответ: «Чистый код — это важно! Ну а простые тесты — вообще мега важно!».
Вот так и родилась библиотека для mochajs, которая позволяет создавать ленивые переменные (aka let) и `subject`.
Для тех кто понимает о чем я и уже напрягся засветился от радости, милости просим на Github.
Всем остальным, а в особенности скептикам предлагаю заглянуть под cut.
Почему это вообще кому-то важно?
Вот почему!.
Ну а теперь серьёзно
Что обычно пишут в тестах?
describe('Invoice', function() { var invoice, user; beforeEach(function() { user = User.create({ role: 'member' }); invoice = user.invoices.create({ price: 10, currency: 'USD' }); }); it('has status "fraud" if amount does not equal to invoice amount', function() { invoice.paid(1, 'USD'); expect(invoice.status).to.equal('fraud'); }); it('has status "fraud" if currency does not equal to invoice currency', function() { invoice.paid(10, 'ZWD'); expect(invoice.status).to.equal('fraud'); }); ..... })
Вроде бы все отлично, счет создан, пользователь создан, оплата отклоняется если от платежной системы пришло меньше денег, чем хотелось… Но когда нужно подменить пользователя или создать счет с другими параметрами мы приходим к более плачевному варианту
describe('Invoice', function() { var invoice, user; describe('by default', function() { beforeEach(function() { user = User.create({ role: 'member' }); invoice = user.invoices.create({ price: 10, currency: 'USD' }); }); it('has status "fraud" if amount does not equal to invoice amount', function() { invoice.paid(1, 'USD'); expect(invoice.status).to.equal('fraud'); }); it('has status "fraud" if currency does not equal to invoice currency', function() { invoice.paid(10, 'ZWD'); expect(invoice.status).to.equal('fraud'); }); }); describe('when user is admin', function() { beforeEach(function() { user = User.create({ role: 'member' }); invoice = user.invoices.create({ price: 10, currency: 'USD' }); }); it('has status "paid" if amount does not equal to invoice amount', function() { invoice.paid(1, 'USD'); expect(invoice.status).to.equal('paid'); }); }); ..... })
Т.е., просто берем дублируем setup, передаем другие параметры и воуля! Да здравствует копи-паст… А переменные кто будет чистить в `afterEach`?
Лень против копи-паста!
Одна из задач которую решает эта библиотечка — это уничтожение копи-паста! Как именно? Да просто
describe('Invoice', function() { def('user', function() { return User.create({ role: 'member' }); }); def('invoice', function() { return $user.invoices.create({ price: 10, currency: 'USD' }); }); describe('by default', function() { it('has status "fraud" if amount does not equal to invoice amount', function() { $invoice.paid(1, 'USD'); expect($invoice.status).to.equal('fraud'); }); }); describe('when user is admin', function() { def('user', function() { return User.create({ role: 'admin' }); }); it('has status "paid" if amount does not equal to invoice amount', function() { $invoice.paid(1, 'USD'); expect($invoice.status).to.equal('paid'); }); }); ..... })
Кода стало меньше, копи-пасты меньше, прозрачность выше! Ура! Мало того, переменные удаляются после каждого теста самостоятельно и Вам не нужно писать `afterEach` блоки. Удобно?
Note: знак `$` к переменным добавлен во избежание коллизий с именами. Если такая переменная уже существует — получаем exception.
А теперь о том как это работает
Ленивые переменные на то и ленивые, что создаются только в момент доступа к ним. Т.е., в последнем `describe` наш `$invoice` создается внутри `it` (а не `beforeEach`), но уже с другим пользователем: вместо обычного создается админ. Таким образом произошла подмена и счета теперь привязываются к нашему админу, который может творить все, что угодно.
Теперь думаю понятно, что ленивые переменные создаются в контексте suite-а, а не теста и что писать `def` внутри теста нелогично (знаю, знаю все мы умные люди, но я просто должен был это написать).
В конце концов, что на выходе?
- Ленивость! Больше никаких лишних вызовов. Не позволяем тестам быть медленными
- Возможность компонировать переменные
- Отсутствие копи-паста
- Предусмотрительная очистку переменных после каждого `it`
- И еще парочку маленьких фич в придачу о которых можно почитать на досуге в README
Тесты в одну строчку?
Как уже выше было упомянуто, библиотека позволяет определять `subject` для теста
describe('Invoice', function() { subject(function() { var admin = User.create({ role: 'member' }); return Invoice.create({ price: 10, currency: 'USD', user: admin }) }); it('has status "pending" by default', function() { expect($subject.status).to.equal('pending'); });
Что в свою очередь приводит нас к синтаксису
describe('Invoice', function() { subject(function() { var admin = User.create({ role: 'member' }); return Invoice.create({ price: 10, currency: 'USD', user: admin }) }); its('status', () => isExpected.to.equal('pending')); // or even better it(() => isExpected.to.be.pending)
Этого пока нет, но достаточно просто сделать имея ES6 фичи в рукаве и возможность создавать `subject` в тестах.
P.S.: для тех кому не хватает `sharedExamples` в JavaScript тестах предлагаю посмотреть еще и эту статью
P.P.S.: SOLID в тестах важнее SOLID во всех других местах.
ссылка на оригинал статьи https://habrahabr.ru/post/114047/
Добавить комментарий