Комментарии 20
Итак, взяли смешной детский пример, бесконечно далёкий от реальности, написали 29 строчек тривиальнейшего кода, и 50 строчек тестов. Наверное, это хорошо. Непонятно только, почему.
И, разумеется, все тесты нафиг поломаются, если мы заменим одну либу модальных окон на другую с таким же API. Всё равно поломаются.
Что конкретно призвана иллюстрировать статья? Что на простейший для понимания код легко и непринуждённо можно написать еще больше еще более сложного кода тестов?
И, разумеется, все тесты нафиг поломаются, если мы заменим одну либу модальных окон на другую с таким же API. Всё равно поломаются.
Что конкретно призвана иллюстрировать статья? Что на простейший для понимания код легко и непринуждённо можно написать еще больше еще более сложного кода тестов?
-2
Привет, JustDont! Спасибо за комментарий. Цели моей статьи — рассказать какие преимущества дает TDD, сравнить с test last подходом и показать на упрощенном примере с продакшена, как можно начать разрабатывать по TDD.
В конце статьи я специально оставил опросник, чтобы в случае заинтересованности разобрать более сложные темы и примеры разработки по TDD.
В конце статьи я специально оставил опросник, чтобы в случае заинтересованности разобрать более сложные темы и примеры разработки по TDD.
0
рассказать какие преимущества дает TDD, сравнить с test last подходом и показать на упрощенном примере с продакшена, как можно начать разрабатывать по TDD
Проблема в том, что ваш рассказ о преимуществах никак не коррелирует с приведенным примером. По преимуществам всё шоколадно, а в примере вы приводите тесты, которые во-первых не являются «юнит-» в строгом смысле определения, а во-вторых в половине случаев тестируют трюизмы.
0
а вы как то формализовали что именно вы проверяете в тестах, а что нет? например css расположение элементов? сколько сценариев к компоненту?
есть какие то критерии по которым вы говорите что тут полный тест а тут не полный?
есть какие то критерии по которым вы говорите что тут полный тест а тут не полный?
0
Привет, artemu78. Спасибо за вопросы! Касательно того, что тестируем, как я и сказал в статье, верстку мы тестами не покрываем. Покрывается все, что касается компонентов, логики и работы со стором. Количество сценариев зависит от сложности каждого конкретного компонента. При правильной разработке по TDD у вас не возникают вопросы о полноте тестов или количестве сценариев. Так как сначала вы пишите тест, а затем просто хотите этот тест “позеленить”, в таком случае, ваши тесты всегда будут полными.
+1
Как мне кажется, минус в виде замедления скорости разработки, весьма существенный, с точки зрения бизнеса.
0
Modin, спасибо за комментарий! Вы правы, как я и сказал в статье, вначале будет заметное снижение скорости, и поэтому важно это бизнесу объяснить. Но в продуктовых компаниях, нацеленных на длительные рост и развитие, важнее не кратковременная просадка, а скорость и качество в длительном будущем. Поэтому в нашей компании, мы рассматриваем и пробуем любые варианты, которые в долгосрочной перспективе принесут нам больше пользы.
0
Спасибо за статью! Вы молодцы! Я считаю, что использование в команде TDD — это уровень!
Мне тоже нравится TDD, но я скептически отношусь к тестированию UI-компонентов. На мой взгляд, это слишком сложно. А инструменты для тестирования UI настолько наворочены, что из-за этого легко потерять суть. Поэтому я упоролся и попробовал перенести код компонента из примера на обычные объекты. Типа, упрощённая аналогия, чтобы проанализировать код и подумать головой при написании тестов. Сложно сказать, что из этого получилось. Но ясно одно — через TDD я бы так не написал.
TDD — это не про то, что «пиши тесты на всё, пиши тесты впереди, будь героем!». TDD — это про то, что «пиши так, чтобы было максимально легко тестировать». В данном случае, видимо, мощность инструментов сводит TDD на нет.
Ну а теперь разбираемся… По сути ApplicationPublishModal — это враппер над переданной/захардкоженой модалкой. Он (почему-то) управляет открытием закрытием модалки, которая по идее могла бы управлять этим сама. Даже текст тестов намекает, что тут что-то не так. Ну и второе, что он делает, это определяет заголовок и текст кнопки. И тут самое оно! Если вы хотите быть уверенными, что заголовок/текст генерируется правильно в зависимости от флажка, вынесите это в отдельную чистую функция и покройте 10 тестами. Это будет супер легко, и по-TDD. Остальное — просто дублирование текста из файла с кодом, в файл с тестами. Не тратье свою жизнь на тестирование прокидывания параметров. Это ж рекурсия!)
Извините, если слишком резко. Но это мой мнение. Вот пример моих тестов по TDD.
Резюме: Вы на правильном пути. Но в системе есть места, типа функции main, которые сложно и бесполезно тестировать. Я считаю UI-компоненты такими местами. Лучший способ продолжать двигаться путем TDD — вытаскивать из компонентов всё что можно, оперировать чистыми функциями и объектами, тренироваться на них. Желаю удачи!
Мне тоже нравится TDD, но я скептически отношусь к тестированию UI-компонентов. На мой взгляд, это слишком сложно. А инструменты для тестирования UI настолько наворочены, что из-за этого легко потерять суть. Поэтому я упоролся и попробовал перенести код компонента из примера на обычные объекты. Типа, упрощённая аналогия, чтобы проанализировать код и подумать головой при написании тестов. Сложно сказать, что из этого получилось. Но ясно одно — через TDD я бы так не написал.
TDD — это не про то, что «пиши тесты на всё, пиши тесты впереди, будь героем!». TDD — это про то, что «пиши так, чтобы было максимально легко тестировать». В данном случае, видимо, мощность инструментов сводит TDD на нет.
Упрощённая версия компонента
export interface IModal {
close(): void;
}
export interface IModalParams {
opened: boolean;
width: number;
title: string;
buttonText: string;
onButtonClick: () => void;
}
export class ApplicationPublishModal {
constructor(
private appName: string,
private isPublic: boolean,
private createModal: (params: IModalParams) => IModal,
private onPublish: () => void,
) {}
// аналог render()
init(): void {
const modal = this.createModal({
opened: true,
width: 480,
title: this.getTitle(),
buttonText: this.getButtonText(),
onButtonClick: () => {
this.onPublish();
modal.close();
},
});
}
private getTitle(): string {
return !this.isPublic ? `Publish ${this.appName} app` : `Republish ${this.appName} app`;
}
private getButtonText(): string {
return !this.isPublic ? 'Publish' : 'Republish';
}
}
Тесты
import { ApplicationPublishModal, IModal, IModalParams } from './q';
export class ModalStub implements IModal, IModalParams {
opened: boolean = false;
width: number = 0;
title: string = '';
buttonText: string = '';
onButtonClick: () => void = () => {};
close(): void {
this.opened = false;
}
}
describe('ApplicationPublishModal', () => {
it('should open modal on init', () => {
const modal = new ModalStub();
const applicationPublishModal = createApplicationPublishModal({ modal });
applicationPublishModal.init();
expect(modal.opened).toBe(true);
});
it('should set modal width', () => {
const modal = new ModalStub();
const width = 480;
const applicationPublishModal = createApplicationPublishModal({ modal });
applicationPublishModal.init();
expect(modal.width).toBe(width);
});
it('should close modal on modal button click', () => {
const modal = new ModalStub();
const applicationPublishModal = createApplicationPublishModal({ modal });
applicationPublishModal.init();
modal.onButtonClick();
expect(modal.opened).toBe(false);
});
it('should call onPublish callback on modal button click', () => {
const modal = new ModalStub();
const spy = jasmine.createSpy();
const applicationPublishModal = createApplicationPublishModal({ modal, onPublish: spy });
applicationPublishModal.init();
modal.onButtonClick();
expect(spy).toHaveBeenCalled();
});
describe('should provide title to modal', () => {
it('if app not public', () => {
const modal = new ModalStub();
const applicationPublishModal = createApplicationPublishModal({
modal,
appName: 'AppName',
isPublic: false,
});
applicationPublishModal.init();
expect(modal.title).toBe('Publish AppName app');
});
it('if app public', () => {
const modal = new ModalStub();
const applicationPublishModal = createApplicationPublishModal({
modal,
appName: 'AppName',
isPublic: true,
});
applicationPublishModal.init();
expect(modal.title).toBe('Republish AppName app');
});
});
describe('should provide button text to modal', () => {
it('if app not public', () => {
const modal = new ModalStub();
const applicationPublishModal = createApplicationPublishModal({ modal, isPublic: false });
applicationPublishModal.init();
expect(modal.buttonText).toBe('Publish');
});
it('if app public', () => {
const modal = new ModalStub();
const applicationPublishModal = createApplicationPublishModal({ modal, isPublic: true });
applicationPublishModal.init();
expect(modal.buttonText).toBe('Republish');
});
});
});
interface ApplicationPublishModalParams {
appName?: string;
isPublic?: boolean;
modal: ModalStub;
onPublish?: () => void;
}
function createApplicationPublishModal({
appName = '',
isPublic = false,
modal,
onPublish = () => {},
}: ApplicationPublishModalParams): ApplicationPublishModal {
return new ApplicationPublishModal(
appName,
isPublic,
(params) => Object.assign(modal, params),
onPublish,
);
}
Ну а теперь разбираемся… По сути ApplicationPublishModal — это враппер над переданной/захардкоженой модалкой. Он (почему-то) управляет открытием закрытием модалки, которая по идее могла бы управлять этим сама. Даже текст тестов намекает, что тут что-то не так. Ну и второе, что он делает, это определяет заголовок и текст кнопки. И тут самое оно! Если вы хотите быть уверенными, что заголовок/текст генерируется правильно в зависимости от флажка, вынесите это в отдельную чистую функция и покройте 10 тестами. Это будет супер легко, и по-TDD. Остальное — просто дублирование текста из файла с кодом, в файл с тестами. Не тратье свою жизнь на тестирование прокидывания параметров. Это ж рекурсия!)
Извините, если слишком резко. Но это мой мнение. Вот пример моих тестов по TDD.
Резюме: Вы на правильном пути. Но в системе есть места, типа функции main, которые сложно и бесполезно тестировать. Я считаю UI-компоненты такими местами. Лучший способ продолжать двигаться путем TDD — вытаскивать из компонентов всё что можно, оперировать чистыми функциями и объектами, тренироваться на них. Желаю удачи!
+3
Самое любопытное, что в статье выдвинут тезис, что, мол, TDD лучше, потому что придётся сначала подумать над тем, что писать. И затем в качестве примера выдвигаются тесты, где тестируется прокидывание пропсов в захардкоженный библиотечный компонент.
Страшновато себе представить, что вот это вот и выдаётся за результат «подумали». Что же там было бы без «подумали»?
А юнит-тестирование чистых функций — это очень полезная штука, да. TDD или не TDD — дискуссионно, но сами юнит-тесты в этих случаях очень нужны. Насчёт остального (рендера и т.д.) — я придерживаюсь мысли, что упирать тут в «юнит-» не только бесполезно, но и вредно. Хорошие функциональные тесты реактовских компонентов я видел. Хорошие юнит-тесты — неа. Они или не «юнит», или ничего полезного не тестируют, или и то и другое.
Страшновато себе представить, что вот это вот и выдаётся за результат «подумали». Что же там было бы без «подумали»?
А юнит-тестирование чистых функций — это очень полезная штука, да. TDD или не TDD — дискуссионно, но сами юнит-тесты в этих случаях очень нужны. Насчёт остального (рендера и т.д.) — я придерживаюсь мысли, что упирать тут в «юнит-» не только бесполезно, но и вредно. Хорошие функциональные тесты реактовских компонентов я видел. Хорошие юнит-тесты — неа. Они или не «юнит», или ничего полезного не тестируют, или и то и другое.
0
Ещё не учтен момент про 40-90% багов, которые и в самих тестах проскочат, но не будут считаться багами, ведь покрыто)
0
Согласен, проще было тогда уж написать e2e тест на Cypress, по крайней мере было бы больше уверенности что с изменением компонента ничего не сломалось, критерием что компонент работает будет что-то в реальном браузере.
0
Привет, Justerest! Спасибо за твой крутой содержательный комментарий и проделанный эксперимент! Понимаю откуда появляются вопросы к примеру, в этой статье я сознательно старался его упростить т.к. хотел, чтобы эта статья стала некоторым мотиватором начать, попробовать экстремальное программирование по TDD. Соответственно сам пример будет интересен в основном новичкам TDD, опытным разработчикам будет интереснее почитать о преимуществах и сравнении test first с test last. Однако теперь я вижу, что разбор более сложных примеров и тем будет интересен многим, поэтому уже начинаю думать над следующей статьей более продвинутого уровня, с упором на примеры.
Кроме того, спасибо за пожелания, и вам желаю удачи!
Кроме того, спасибо за пожелания, и вам желаю удачи!
+1
А есть какая-нибудь ретроспективная статистика? Объективная или хотя бы субъективная?
Тип по ощущением стало меньше мелких багов после изменений, или чё-то такое?
Тип по ощущением стало меньше мелких багов после изменений, или чё-то такое?
0
Привет, discopalevo. Спасибо за вопросы! Я думал над добавлением в заключение более точных цифр и графиков, но все-таки не стал перегружать этим статью. Если говорить о багах, то их число, в компонентах написанных по TDD, моментально снизилось и действительно приблизилось к нулю, благодаря чему нам удается не отвлекаться на это в спринте. Если говорить о скорости разработки, то этот график будет более плавным, из спринта в спринт кодовая база на TDD и опыт позволяют нам ускоряться, и если сравнивать емкость спринта сейчас и полгода назад, то при тех же ресурсах, мы делаем примерно в полтора раза больше задач.
0
Сам подход не нравится. Что будет если например потребуется отобразить предпросмотр перед публикацией и кнопку отменить? Или открыть модальное окно ещё раз?
И компонент Modal может превратиться в монстра с 20 возможными пропсами.
И компонент Modal может превратиться в монстра с 20 возможными пропсами.
const ApplicationPublishModal = ({ actionLabel, appName, isPublic, publish }) => {
return (<Modal>
<Container width={480}>
<Title>{actionLabel} {appName} app</Title>
<Button onClick={publish}>
`${isPublic ? 'Rep' : 'P'}ublish`
</Button>
</Container>
</Modal>
)
}
0
Эта методология разработки мотивирует в первую очередь подумать, а не приступить. Это позволяет глубже погрузиться в задачу и не упустить всевозможные корнер-кейсы.
А без TDD и тестов разве не нужно сначала подумать и вникнуть в задачу, перед тем как писать код? Разве не нужно продумывать всевозможные эдж-кейсы?
При test last подходе не нужно много думать — можно сразу переходить к написанию кода.
А если подумать много заранее? получается можно обойтись без TDD?
Т.е. получается какая разница где думать много — перед написанием кода, или перед написанием тестов? Всё-равно подумать много надо в любом случае. Получается TDD — лишнее звено?..)
+1
Если код пишет толпа джуниоров то TDD и Typescript нужны чтобы гарантировать хоть какое-то качество кода.
0
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
REACT + JEST = TDD ❤️