Як ввести модульне тестування у велику застарілу (C / C ++) кодову базу?


74

У нас є великий мультиплатформенний додаток, написаний мовою C. (з невеликою, але зростаючою кількістю C ++). Він розвивався протягом багатьох років із багатьма функціями, яких ви очікували б у великому додатку C / C ++:

  • #ifdef пекло
  • Великі файли, через які важко виділити перевіряється код
  • Функції, які є занадто складними, щоб їх можна було легко перевірити

Оскільки цей код орієнтований на вбудовані пристрої, запустити його на фактичній цілі дуже багато. Тому ми хотіли б зробити більше нашої розробки та тестування за короткі цикли, на локальній системі. Але ми хотіли б уникнути класичної стратегії "скопіювати / вставити у файл .c у вашій системі, виправити помилки, скопіювати / вставити назад". Якщо розробники збираються зробити це для цього, ми хотіли б мати можливість відтворити ті самі тести пізніше та запускати автоматизовано.

Ось наша проблема: для того, щоб рефакторинг коду був більш модульним, нам потрібно, щоб він був більш перевіреним. Але для того, щоб запровадити автоматизовані модульні тести, нам потрібно, щоб вони були більш модульними.

Одна проблема полягає в тому, що оскільки наші файли настільки великі, ми можемо мати функцію всередині файлу, яка викликає функцію в тому ж файлі, яку нам потрібно заглушити, щоб зробити хороший модульний тест. Здається, це було б меншою проблемою, оскільки наш код стає більш модульним, але до цього ще далеко.

Одне, про що ми думали, - це позначення коментарем вихідного коду, який можна перевірити. Тоді ми могли б написати сценарій сканування скриптів для перевіреного коду, скомпілювати його в окремий файл і зв’язати з модульними тестами. Ми могли повільно запровадити модульні тести, оскільки ми виправляємо дефекти та додаємо більше функціональних можливостей.

Однак є занепокоєння, що підтримка цієї схеми (разом з усіма необхідними функціями заглушки) стане занадто великою проблемою, і розробники припинять підтримувати модульні тести. Тож інший підхід полягає у використанні інструменту, який автоматично генерує заглушки для всього коду, і пов’язує файл із цим. (єдиним інструментом, який ми знайшли, що зробить це, є дорогий комерційний продукт) Але, схоже, такий підхід вимагає, щоб увесь наш код був більш модульним, перш ніж ми зможемо почати, оскільки лише зовнішні дзвінки можна заглушити.

Особисто я волів би, щоб розробники думали про свої зовнішні залежності та розумно писали власні заглушки. Але це може бути приголомшливим, щоб загасити всі залежності для жахливо зарослого файлу в 10 000 рядків. Може бути важко переконати розробників, що їм потрібно підтримувати заглушки для всіх своїх зовнішніх залежностей, але чи це правильний спосіб це зробити? (Ще один аргумент, який я чув, полягає в тому, що супровідник підсистеми повинен підтримувати заглушки для своєї підсистеми. Але мені цікаво, чи "примушування" розробників писати власні заглушки призведе до кращого модульного тестування?)

#ifdefs, Звичайно, додати ще цілий аспект проблеми.

Ми розглянули декілька платформ модульних тестів на основі C / C ++, і є безліч варіантів, які виглядають чудово. Але ми не знайшли нічого, що полегшило б перехід від "волосистої кулі коду без модульних тестів" до "коду, що перевіряється модулем".

Тож ось мої запитання до будь-кого іншого, хто пройшов через це:

  • Що є гарною відправною точкою? Ми йдемо у правильному напрямку, чи нам не вистачає чогось очевидного?
  • Які інструменти можуть бути корисними для переходу? (бажано безкоштовно / з відкритим кодом, оскільки наш бюджет зараз приблизно "нульовий")

Зверніть увагу, що наше середовище збірки базується на Linux / UNIX, тому ми не можемо використовувати жодні інструменти лише для Windows.


5
@ S.Lott вибачте, але як питання щодо модульного тестування омани питання на TDD?
Роб Уеллс,

7
@ S.Lott: Блокове тестування не є TDD. TDD передбачає модульне тестування, але модульні тести також можуть бути модернізовані після написання коду, саме про це задається цим запитанням. Що стосується другого, я також не згоден. Це характерно для C / C ++, мов, які ставлять власні проблеми модульного тестування.
jalf

