Як зробити розробку продуктивнішою через автоматизацію юніт-тестів з ChatGPT
Привіт усім! Мене звуть Ярослав. Я працюю в компанії WiX на позиції QA Automation / SDET, і сьогодні я хочу поділитися з вами досвідом використання ChatGPT для написання юніт-тестів на конкретних прикладах.
Юніт-тести є важливою складовою процесу розробки програмного забезпечення, яка дозволяє перевірити правильність роботи окремих компонентів програми. Правильно написані юніт-тести допомагають забезпечити стабільність і надійність коду, полегшують рефакторинг і поліпшують загальну якість продукту.
Одним зі способів використання ChatGPT може бути створення шаблонів для юніт-тестів. Ця модель може допомогти вам сформулювати загальні правила тестування, які можна використовувати для створення багатьох тестових випадків.
Наприклад, ви можете попросити модель згенерувати тестові шаблони для перевірки коректності валідації форм, обробки виключень або взаємодії з базою даних. Це дозволить вам швидко створювати нові тестові випадки на основі заздалегідь визначених шаблонів.
Але написання юнітів зазвичай — ресурсомісткий та не дуже цікавий процес, який може потребувати багато часу. Спілкуючись з розробниками та QA я зрозумів, що більшість з них пише юніти власноруч, і таски з написання юнітів їм не дуже подобаються.
В цій статті я наведу приклади, як я значно скоротив час на їх написання.
Задача
Є метод, який створює селектор на основі заданого атрибуту та матчеру:
export function getSelectorWithAttributeType( key: string, attributeType: AttributeType, attributeMatcher?: AttributeMatcher, ) { const attributeMatcherString = attributeMatcher ? attributeMatcher : AttributeMatcher.EQUALS; return `[${attributeType}${attributeMatcherString}"${key}" i]`; }
де AttributeType — рядок, а attributeMatcher — літерал порівняння.
І є низка методів, яка використовує цей метод обгорткою для певних атрибутів, наприклад:
export function getRoleSelector( key: string, attributeMatcher?: AttributeMatcher, ) { return getSelectorWithAttributeType( key, AttributeType.ROLE, attributeMatcher, );
Таких методів більш як 10, і добре мати хоча б по одному юніту на кожен. Навряд їх взагалі будуть чіпати, але якщо раптом зламають певний з них — багато тестів на кількох проєктах почнуть падати.
Спробуємо натаскати ChatGPT на цю таску так, щоб окрім copy/ paste ніяких дій не потрібно було робити.
Перший крок — описуємо йому задачу:
Отримуємо блок коду:
import { getSelectorWithAttributeType, AttributeMatcher } from './your-file'; describe('getSelectorWithAttributeType', () => { it('should return selector with EQUALS attribute matcher when no attribute matcher is provided', () => { const key = 'value'; const attributeType = 'data-key'; const selector = getSelectorWithAttributeType(key, attributeType); expect(selector).toBe('[data-key="value" i]'); }); it('should return selector with the provided attribute matcher', () => { const key = 'value'; const attributeType = 'data-key'; const attributeMatcher = AttributeMatcher.CONTAINS; const selector = getSelectorWithAttributeType(key, attributeType, attributeMatcher); expect(selector).toBe('[data-key*="value" i]'); }); it('should return selector with case-insensitive matching', () => { const key = 'value'; const attributeType = 'data-key'; const attributeMatcher = AttributeMatcher.EQUALS; const selector = getSelectorWithAttributeType(key, attributeType, attributeMatcher); expect(selector).toBe('[data-key="value" i]'); }); });
Виглядає не так вже і погано. Але були використані не всі матчери.
Отримуємо блок коду:
import { getSelectorWithAttributeType, AttributeMatcher } from './your-file'; describe('getSelectorWithAttributeType', () => { it('should return selector with CONTAINS attribute matcher', () => { const key = 'value'; const attributeType = 'data-key'; const attributeMatcher = AttributeMatcher.CONTAINS; const selector = getSelectorWithAttributeType(key, attributeType, attributeMatcher); expect(selector).toBe('[data-key*="value" i]'); }); it('should return selector with EQUALS attribute matcher', () => { const key = 'value'; const attributeType = 'data-key'; const attributeMatcher = AttributeMatcher.EQUALS; const selector = getSelectorWithAttributeType(key, attributeType, attributeMatcher); expect(selector).toBe('[data-key="value" i]'); }); it('should return selector with STARTS_WITH attribute matcher', () => { const key = 'value'; const attributeType = 'data-key'; const attributeMatcher = AttributeMatcher.STARTS_WITH; const selector = getSelectorWithAttributeType(key, attributeType, attributeMatcher); expect(selector).toBe('[data-key^="value" i]'); }); it('should return selector with ENDS_WITH attribute matcher', () => { const key = 'value'; const attributeType = 'data-key'; const attributeMatcher = AttributeMatcher.ENDS_WITH; const selector = getSelectorWithAttributeType(key, attributeType, attributeMatcher); expect(selector).toBe('[data-key$="value" i]'); }); });
Тепер це більш-менш те, що нам потрібно. Коригуємо імпорти/ перевикористовуємо key, attributeType в expect, і перша частина роботи зроблена.
Формулюємо наступну задачу
Оскільки в обгортках ми використовуємо базову функцію, нам достатньо перевірки, що базова функція була викликана.
Отримуємо код:
import { getDataAidSelector, getSelectorWithAttributeType, AttributeType, AttributeMatcher } from './your-file'; describe('getDataAidSelector', () => { it('should call getSelectorWithAttributeType with key and attributeMatcher for DATA_AID', () => { const key = 'value'; const attributeMatcher = AttributeMatcher.CONTAINS; const getSelectorSpy = jest.spyOn(getSelectorWithAttributeType, 'getSelectorWithAttributeType'); getDataAidSelector(key, attributeMatcher); expect(getSelectorSpy).toHaveBeenCalledWith(key, AttributeType.DATA_AID, attributeMatcher); getSelectorSpy.mockRestore(); }); it('should call getSelectorWithAttributeType with key and default attributeMatcher for DATA_AID', () => { const key = 'value'; const getSelectorSpy = jest.spyOn(getSelectorWithAttributeType, 'getSelectorWithAttributeType'); getDataAidSelector(key); expect(getSelectorSpy).toHaveBeenCalledWith(key, AttributeType.DATA_AID, undefined); getSelectorSpy.mockRestore(); }); });
spyOn мені не підходить в цьому кейсі, бо насправді getSelectorWithAttributeType не є об’єктом.
Коригуємо задачу:
Відповідь бота:
import { getDataAidSelector } from './yourModule'; // import the module containing the function import { getSelectorWithAttributeType, AttributeType } from './your-file'; // import the module containing getSelectorWithAttributeType jest.mock('./your-file', () => ({ ...jest.requireActual('./your-file'), getSelectorWithAttributeType: jest.fn(), })); describe('getDataAidSelector', () => { afterEach(() => { jest.clearAllMocks(); }); it('should call getSelectorWithAttributeType with the specified parameters', () => { const key = 'testKey'; const attributeMatcher = AttributeMatcher.CONTAINS; getDataAidSelector(key, attributeMatcher); expect(getSelectorWithAttributeType).toHaveBeenCalledWith( key, AttributeType.DATA_AID, attributeMatcher ); }); it('should call getSelectorWithAttributeType with only the key parameter', () => { const key = 'testKey'; getDataAidSelector(key); expect(getSelectorWithAttributeType).toHaveBeenCalledWith( key, AttributeType.DATA_AID, undefined ); }); });
Коригуємо тест до остаточного вигляду у проєкті та показуємо боту:
Результат:
import { AttributeMatcher, AttributeType, getDataAutomationIdSelector, getSelectorWithAttributeType, } from '../../src'; jest.mock('../../src/selector/getSelectorWithAttributeType', () => ({ ...jest.requireActual('../../src/selector/getSelectorWithAttributeType'), getSelectorWithAttributeType: jest.fn(), })); describe('Selectors: getDataAutomationIdSelector()', () => { afterEach(() => { jest.clearAllMocks(); }); it('should call getSelectorWithAttributeType with key and provided attribute matcher', () => { const key = 'value'; const attributeMatcher = AttributeMatcher.EQUALS; getDataAutomationIdSelector(key, attributeMatcher); expect(getSelectorWithAttributeType).toHaveBeenCalledWith( key, AttributeType.DATA_AUTOMATION_ID, attributeMatcher, ); }); });
Бот коректно написав назви, вгадав ім’я атрибута в об’єкті і навіть підставив інший матчер, ніж у моєму прикладі, що є гарною практикою.
Тепер я можу згенерувати весь тес- сьют без рев’ю наступних кейсів, що бот буде генерувати для цього випадку. І, звісно, я значно зекономив свій час, бо на всі ці дії мені знадобилось близько
Іноді виникають проблеми, бо третя версія ChatGPT часто втрачає контекст, і процес наведення доводиться починати з нуля.
Висновки
У цій статті я поділився своїм досвідом роботи з ChatGPT для оптимізації написання юніт-тестів на конкретних прикладах.
Завдяки допомозі бота, можна швидко згенерувати основний шаблон тесту та заповнити його конкретними даними.
Крім того, ChatGPT може пропонувати варіації тестів та коригувати код на основі вхідних даних.
Необхідно зауважити, що ChatGPT наразі не може повністю замінити розробника і його досвід у написанні тестів. Проте він може суттєво зекономити час, спростивши рутинні завдання та генеруючи основний шаблон тесту.
11 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів