РСС

Ношу шлем, тяжело дышу…

Меня зовут Антон Шувалов. Я работаю в Lazada. Кроме программирования я пишу музыку и иногда занимаюсь дизайном интерфейсов. Я есть в Twitter, Facebook, и на GitHub. Вы можете написать мне email.

Если вы задумали порадовать меня небольшим подарком (не может быть!) — вот список моих мещанских мечт.

Попробовал в TDD. Люто-бешено доставляет

На самом деле — нет. Немного не TDD. Думаю, что правильнее назвать это стремлением к 100%-покрытию кода тестами: у меня нет «красненьких» и «зелененьких» этапов и рефакторинга после n-ного цикла. А иногда (бог ты мой) я пишу код до написания тестов.

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

Однажды в подъезде друзья предложили мне попробовать тесты…

Примерно около года назад я начал писать под NodeJS. Вся эта философия модульности меня очень впечатляла — я мог делить свой код на небольшие компоненты, публиковать их в NPM, подключать к разным проектам одной командой.

npm i -S my-awesome-module

Большинство модулей, которые я находил в npm или на GitHub имели тесты. Более того, они использовали CI-сервис «Travis», который автоматически тестировал каждый коммит и пулл-реквест. Тогда мне это казалось каким-то излишеством. Я помню, как я разбирался с устройством LocomotiveJS, и наткнулся на модуль, который я затем показывал друзьям и говорил: «Ребята! Я решил писать так!».

Шутил, значит.

Вообще, код @jaredhanson мне очень нравится. Изучая его я заметил, что некоторые тесты — всего лишь проверка типа в module.exports.

var merge = require('../index');

describe('utils-merge', function() {
    
  it('should export function', function() {
    expect(merge).to.be.a('function');
  });
  
});

Тут-то я и задумал попробовать тесты. Написать такую штуку можно очень быстро. А «Travis.CI», прогнав тест, скажет мне наверняка — глупой ошибки синтаксиса в коде нет. Кроме того, очень быстро можно написать assert’ы, которые будут проверять соответствие результатов нашим ожиданиям.

describe('merge', function() {

  describe('an object', function() {
    var a = { foo: 'bar' }
      , b = { bar: 'baz' };
    var o = merge(a, b);
    
    it('should merge properties into first object', function() {
      expect(Object.keys(a)).to.have.length(2);
      expect(a.foo).to.be.equal('bar');
      expect(a.bar).to.be.equal('baz');
    });
    
    it('should return first argument', function() {
      expect(o).to.be.equal(a);
    });
  });

  // ...

});

Сейчас в моем проекте есть файл с тестами поведения API ExpressJS в котором около 1500 строк. Большинство из них — assert’ы, написанные банальной копипастой. Нет, это не очень-то правильно. Да, это работает.

Сейчас, имея под рукой около 60-ти тестов я понимаю, насколько рационально было бы разделить их на отдельные features, где каждый файл отвечает только за конкретную фичу, не содержит проверок, этой фичи не касающихся. В следующий раз я поступлю именно так, и я действительно понимаю почему.

Коллеги, я вот приду сейчас домой и точно напишу документацию!

Еще одна полезная особенность тестов — это документация. Идея писать тесты на свой код посетила меня еще и потому, что я стал с завидной регулярностью находить себя читающим тесты, а не докуменацию. А началось это с того, что я, разбираясь с MongooseJS, не смог понять, почему же в моем случае .populate() работает совсем не так, как я этого ожидал. Тесты, в отличии от документации, оказались более полными. Возможно, даже более понятными.

Таким образом я пришел к пониманию того, что на основе достаточно полных тестов я могу написать документацию тогда, когда я захочу. Более того, на практике это не составило особого труда.

Вот только, в отличии от документации, тесты всегда актуальны. 1:0, документация!

Эти ваши «состояния потока»

Я помню, как еще год назад мне приходилось держать в голове детали устройства системы, чтобы понимать, в каком месте может появиться брешь при добавлении очередной фичи. Затем, значит, во время разработки фичи я, достаточно регулярно сталкивался с непроясненными требованиями: «В каком виде возвращать значения?», «Боже мой! Мы же не договорились, как он загрузит файл! А вдруг…», «В ТЗ ничего об этом не было, а я забыл спросить менеджера!»… Все это вырывало мой аватар из состояния «потока», и перемещало его в сказочно-прекрасное состояние «ПАНИКААА!!!111121 МНЕ ЖЕ ЗАВТРА НАДО!!!!1». Да и вообще, регулярно отвлекаясь на чтение обычно весьма косноречивых ТЗ, я так боюсь расплескать свою чашу концентрации.

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

А еще я ленив. Сейчас я вспоминаю, как я «тестировал» другое приложение, тоже на express, в Postman, руками отправляя запросы, вглядываясь в прыгающие в глазах буквы и цифры в теле ответа. Все это отнимает драгоценную концентрацию, я становлюсь толстым. А если подумать, сколько полезного времени уходит на это? Ведь тестировать приходится не один, и даже не 10 раз.

Сейчас мы тут костылик напишем, но завтра обязательно все исправим

Это как и с документацией. Смещается фокус. Очищается память. Это бы, конечно, ничего, но ведь если мы и доберемся через неделю до этого костылеточáщего кода, то не сломаем ли мы ненароком систему в каком-нибудь неожиданном месте, переписав все «нормально»? А если переписывать не 10 строк? А если порефакторить захочется, чтобы внукам потом показать и листинги в рамку чтобы?

Беда-беда, огорчение. Сломать что-то случайно — это вам за милую душу. Ваши 2 часа работы окажутся парой дней, а может даже больше. А рефакторить — так, это вообще Сизифов труд — это бесконечно можно. Не страшно?

Мне страшно. И всегда страшно было. Я всегда с опаской смотрю на такие вещи. Думаю, это главная причина, по которой я убежден в том, что сразу написать «нормально» — лучше. Лучше, чем написать костыль, который пообещать себе обязательно исправить. Лучше, и, наверное, даже быстрее.

Так вот, любители рефакторинга, с тестами можно предаваться своим утехам, без страха устроить второе пришествие вашему руководителю, например.

Кода

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

А теперь я бы хотел попросить вас, друзья, о совете. Очевидно, что TDD — это не просто набор тестов. Так же очевидно, что люди, которые разработали эту методологию — отличные ребята. Я бы хотел попросить вас, друзья, посоветовать мне книгу, которую стоит обязательно прочитать, чтобы нормально научиться «в TDD».

«Как рушатся комплексные системы», Ричард И. Кук
О фундаментальных проблемах больших запутанных систем
7 паттернов для рефакторинга JavaScript-приложений
Перевод отличной серии статей о проектировании и рефакторинге проектов
Музыка для работы
Мои плейлисты: теплый glitch, нежные девичьи голоса, интересная электроника и chillwave
Ссылколог
Коллекционирую полезные ссылки