4
Це питання настільки відповідає моїй ситуації, що я навіть перевірив, чи писав я його!
Маркус Шнелл,

1
Пов’язане ТА питання: stackoverflow.com/questions/91683/… від 18 вересня 2008 року.
linuxbuild

1
@Mike Я бачу, що ви не вибрали відповіді, і я починаю стикатися з подібною проблемою. Можливо, ви могли б поділитися своїм досвідом із цією проблемою та тим, які рішення найкраще працювали для вас за останні 5 років.
robbmj

Відповіді:


49

ми не знайшли нічого, що полегшило б перехід від "волосистої кулі коду без модульних тестів" до "коду, що перевіряється модулем".

Як це не сумно - жодного чудодійного рішення - просто багато важкої роботи, щоб виправити роки накопиченого технічного боргу .

Не існує легкого переходу. У вас велика, складна, серйозна проблема.

Ви можете вирішити це лише крихітними кроками. Кожен крихітний крок передбачає наступне.

  1. Виберіть дискретний фрагмент коду, який є абсолютно важливим. (Не гризти по краях на смітті.) Виберіть важливий компонент, який - як-небудь - можна вирізати з решти. Хоча одна функція ідеальна, це може бути заплутаний кластер функцій або, можливо, цілий файл функцій. Нічого страшного - починати з чогось менш ніж ідеального для ваших перевіряваних компонентів.

  2. З'ясуйте, що він повинен робити. З’ясуйте, яким це повинен бути інтерфейс. Для цього вам, можливо, доведеться зробити деякий початковий рефакторинг, щоб зробити ваш цільовий фрагмент фактично дискретним.

  3. Напишіть "загальний" інтеграційний тест, який поки що - більш-менш тестує ваш дискретний фрагмент коду, як його було знайдено. Нехай це пройде, перш ніж спробувати змінити щось суттєве.

  4. Переробіть код на охайні, перевіряються одиниці, які мають більший сенс, ніж ваші поточні волосся. Вам доведеться підтримувати певну зворотну сумісність (на даний момент) із вашим загальним тестом інтеграції.

  5. Напишіть модульні тести для нових одиниць.

  6. Як тільки все це пройде, виведіть із експлуатації старий API та виправте, що буде порушено зміною. За потреби переробіть оригінальний тест інтеграції; він тестує старий API, ви хочете протестувати новий API.

Ітерація.


Ця відповідь звучить чудово. Але це передбачає, що ми маємо час у графіку, щоб це зробити. ;) Ми можемо в кінцевому підсумку зробити це в основному, але не точно дотримуючись ваших порад. Тобто нам, можливо, доведеться «погризти краї по сміттю». Спам - це будь-який код, який має помилки, або будь-який код, який ми вводимо, або будь-який код, який ми повинні торкнутися, щоб ввести новий код. Напевно, у нас немає розкоші вибрати гігантську, основну функцію для початку.
mpontillo

4
Якщо ви не починаєте з основної функції, тестування є необов’язковим. Менеджери вирішать, що тестування не потрібно, і відмовляться від нього. Якщо ви починаєте з чогось основного, тестування стає важливим.
S.Lott

Я згоден з вами, і хотів би, щоб це було так просто. На жаль, Уолл-стріт дивиться лише на наступний квартал. Тож середній менеджмент хоче, щоб тестування було вкрай важливим, але вищий керівник хоче якомога швидше отримати більше можливостей! Вони хочуть якості, обсягу та розкладу, але вони не бажають приймати розмах чи розклад. Тож нам потрібно знайти рівновагу.
mpontillo

3
@Mike: Це дуже просто. Конфлікт в управлінні - неминуче призведе до зусиль, якщо ви не заглибитесь у щось важливе. Ситуація погана, але не складна. Зберігайте електронні листи та свідомо хихикайте, коли тестування модуля скасовано або скасовано.
S.Lott

