пишем только чистые функции. Вроде как ооп прикольно тем, что создаем абстракцию какой-то сущности с публичным интерфейсом для работы. Но в ооп почти все методы не чистые (из-за работы с this), из-за этого сложно юнит тестировать. Как найти эту золотую середину? Смириться с тем, что тру юнит тестами нельзя покрыть класс?
Во первых в ООП методы и не должны быть чистые и вообще весь класс надо рассматривать как единый юнит, главное чтобы все зависимости инжектились через конструктор (можно конечно и через сеттеры но не желательно). ТОгда и тестировать будет легко и код будет чистым ФП и ООП прекрасно существуют по отдельности можно их не смешивать.
у меня ФП и ООП прекрасно существуют совместно
Прям без переменных с функторами и монадами?
Да, например, я могу заинжектить приватный внутренний массив, в котором Colection хранит элементы Это решит проблему при тестировании Но сделает использование класса очень неудобным, т.к. пользователям вообще не нужны эти детали, где именно collection хранит свои элементы
зачем тут лишняя терминология? просто функции как значения и функции с методами
Ну это не ФП, дело не в терминологии. Просто ты используешь лямбда функции и все.
а можно заижектить не в самой коллекции, а в её потомке и использовать для тестирования его?
глянь на это https://github.com/Svoloch/etc-js и скажи это ФП или ООП
Тогда тест (вот эта часть с потомком и инжектом) зависит от внутренностей класса. С таким успехом я могу тупо взять и заассертить приватное поле
модифицируй именно класс коллекции чтобы он сам проверял что в нём поменялось
Похоже функциональный стиль но нехватает таких вещей как композиция функций, я бы рекомендовал в ФП стиле рамду https://ramdajs.com/docs/
композиция это then (если я правильно понимаю это понятие)
Если бы фп и ооп прекрасно существовали по отдельности, в джаве бы на было лямбд, а в c# linq и делегатов
Хм. Это как?
В JS уже есть оператор композиции функций — |>
можешь показать краткий вариант кода коллекции и обёртки над ней, которую тестируешь? (если я правильно понял архитектуру твоего приложения) и тесты тоже покажи
на данном этапе предпочитаю избегать всего что за пределами шестого стандарта
Ну вот minimal case, с которым возникла проблема у меня class Collection { _items = []; push(item) { this._items.push(item); } } // test describe(‘Collection’, () => { it(’should push items’, () => { const collection = new Collection(); collection.push(‘something’); assert(); // assert what?? }) })
так это не js, я то думаю откуда такие странные проблемы
Да блин, сейчас исправлю на js, по привычке написал на ts
Исправил
да оно и в js скомпилит так же, приватные поля там и так будут видны (или я ошибаюсь?)
Да, будут видны приватные поля, если только стандарт не выставлен на ES-из-последних Но не суть, вопрос же не в этом )
если видны, то результат модификации можно проверить
Тесты должны тестировать публичный интерфейс, который предоставляет класс. например, любые изменения _items не должны ломать тесты, если публичное API остается нетронутым
проверяйте приватные поля класса через Reflect.get
Проблема не в том, что я не могу получить приватные поля, а в том, что это делать неправильно в юнит тестах
Ты ж наверное не просто так туда добавляешь items наверное как то можешь их читать? Вот и надо тестировать метод который читает, либо ты вызываешь какой то другой класс, тогда с помощью. spy тестируешь что вызванному методу передались нужные items
Это плохая идея, тестировать надо только публичный интерфейс
Да, я могу сделать collection.push(…); assert.deepEqual(collection.getAll()); Просто это уже не юнит тест — если что-то сломаем в getAll, то наш тест push упадет, хотя не должен. Но похоже, это единственный выход
Юнит в ООП это класс а не метод
Хммм, а вот это многое объясняет..! Если это действительно так, то я могу в юнитах спокойно использовать несколько методов и не стабить если один метод использует другие этого класса? А где можно почитать про эту концепцию
Где почитать точно не скажу, когда то проходил курс по тестированию и смысл был в том что все зависимости замокали и тестируем как единую сущность
В контексте юнит тестов доступ к приватным полям класса это рефлексия, поэтому вполне нормально
Я просто в таких случаях: class Class { // … method() { this.anotherMethod1(); this.anotherMethod2(); } } Стабал anotherMethod1 и 2 при тестировании method. То есть этого делать не стоит?
Стабить надо только внешние зависимости а тестировать через публичный интерфейс
Я руководствовался тем, что юнит — один метод, поэтому стабал (изменения в anotherMethod не приводят к падению теста method)
Ну хуже от этого не будет но так можно тесты до бесконечности писать ) И еще тесты будут хрупкие, то есть если что то в классе изменится надо тест переписывать, а тесты не должны быть хрупкими
А почему так тесты будут более хрупкими? Типа если поменяется реализация anotherMethod, а стаб останется старым, то тесты начнут падать?
Ну да надо поддерживать больше стабов и при каждом рефакторинге приватных методов рефакторить еще и тест
Нет, приватные я не стабаю, только публичные
А зачем стабить публичные если их и так можно проверить?
Ну типа следуя концепции юнит теста, что тестируем ровно 1 вещь Если юнит — метод, тогда при падении теста я со 100% увернностью могу сказать, что проблема в этом конкретном методе, и не затрагивает другие
Я понял, теоретически так можно но это ты слишком глубоко копнул, поддерживать будет тяжело. Да и писать кучу лишних моков. Я обычно замокал все внешние зависимости, протестил инициализацию обьекта и все паблик методы, если какой то метод приватный но очень сложный и очень хочется его потестить делаю метод чистым и выношу в хелпер и тестирую этот хелпер
Понял, спасибо огромное за советы ) А интересно, возможно ли (и стоит ли) сделать такой класс, как приведенный выше collection, immutable?
Ну это зависит от того какие требования к программе, не забывай что immutable сущности жрут значительно больше пямяти
Обсуждают сегодня