Pull to refresh
8
Karma
0
Rating
Андрей Королёв @koroandr

Javascript-разработчик

Путь длиною в React

Мы для этих целей сделали свой микрофреймворк — intakejs.
В ближайшее время готовим к нему нормальную документацию, а потом и статью напишем.

Тестирование JS. Кармический Webpack

По поводу chai:
Вот пример одной и той же проверки (если нигде не напутал) на chai и на "чистом" jasmine:

expect(Instance).to.have.property('render'); //chai
expect(Instance.render).toBeDefined(); //jasmine

Как по мне, так разница совсем незначительная. Вот я и подумал, вдруг chai дает какие-то убер-плюшки по сравнению с jasmine.

Тестирование JS. Кармический Webpack

Спасибо за статью, сам пользуюсь подобным стеком технологий, отсюда два вопроса:

  1. Скажите пожалуйста, а в чем преимущество chai перед встроенными ассертами jasmine? Просто выглядит это все примерно одинаково (с точностью до синтаксиса), а иметь лишнюю зависимость не хочется. Насколько я понял, chai придумывался больше для работы с mocha.
  2. У Вас завелись source map'ы? У меня примерно такая же связка, только typescript вместо babel'я, я перепробовал все опции webpack на тему source map'ов, ни одна из них не привела к адекватному результату.

Разделяем интерфейсы для юнит-тестирования

Если у вас написание модульного теста занимает 3 экрана — значит, что-то не так с самим тестируемым модулем. Либо его сложно использовать (тогда и тесты, и «настоящий» код, использующий этот модуль, будет громоздким), а значит надо рефакторить интерфейс, либо сложно выделить его зависимости, а это значит у него проблемы с coupling'ом, и опять-таки модуль надо рефакторить.

Есть одно исключение — сложно тестировать код, который не содержит логики, а содержит только связи (создание объектов, проброс событий, проксирование методов). Но ивыгода от тестирования такого кода сомнительна.

Разделяем интерфейсы для юнит-тестирования

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

Разделяем интерфейсы для юнит-тестирования

Ну то, что код надо анализировать, я ни в коем случае не отрицаю, это необходимо независимо от используемых методик и паттернов :)

А вот передавать весь Storage ради двух методов, на мой взгляд, довольно сомнительное решение (в плане coupling'а).

Разделяем интерфейсы для юнит-тестирования

«Смысл» может быть разный:)
Я писал об этом уже в этой же ветке:
В качестве меры «связанности по смыслу» вполне можно использовать «используются вместе».


Вам не кажется, что если какой-то модуль использует набор методов другого модуля — то они связаны по смыслу? Я к тому, что вряд ли при правильном разбиении на модули получится так, что от одного модуля в одном и том же месте нужны две ну совершенно не связанные между собой функции.

И еще, для вашего примера возможно два вполне логичных разбиения: по типу данных (int/string) и по типу аксессора (get/set). Вполне может сложиться такая ситуация, что модулю нужен getInt и setString. Что тогда делать, использовать сразу два интерфейса?

Разделяем интерфейсы для юнит-тестирования

Что значит «нецельный интерфейс»? На мой взгляд, «цельность» интерфейса и есть «связанность методов в нем по смыслу». Так что тут вопрос в том, как эту «цельность» определять.

А изменение интерфейсов (по сути API класса/модуля) — это совсем не то с чем хочется иметь дело часто.

А вот тут не согласен. API от этого не меняется, фасадный интерфейс остается тем же. А вот то, что в клиентском модуле появилась новая зависимость от метода, как раз и отражается в изменении интерфейса.

Разделяем интерфейсы для юнит-тестирования

Ну, о том, что порождается много интерфейсов, в которых надо как-то ориентироваться, я уже писал в самой статье. Да, такая проблема правда существует, и при наличии какого-то соглашения об их именовании это не доставляет большого дискомфорта.

Про логику разбиения на интерфейсы я уже писал где-то ниже, она не связана с тестами, скорее тесты подтверждают ее целесообразность. Конечно, модифицировать код для тестов — не очень хорошая практика, но, с другой стороны, нужно как-то поддерживать баланс между сложностью кода и сложностью тестов. Тем более, на мой взгляд, лучше пускай сложность уйдет на уровень архитектуры, чем остается на уровне реализации.