1
Біль кожного розробника приєднується до пізнього проекту. :(
Нараяна,

25

Майкл Пір’я написав Біблію з цього приводу, Ефективно працюючи з Кодексом спадщини


так, це справді хороша книга для такого роду речей (хоча це трохи боляче)
Ray Tayek

6
Коротка версія книги: objectmentor.com/resources/articles/…
Еско Луонтола,

1
@EskoLuontola поновлення посилання: web.archive.org/web/20130615005036/http: //www.objectmentor.com / ...
lucrativelucas

8

Мій невеликий досвід роботи із застарілим кодом та впровадження тестування полягав би у створенні " Тестів характеристик ". Ви починаєте створювати тести з відомими вхідними даними, а потім отримуєте вихідні дані. Ці тести корисні для методів / класів, які ви не знаєте, чим вони насправді займаються, але ви знаєте, що вони працюють.

Однак іноді буває практично неможливо створити модульні тести (навіть тести характеристик). У такому випадку я атакую ​​проблему за допомогою приймально-здавальних тестів ( у даному випадку - Фітнесу ).

Ви створюєте цілу купу класів, необхідних для тестування однієї функції та перевірки її на фітнес. Це схоже на "тести характеристик", але це на один рівень вище.


7

Як сказав Джордж, Ефективна робота з Кодексом спадщини є біблією для такого роду речей.

Однак єдиний спосіб, як інші учасники вашої команди будуть купувати гроші, - це якщо вони бачать користь для них особисто, якщо тести працюють.

Для досягнення цього вам потрібна тестова структура, якою простіше у використанні. Плануйте, щоб інші розробники брали свої тести як приклади, щоб писати власні. Якщо вони не мають досвіду юніт-тестування, не сподівайтесь, що вони витратять час на вивчення фреймворку, вони, мабуть, побачать написання модульних тестувань як уповільнення їхнього розвитку, тому незнання фреймворку є приводом пропустити тести.

Приділіть деякий час на безперервну інтеграцію за допомогою круїз-контролю, luntbuild, cdash тощо. Якщо ваш код автоматично компілюється щовечора і запускаються тести, розробники почнуть бачити переваги, якщо модульні тести ловлять помилки до qa.

Одне заохочення - це спільне володіння кодом. Якщо розробник змінює свій код і порушує чужий тест, він не повинен очікувати, що ця людина виправить його, він повинен дослідити, чому тест не працює, і виправити це самостійно. З мого досвіду, це одне з найскладніших речей.

Більшість розробників пишуть певну форму модульного тесту, іноді невеликий фрагмент викинутого коду, який вони не реєструють і не інтегрують збірку. Зробіть їх інтеграцію у збірку простим, і розробники почнуть купувати.

Мій підхід полягає в тому, щоб додати тести для нового, і, коли код модифікується, іноді ви не можете додати стільки або таких детальних тестів, скільки хотіли б, не розв'язуючи занадто багато існуючого коду, помиляючись на практичному.

Єдине місце, де я наполягаю на модульних тестах, - це специфічний код платформи. Якщо #ifdefs замінюється функціями / класами вищого рівня для певних платформ, вони повинні бути протестовані на всіх платформах з однаковими тестами. Це економить час на додавання нових платформ.

Ми використовуємо boost :: test для структурування нашого тесту, прості функції самореєстрації полегшують написання тестів.

Вони загорнуті в CTest (частина CMake), це запускає групу виконуваних файлів модульних тестів одночасно і створює простий звіт.

Наша нічна збірка автоматизована за допомогою ant та luntbuild (мурашині клеї c ++, .net та Java)

Незабаром я сподіваюся додати до збірки автоматичне розгортання та функціональні тести.


5

Ми зараз робимо саме це. Три роки тому я приєднався до команди розробників у проекті без модульних тестів, майже без огляду коду та досить спеціального процесу збірки.

База коду складається з набору COM-компонентів (ATL / MFC), крос-платформного картриджа даних O ++ Oracle та деяких компонентів Java, що використовують крос-платформну основну бібліотеку C ++. Деякому коду майже десять років.

Першим кроком було додавання деяких модульних тестів. На жаль, поведінка дуже керована даними, тому було зроблено певні початкові зусилля щодо створення модульного модуля тестування (спочатку CppUnit, тепер розширеного до інших модулів з JUnit та NUnit), який використовує тестові дані з бази даних. Більшість початкових тестів були функціональними тестами, які вдосконалювали найвіддаленіші шари, а не насправді одиничними тестами. Ймовірно, вам доведеться витратити певні зусилля (на які вам може знадобитися бюджет) для впровадження тестового джгута.

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

За останній рік ми додали безперервну інтеграцію з CruiseControl та автоматизували наш процес збірки. Це додає набагато більше стимулів регулярно перевіряти та здавати тести, що було великою проблемою на початку. Тому я рекомендую вам включити регулярні (принаймні щоночі) модульні тестові запуски як частину вашого процесу розробки.

Нещодавно ми зосередилися на вдосконаленні нашого процесу перегляду коду, який був досить рідкісним та неефективним. Намір полягає в тому, щоб набагато дешевше ініціювати та виконати перегляд коду, щоб розробникам пропонувалося робити їх частіше. Крім того, як частина нашого вдосконалення процесу, я намагаюся отримати час для перегляду коду та модульних тестів, включених до планування проекту, на набагато нижчому рівні, щоб гарантувати, що окремі розробники повинні більше думати про них, тоді як раніше була лише фіксована частка часу, присвяченого їм, набагато легше було загубитися в розкладі.


4

Я працював як над проектом Green Field, так і з повністю протестованими модульними базами кодів, і над великими програмами C ++, які виросли протягом багатьох років, і з багатьма різними розробниками.

Чесно кажучи, я б не турбував спроби отримати застарілу базу коду до стану, де тести модулів та тестування першої розробки можуть додати велику цінність.

Як тільки застарілий код коду досягає певного розміру та складності, добираючи його до тієї межі, коли охоплення модульним тестом надає вам багато переваг, стає завданням, еквівалентним повному перезапису.

Основна проблема полягає в тому, що як тільки ви почнете рефакторинг для перевірки, ви почнете впроваджувати помилки. І лише після того, як ви отримаєте високий рівень охоплення тестом, ви можете очікувати, що всі ці нові помилки будуть знайдені та виправлені.

Це означає, що ви або ходите дуже повільно і обережно, і ви не отримаєте переваг добре перевіреної модульної бази коду до багатьох років. (мабуть, ніколи з того часу, коли не відбуваються злиття тощо.) Тим часом ви, мабуть, впроваджуєте деякі нові помилки без видимого значення для кінцевого користувача програмного забезпечення.

Або ви йдете швидко, але маєте нестабільну базу коду, поки все, що ви не досягли високого тестового охоплення всього коду. (Отже, у вас вийде 2 відділення, одне у виробництві, одне для перевіреної одиниці версії.)

З огляду на те, що все це питання масштабу для деяких проектів, переписування може зайняти кілька тижнів, і, безсумнівно, того варто.


Ви можете трохи рефакторувати код, щоб зробити його перевіряним, потім ви пишете тести, нарешті, виконуєте повний рефактор. Покинути стару основу коду непросто, частіше за все, код містить приховані припущення або "очевидну" (для користувачів) логіку, яка є важливою. Починаючи з нуля - це вірний спосіб їх упустити.
Губерт Каріо

3

Одним з підходів, який слід розглянути, є спочатку встановити загальносистемну систему моделювання, яку можна використовувати для розробки інтеграційних тестів. Починаючи з інтеграційних тестів, може здатися протиреченням інтуїтивно зрозуміле, але проблеми із справжнім модульним тестуванням у середовищі, яке ви описуєте, досить грізні. Можливо, більше, ніж просто моделювання всього часу роботи програмного забезпечення ...

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

PS: Подумайте про написання командної системи моделювання, можливо побудованої на Python або Tcl. Це дозволить вам легко виконувати тести сценаріїв ...


Дуже хороша порада! Маючи хороші тести інтеграції, він може розпочати рефакторинг коду, щоб він був більш модульним та більш перевіряемым. Без взагалі жодних тестів було б занадто ризиковано починати рефакторинг у більш перевіряється на одиниці код.
JacquesB

Дякую за відповідь. Цілісне моделювання було б чудово. Це одна з можливостей, але це величезна кількість роботи. Деякі модулі вже досить відокремлені, щоб розробники могли працювати у своєму власному середовищі моделювання, але зараз я б сказав, що це виняток, а не правило.
mpontillo

3

G'day,

Я б почав з того, щоб поглянути на будь-які очевидні моменти, наприклад, використовувати dec у файлах заголовків для одного.

Потім починайте розглядати, як було викладено код. Це логічно? Можливо, починайте розбивати великі файли на менші.

Можливо, візьміть копію чудової книги Джона Лакоса "Широкомасштабний дизайн програмного забезпечення C ++" ( дезінфіковане посилання на Amazon ), щоб отримати деякі ідеї щодо того, як її слід викласти.

Як тільки ви почнете отримувати трохи більше віри в саму основу коду, тобто макет коду, як у макеті файлу, і очистите деякі неприємні запахи, наприклад, використовуючи dec's у файлах заголовків, тоді ви можете почати вибирати деякі функціональні можливості, які ви можете використовуйте, щоб почати писати свої модульні тести.

Виберіть хорошу платформу, мені подобаються CUnit і CPPUnit, і переходьте звідти.

Однак це буде довга, повільна подорож.

HTH

ура,


2

Набагато простіше зробити його спочатку більш модульним. Ви не можете реально перевірити щось із великою кількістю залежностей. Коли робити рефакторинг, це хитрий розрахунок. Ви дійсно повинні зважити витрати та ризики та вигоди. Чи є цей код чимось, що буде широко використовуватися повторно? Або цей код насправді не зміниться. Якщо ви плануєте продовжувати використовувати це, то, ймовірно, ви хочете здійснити рефакторинг.

Здається, хоча ви хочете зробити рефакторинг. Потрібно почати з вибивання найпростіших утиліт і спиратися на них. У вас є ваш модуль C, який робить мільйони справ. Можливо, наприклад, там є якийсь код, який завжди форматує рядки певним чином. Можливо, це можна виявити як окремий утилітний модуль. У вас є новий модуль форматування рядків, ви зробили код більш читабельним. Це вже вдосконалення. Ви стверджуєте, що потрапили в ситуацію 22. Ви насправді не є. Просто переміщаючи речі, ви зробили код більш читабельним і ремонтопридатним.

Тепер ви можете створити unittest для цього розбитого модуля. Ви можете зробити це декількома способами. Ви можете створити окремий додаток, який просто включає ваш код і запускає купу випадків в основній програмі на вашому ПК, або, можливо, визначити статичну функцію під назвою "UnitTest", яка буде виконувати всі тестові випадки і повертати "1", якщо вони пройдуть. Це може бути запущено на ціль.

Можливо, ви не можете піти на 100% з таким підходом, але це початок, і це може змусити вас побачити інші речі, які легко можна розбити на перевіряються утиліти.


2

Я думаю, в основному у вас є дві окремі проблеми:

  1. Велика база коду для рефактора
  2. Робота з командою

Модуляризація, рефакторинг, вставка модульних тестів тощо - це складне завдання, і я сумніваюся, що будь-який інструмент може взяти на себе більші частини цієї роботи. Це рідкісна навичка. Деякі програмісти можуть це робити дуже добре. Більшість це ненавидять.

Виконання такого завдання з командою нудно. Я сильно сумніваюся, що "примушування" розробників коли-небудь буде працювати. Думки Іайнса дуже добрі, але я б подумав про те, щоб знайти одного-двох програмістів, які можуть і хочуть "зачистити" джерела: Refactor, Modualrize, ввести модульні тести тощо. Нехай ці люди виконують цю роботу, а інші вводять нові помилки, функції Aehm. Тільки люди, яким подобається така робота, досягнуть успіху на цій роботі.


Дуже важливий момент про те, щоб "правильний" тип людей виконував цю роботу. Це може змінити реальні проекти. Крім того, це полегшує врахування витрат (n додаткових людей, а не n людей, які віддають немірну кількість свого дорогоцінного часу).
Роберт,

1

Зробіть тести легкими.

Я б почав з того, щоб поставити "автоматичний запуск" на місце. Якщо ви хочете, щоб розробники (включаючи вас) писали тести, спростіть їх запуск і перегляньте результати.

Написання тесту з трьох рядків, проведення його проти останньої збірки та перегляд результатів має бути на відстані одного клацання , а не надсилання розробника до кавомашини.

Це означає, що вам потрібна остання збірка, можливо, вам доведеться змінити політику, як люди працюють з кодом і т. Д. Я знаю, що такий процес може бути PITA із вбудованими пристроями, і я не можу дати з цим жодної поради. Але я знаю, що якщо виконувати тести важко, їх ніхто не напише.

Перевірте, що можна перевірити

Я знаю, що тут я протирічу загальній філософії Unit Test, але ось що я роблю: пишу тести на те, що легко перевірити. Я не турбуюся з глузуванням, я не рефакторирую, щоб зробити це перевіряним, і якщо є інтерфейс, я не маю юніт-тесту. Але все більше і більше моїх бібліотечних процедур мають таку.

Я дуже вражений тим, що зазвичай виявляють прості тести. Збирати низько звисаючі плоди аж ніяк не марно.

Дивлячись на це по-іншому: Ви не планували б підтримувати цю гігантську безладдя, якби це не був успішний продукт. Ваш поточний контроль якості - це не повний збій, який потрібно замінити. Скоріше, використовуйте модульні тести там, де їх легко зробити.

(Однак вам потрібно це зробити. Не зациклюйтеся на "виправленні всього" навколо процесу збірки.)

Навчіть, як покращити свою базу коду

Будь-яка база коду з цією історією вимагає вдосконалення, це точно. Однак ви ніколи не переробите все це.

Розглядаючи дві частини коду з однаковою функціональністю, більшість людей можуть погодитись, яка з них є "кращою" за певного аспекту (продуктивність, читабельність, ремонтопридатність, перевіряність, ...) Твердих частин три:

  • як збалансувати різні аспекти
  • як погодитись, що цей шматок коду досить хороший
  • як перетворити поганий код на досить хороший, не зламавши нічого.

Перший момент - це, мабуть, найскладніше і таке ж соціальне, як і технічне питання. Але з інших моментів можна дізнатися. Я не знаю жодних офіційних курсів, які б застосовували такий підхід, але, можливо, ви можете організувати щось власне: що завгодно: від двох хлопців, що працюють разом, до "майстер-класів", де ви берете неприємний шматок коду та обговорюєте, як його вдосконалити.



1

У всьому цьому є філософський аспект.

Ви дійсно хочете перевірений, повністю функціональний, охайний код? Це ВАША мета? Чи ВИ отримуєте від цього взагалі якусь користь ?.

так, спочатку це звучить абсолютно по-дурному. Але чесно кажучи, якщо ви не справжній власник системи, а не просто працівник, тоді помилки просто означають більше роботи, більше роботи означає більше грошей. Ви можете бути повністю щасливі, працюючи над волоссям.

Я просто здогадуюсь тут, але, ризик, який ви берете на себе, взявшись за цю величезну боротьбу, напевно, набагато вищий, ніж можлива окупність, яку ви отримуєте, опрацюючи код. Якщо вам не вистачає соціальних навичок, щоб це зробити, вас просто сприйматимуть як проблемного. Я бачив цих хлопців, і я теж був таким. Але, звичайно, це дуже круто, якщо ви все-таки це пройдете. Я був би вражений.

Але, якщо ви відчуваєте, що зараз знущаєтесь витрачати зайві години, щоб тримати неприбрану систему, чи справді ви думаєте, що це зміниться, як тільки код стане охайним і приємним ?. Ні .. як тільки код стане приємним і охайним, люди отримають весь цей вільний час, щоб повністю знищити його знову в перший доступний термін.

зрештою, керівництво створює приємне робоче місце, а не код.


0

Не впевнений, це актуально чи ні, але я маю тут невеличку пораду. Як я розумію, ви задаєте методологічне запитання про поступову неінвазивну інтеграцію модульного тестування у величезний застарілий код із великою кількістю зацікавлених сторін, які захищають своє болото.

Зазвичай першим кроком є ​​побудова коду тестування незалежно від усіх інших кодів. Навіть цей крок у застарілому коді дуже складний. Я пропоную побудувати ваш код тестування як динамічну спільну бібліотеку з підключенням під час виконання. Це дозволить вам переробляти лише невеликий фрагмент коду, який тестується, а не цілий файл 20K. Отже, ви можете почати охоплювати функції за функціями, не торкаючись / виправляючи всі проблеми зв’язування

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.