Есть еще один профит от разбиения на подинтерфейсы по принципу «востребованности» модулями (обратите внимание, я уже ниже писал, что один и тот же метод может встречаться в разных интерфейсах). Оно документирует код. Представьте, допустим у вас есть код, который позволяет купить корзину товаров. Теперь, пусть возникло новое требование: пользователь может в один клик купить один товар, то есть товар теперь реализует интерфейс, необходимый для «покупаемости». Но если мы не разбивали интерфейсы, то выяснить, что же нужно для «покупаемости», мы можем только прочитав код реализации. А при разделении у нас для этого автоматически есть готовый интерфейс, ICartForPurchase.

Разделяем интерфейсы для юнит-тестирования

Тут основной принцип — не чтобы было «легче тестировать», а выделить подинтерфейсы, необходимые для каждого клиента интерфейса подсистемы.
Логика примерно такая: нам нужно разделить интерфейс на части. По какому принципу это делать? Очевидно, давайте связанные по смыслу методы сгруппировывать в отдельные интерфейсы. Остается вопрос — что такое «связанные по смыслу»? В качестве меры «связанности по смыслу» вполне можно использовать «используются вместе». И тестирование, как видите, в этих рассуждениях совсем ни при чем.

Есть один маленький нюанс: разбиение на интерфейсы вида I{Module}For{OtherModule} не обязательно непересекающееся, то есть один и тот же метод может использоваться для разных подсистем. Язык позволяет сделать так при условии, что сигнатуры методов совпадают.

Разделяем интерфейсы для юнит-тестирования

Вполне жизнеспособный подход, согласен. Но есть одно «но»: разделение большого интерфейса на несколько, каждый из которых «заточен» под конкретный модуль, снижает сопряжение для такого модуля. То есть это разделение полезно не только для тестов, но и для самого кода.

Разделяем интерфейсы для юнит-тестирования

Тестируемость — вполне себе неплохая метрика качества кода. Грубо говоря, хороший код и тестировать легко. И если тестировать модуль сложно, то, скорее всего, либо у него не loose coupling, либо не high cohesion.

Просто когда мы начинаем писать юнит-тесты на код, то автоматически ставим тестируемый модуль в условия, в которых он используется в двух разных окружениях. И это заставляет думать на более высоком уровне абстракции.

Понятно, что на приведенных примерах показанные подходы кажутся излишним усложнением. Но когда у вас в фасаде подсистем по 20 методов, и каждому модулю нужна лишь какая-то их часть — разделение их на уровне интерфейсов кажется вполне разумным решением.

Разделяем интерфейсы для юнит-тестирования

Вы совершенно правы, острой нужды в нем нет. Однако, например, это может быть полезно, если у IB есть несколько реализаций. В любом случае, хорошо, когда есть ровно тот интерфейс, который нам необходимо реализовать.

Разделяем интерфейсы для юнит-тестирования

Может, я не очень правильно выразился, но попробую пояснить:
Вот например, есть модуль, который выполняет авторизацию и возвращает токен сессии. А внутри себя он выполняет еще и сохранение сессии в local storage. По идее, получается, что функционал не очень связный, ведь для работы с хранилищем лучше выделить отдельный модуль. А в итоге мы получаем побочный эффект, если, например, не хотели эту сессию сохранять.

Разделяем интерфейсы для юнит-тестирования

Насколько я знаю, в Angular 2 используется подход, очень похожий на Spring. Там как раз TypeScript, начиная с версии 1.5 он поддерживает ES6-декораторы, так что это даже выглядит похоже.

У нас в проекте используется собственная реализация паттерна Service Locator.

TypeScript: общие впечатления

Вообще, с точки зрения классического ООП, конструктор — это часть реализации, а не интерфейса.
Думаю, авторами языка этот костыль был внедрен для того, чтобы можно было писать дефиниции на уже существующие библиотеки (в которых вполне может быть полиморфизм в конструкторе, а в TS полиморфизма в таком понимании нет)

Правильное использование promise в angular.js

В п. 5 имеется в виду асинхронно? А если правда параллельно, то как?

Как хорошо вы знаете стандартную библиотеку?

Пишу на java и javascript, редко на python. В основном подсказками IDE пользуюсь, причем производительность возрасла, когда на столе появилась бумажка с хоткеями этой самой IDE :)

Мне кажется, важно знать основные концепции и подходы (как уже писали выше) и обязательно изучить инструменты разработки под свой язык. Хотя, с другой стороны, есть люди, которые комфортно пишут в простейшем текстовом редакторе и не чувствуют дискомфорта. Тут уж каждому свое.

Прощай, Zen Coding. Привет, Emmet!

Очень хочется такой плагин для IntelliJ IDEA, а то каждый раз переключаться в Sublime для html и css неудобно. Надеюсь, авторы доберутся и до этой IDE.
1

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity