Keil uvision mac os

Keil uvision mac os

How to run EE319K, EE445L , EE445M software on a Mac.

So here is a short summary of what you need:
1. A Mac preferably one that has a Intel Core Duo processor or better

2. A virtualization software . Here are some choices:
a. Parallels (http://www.parallels.com/): $79 retail but the campus computer store has a better deal ($39.99 for Mac OS X)
b. VMware Fusion (http://www.vmware.com/products/fusion/): $50 retail but again the campus computer store may have a better deal
c. VirtualBox (https://www.virtualbox.org/): Its FREE Parallels is the most feature rich of the lot but VMware Fusion is the fastest. Of course Virtualbox is free like free beer!

3. Windows 7 OS CD from the campus computer store ($34 I think). I suggest you go to the Microcenter and carefully explain to the staff what you are attempting to do, so you get the correct version of Windows 7 (and the cheapest price).

4. How to install Keil uVision for the Arm, MDK-Lite (32KB) Edition. It does not require a serial number or license key.:
a. Go to https://www.keil.com/demo/eval/armv4.htm
Enter your contact information with valid address, phone and email (use your official UT email address),
Company = University of Texas,
devices=TM4C123,
Click submit
b. Right-click on MDK474.EXE (590,659K) and save it to your computer (do not install Keil version 5.xx)
c. Install in step 4 before. Directions on how to configure Keil.

5. PCBartist (http://www.4pcb.com/). Be careful when downloading not to agree to any add ons . This free program tries to install other adware.

This is the order in which you should proceed with the installation.
1. Install the virtualization software. The first two options are pretty straightforward, the third I have not run myself. Once you complete your installation, you will be presented with instructions on creating virtual images of the OSs you would like to run.
2. You will install Windows within the virtualization software you choose. Here is the prompt I get from Fusion when I choose File->New Insert the Windows CD and go through the steps of installing the OS. This should take around 45 mins or so.
3. Now you are ready to run Windows. Again, you will open your virtualization application and choose the Windows OS you just installed to be run. My Fusion screen looks like below showing Windows Vista as the OS that I can run: Click on the play button and you will boot the Windows OS. You will be running both Windows and Mac OS at this point. There are some neat features in both Fusion and Parallels that allow you to seamlessly integrate the running of applications from both Operating systems.
4. Install Keil by downloading it from the link (use Microsoft Explorer) above. This will also install all the drivers you need to communicate with the board.
5. Run Keil and connect the board to your Mac using USB. Try the Blink example shown in class.
6. Install PCBartist , and open one of the starter files from the lab.

An EE319K student reports this experience. “I am using a mid-2012 MacBook, trying to run Keil uVision on a Windows 7 image running inside VMWare Fusion. After much frustration, I have found that VMWare Fusion currently improperly handles the USB 3.0 ports on mid-2012 MacBooks , rendering the LM3S1968 eval board unable to properly communicate with the necessary tools. This can be fixed by either running Windows natively (via bootcamp , without virtualization) or by connecting the board via an external USB 2.0 hub. Connecting it through an external hub allows the board to be used inside VMWare Fusion.”

An ECE senior student reports: «I was reading through the lab manual for your spring 2013 445M class and read your «run EE 445L/445M/460M software on Macs» instructions. I would like to provide some feedback on that (not the instructions but my experiences on running EE software on macs). I have not tried VMFusion or VirtualBox but I have been running Parallels 7 for the last two years. I have had two macs (high end 2011 macbook pro and low end 2012 retina macbook pro) and both have been able to run Keil, PCBArtist, and even the Xilinx and ModelSim software for my VHDL class flawlessly. Parallels is the more expensive of the 3 but it has served me well for all of my windows software needs, and I would like to confirm that Parallels is probably the safest bet to be able to do the labs for this and other classes. «

Источник

Keil uVision

Среда разработки, представляющая собой набор утилит для выполнения полного комплекса мероприятий по написанию программного обеспечения для микроконтроллеров.

Keil uVision позволяет работать с проектами любой степени сложности, начиная с введения и правки исходных текстов и заканчивая внутрисхемной отладкой кода и программированием ПЗУ микроконтроллера. От разработчика скрыта большая часть второстепенных функций, что сильно разгружает интерфейс и делает управление интуитивно понятным. Однако при возрастании сложности реализуемых задач, всегда можно задействовать весь потенциал модулей, функционирующих под управлением единой оболочки. Среди основных программных средств Keil uVision можно отметить.
1. Базу данных микроконтроллеров, содержащую подробную информацию обо всех поддерживаемых устройствах. Здесь хранятся их конфигурационные данные и ссылки на источники информации с дополнительными техническими описаниями. При добавлении нового устройства в проект все его уникальные опции устанавливаются автоматически.
2. Менеджер проектов, служащий для объединения отдельных текстов программных модулей и файлов в группы, обрабатываемые по единым правилам. Подобная группировка позволяет намного лучше ориентироваться среди множества файлов.
3. Встроенный редактор, облегчающий работу с исходным текстом за счет использования многооконного интерфейса, выделения синтаксических элементов шрифтом и цветом. Существует опция настройки в соответствии со вкусами разработчика. Редактирование остается доступным и во время отладки программы, что позволяет сразу исправлять ошибки или отмечать проблемные участки кода.
4. Средства автоматической компиляции, ассемблирования и компоновки проекта, которые предназначены для создания исполняемого (загрузочного) модуля программы. При этом между файлами автоматически генерируются новые ассемблерные и компиляторные связи, которые в дальнейшем позволяют обрабатывать только те файлы, в которых произошли изменения или файлы, находящиеся в зависимости от изменённых. Функция глобальной оптимизации проекта позволяет достичь наилучшего использования регистров микроконтроллера путем неоднократной компиляции исходного кода. Компиляторы uVision работают с текстами, написанными на Си или ассемблере для контроллеров семейств ARM, MSC51, C166 и многих других. Кроме того возможно использование компиляторов других производителей.
5. Отладчик-симулятор, отлаживающий работу скомпилированной программы на виртуальной модели микропроцессора. Довольно достоверно моделируется работа ядра контроллера и его периферийного оборудования: портов ввода-вывода, таймеров, контроллеров прерываний. Для облегчения комплексной отладки разрабатываемого программного обеспечения возможно подключение программных моделей нестандартного оборудования.
6. Дополнительные утилиты, облегчающие выполнение наиболее распространенных задач. Число и набор меняется от версии к версии. Выделяют следующие из них:
• Source Browser, содержащую базу данных программных символов для быстрого поиска;
• Find in Files, предназначенную для поиска заданного кода во всех файлах указанной папки или проекта;
• Tools Menu, позволяющую использовать утилиты сторонних производителей;
• PC-Lint, анализирующую исходный текст программы с выделением потенциально опасных мест;
• Flash tool, программирующую FLASH-память микроконтроллеров.

Среда программирования разработана компанией Keil, которая была основана в Мюнхене в 1982 году братьями Гюнтером и Рейнхардом. В октябре 2005 года Keil вошла в состав американской корпорации ARM. На сегодняшний день она представляет широкий спектр различных средств для разработки программ, включающих Cи-компиляторы, макроассемблеры, отладчики, симуляторы, линкеры, IDE-приложения и оценочные платы для различных семейств микроконтроллеров.

Программа Keil uVision является платной и стоит очень больших денег. По ссылке ниже, после заполнения анкеты можно скачать демонстрационную версию. Разумеется, в ней присутствует ряд ограничений и основное из них – 32 КБ на размер программы.

Среда разработки Keil uVision представлена на английском языке.

Программа работает на персональных компьютерах под управлением только операционной системы Windows версий 2000, XP, Vista и 7.

Распространение программы: Shareware (платная). Есть демоверсия с рядом ограничений, в т.ч. на размер кода — не более 32 КБ.

Источник

Юнит-тесты в uVision Keil (и не только)

Не утихают споры о том, нужны ли юнит-тесты вообще, а если нужны — то как именно их писать. Сначала писать код или сначала писать тесты? Допустимо ли нарушать инкапсуляцию при тестировании или же можно трогать только публичное API? Сколько процентов кода должно быть покрыто тестами?

Тестирование во встраиваемых системах тоже порождает немало споров. Точки зрения разнятся от «покрытие должно быть 100% + нужны испытательные стенды» до «какие еще тесты, я программу написал — значит все работает».

Я не хочу начинать холивар и вооще стараюсь придерживаться некоего разумного баланса. Поэтому для начала предлагаю рассмотреть самые «низко висящие» плоды, которые позволяет сорвать юнит-тестирование применительно к embedded-разработке.

  1. Можно писать код для устройства, которого у вас еще нет. Это может быть датчик с алиэкспресса, который почтой идет две недели или какой-нибудь отечественный девайс со сроком поставки в полгода — это не столь важно.
  2. Можно менять код и не бояться, что ваши изменения сломают что-то, что работало раньше (если это что-то покрыто тестами, разумеется).
  3. Следствие предыдущего пункта: вы можете передавать разработку другому человеку и тоже не бояться, что он сломает что-то, что раньше работало.
  4. Если вы пишете какие-нибудь библиотеки (в минимуме — просто какой-то код, который используется не один раз), то с помощью тестов вы можете синтезировать ситуации, которые «руками» можно ловить очень долго.
  5. В тестовой конфигурации можно использовать всякие дополнительные инструменты для оценки качества/надежности кода — санитайзеры, опции компилятора для защиты от переполнения стека и тому подобные вещи, которые в «боевую» прошивку не влезают

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

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

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

Далее пойдет речь о юнит-тестировании кода для встраиваемых систем — в основном, микроконтроллеров (МК). Предполагается что код на С и С++

Как можно запускать тесты?

На мой взгляд, глобально можно выделить 3 способа:

  1. На обычном ПК (настольном или на тестовом сервере, не суть)
  2. На физическом МК — т.е. на специальной тестовой плате
  3. В симуляторе МК

Давайте разберем плюсы и минусы каждого подхода.

На обычном ПК

  • тесты выполняются максимально быстро
  • у ПК много памяти, тестовый бинарник может быть существенно больше, чем релизная прошивка
  • не нужна физическая плата; тестовое окружение можно сделать легко воспроизводимым
  • можно использовать дополнительный инструментарий — valgrind, санитайзеры
  • легко выводить результаты тестов — в stdout, в красивое окно, в текстовый файл — на ваш вкус

Другой компилятор и другая стандартная библиотека. Это плюс, потому что позволяет отловить платформозависимый код, получить больше предупреждений от нового компилятора. Но не факт, что вы к этому стремитесь. Минус, собственно, в этом же — придется писать платформонезависимый код, скорее всего, оборачиваясь в условную компиляцию; иногда придется подкостыливать код для работы под компилятором, который вам вообще и не нужен для собственно рабочей прошивки.

  • почти наверняка другая архитектура процессора — т.е. какие-то вещи могут работать не так, как в релизной прошивке на целевом устройстве (например, какие-нибудь тонкие отличия между программной реализацией плавающей точки в МК и аппаратной у х86)
  • нужно держать параллельный проект для другого компилятора/системы сборки. Трудоемкость этого зависит от системы сборки, которую вы выберете.
  • трудно тестировать прерывания
  • трудно тестировать работу с ОСРВ
Читайте также:  Как самостоятельно переустановить windows без диска

На физическом МК

  • архитектура та же, что у целевого устройства
  • тесты выполняются достаточно быстро
  • легко тестировать прерывания и работу с ОСРВ
  • можно тестировать работу с внешними устройствами

Спорный момент все тот же, только с другой стороны — компилятор, стандартная библиотека и рантайм такие же, как и в релизе.

  • мало памяти — как для кода, так и оперативной
  • нужна физическая тестовая плата, источник питания и т.д.
  • нужен какой-то интерфейс для связи с ПК или с чем-то еще, чтобы выводить результаты тестирования
  • короче, нужен тестовый стенд

В симуляторе

  • архитектура примерно та же, что и на целевом устройстве
  • не нужна плата или тестовый стенд
  • симулированной памяти может быть много
  • если симулятор сможет, то можно тестировать прерывания и работу с ОСРВ

Спорный момент все тот же.

  • нужен симулятор
  • самый медленный вариант из трех
  • симулятор не совсем идентичен физическому МК, это может породить тонкие проблемы

Следующий интересный вопрос — что покрывать тестами, а что нет?

Что тестировать?

С одной стороны, некоторые вещи тестить легко, а некоторые — тяжело. С другой, некоторые вещи как будто сильнее нуждаются в тестах, а другие — слабее.

Если разобраться, то легко тестить код, который слабо завязан на какие-то сложные концепции или аппаратную специфику. Например, общение с каким-нибудь датчиком легко абстрагируется от реального физического интерфейса — такому коду вполне достаточно умения принять пачку байт и отправить пачку байт.

А, скажем, код для управления BLDC-двигателем тестируется тяжело, потому что для этого нужна, фактически, виртуальная модель двигателя. Или, скажем, попиксельное рисование на каком-нибудь OLED-экране гораздо проще проверять визуально.

А вот насколько сильно вещи нуждаются в тестах, сильно зависит от задачи.

Что же выбрать? Тут я, опять-таки, не берусь давать универсальных советов, а вместо этого попытаюсь обосновать наш выбор с исторической точки зрения. Если вам это не очень интересно, то можете следующую главу смело пропустить.

Историческая перспектива и локальная специфика

Сперва специфика. Тут я не придумал, как можно эти факты упорядочить или сгруппировать, поэтому просто перечислю.

Сперва вещи, над которыми программисты не властны:

  • проектов (т.е. изделий) много, их разработка часто происходит одновременно. Длительность проектов от полугода до полутора лет; что-то более долгоиграющее попадается редко
  • каждое изделие содержит одну, но чаще несколько плат с микроконтроллерами, эти платы связываются друг с другом какими-нибудь интерфейсами, поскольку должны взаимодействовать
  • изделия сами по себе разные, но в них есть повторяющиеся или похожие «куски», как-то:
    • общение по интерфейсам связи между платами
    • управление DC и BLDC двигателями
    • общение с разнообразными датчиками и прочими покупными изделиями
    • работа с матричными клавиатурами и матрицами светодиодов
  • некоторые изделия должны быть выполнены на отечественной элементной базе

А остальное — просто «исторически сложилось», иногда без всякой рациональной причины:

  • почти всегда действует правило «одна прошивка — один разработчик». С одной стороны это снижает трудозатраты на слияния, но с другой стимулирует людей «окукливаться» в своем мирке, не обмениваться кодом и переизобретать велосипеды
  • вся команда сидела под Windows, в качестве IDE использовался uVision Keil. Тут нет рациональных причин, только рационализации 🙂
  • МК — только ARM Cortex; в основном — STM32, для «отечки» — Миландр. Во многом дело вкуса.
  • изначально разработка велась на чистом С, но очень медленно перешла на С++
  • никто из команды (включая меня) не учился на программиста
  • никаких аджайлов, скрамов и прочих методов выпаса кошачьих не применялось
  • начиналась вся эта история примерно в 2013 году — C++11 появился совсем недавно, Keil был версии 4.20 вроде бы и С++11 не поддерживал

К чему же все это привело? А вот к чему.

Разработчики разобщены и стремятся закуклиться, поэтому любые инновации нужно подавать постепенно; они не должны требовать установки 10 программ и полной смены рабочего процесса. Поэтому отпадает вариант «тестирование на ПК» — это потребовало бы ставить какую-то другую IDE, другой компилятор, вести в нем параллельный проект — слишком сложно.

Поэтому же отпадают все варианты, которые требуют ставить Linux (например, запуск тестов в QEMU).

Использование тестовой платы отпадает по умеренно-объективным причинам — нужна плата (и желательно не одна), просто слишком много возни по сравнению с чисто программными вариантами.

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

Запуск тестов в симуляторе Keil’a

Выбор фреймворка

Я, честно говоря, уже плохо помню, чем я руководствовался, когда выбирал фреймворк, слишком много лет прошло. Кажется, я успел посмотреть на GoogleTest, но счел его слишком большим и сложным, фреймворки на чистом С были страшненькими и либо нуждались в ручной регистрации каждого теста, либо опирались на сторонние скрипты, с которыми связываться было не очень охота.
А у CppUTest был пример для IAR’a (а это почти как Keil).

В целом, это не очень важно. Главное, что CppUTest достаточно легко скомпилировался в Keil’e; для этого пришлось создать всего один файл, наполненный платформозависимыми функциями. Практически все оттуда я передрал из аналогичного файла для IAR, если честно.

Для полноты картины, привожу здесь этот файл целиком:

Помимо этого файла, во всем проекте пришлось разрешить исключения (ключом —exceptions ) и выделить достаточно много памяти в стеке и в куче (килобайт по 15 примерно). Чтобы не засорять этим сборку, для тестов я создал отдельную конфигурацию — в Keil это называется «target».

Ну и, конечно же, пришлось убрать ключ —c99 , прописанный в опциях компилятора, потому что с ним Keil даже.срр-файлы пытался компилировать как сишные.

В последних версиях Keil’a этой проблемы нет, поскольку режим С99 включается галочкой, которая действует только на файлы с расширением.с.

Вывод результатов

Вывод CppUTest делает просто printf’ами в терминал, но ведь у МК нет терминала. Как же быть?

На отладочной плате можно было бы воспользоваться выводом в UART, а результаты на компе смотреть, скажем, в putty — но симулятор Keil’a далеко не для всех МК поддерживает симуляцию UART’a.

К счастью, у многих МК на ядре Cortex есть специальный отладочный интерфейс ITM. Если пользоваться полноразмерным разъемом JTAG (или если вы пользуетесь отладчиком STLink через разъем SWD, то у этого разъема должна быть подключена нога SWO) и ваш аппаратный отладчик этот самый ITM поддерживает, то в stdout можно в него и перенаправить.

При этом, поскольку ITM является периферией уровня ядра, симулятор Keil’a поддерживает его всегда! Вывод при этом появляется в отладчике, в окне View->Serial windows->Debug (printf) .

Чтобы сделать это перенаправление, потребуется переопределить несколько функций стандартной библиотеки. Это я делал методом проб и ошибок, на полноту и правоту не претендую, но вроде бы работает без нареканий. Перенаправлял я только stdout , поскольку в stdin и stderr нужды не испытывал.

Сами тесты

Тут, на самом деле, никаких отличий от обычных тестов в CppUTest нету, но я все же приведу пример для наглядности

Впечатления от CppUTest

  • быстро заработал в симуляторе
  • не требуется ручная регистрация тестов

  • требует С++, все тесты компилируются как код на С++ (но этот минус нивелируется переходом на С++)
  • относительно много файлов в фреймворке
  • требуется динамическая память и исключения. Справедливости ради скажу, что вместо исключений можно использовать setjmp/longjmp , но уж лучше исключения!
  • поскольку исключения используются постоянно, отладка по шагам превращалась в очень увлекательное путешествие по библиотечному коду с внезапными прыжками туда-сюда
  • поскольку тесты — это методы, названия тестов должны быть валидными идентификаторами. То есть CamelCase или snaking_case , но NeitherOfThemIsVeryReadable when_test_name_is_long_enough , а названия тестов хочется делать подробными.
  • макросов для сравнения как-то уж слишком много:
    • CHECK — окей, все ясно
    • CHECK_TEXT — хорошо, проверка с поясняющим текстом
    • CHECK_FALSE — хмм, но почему бы не написать CHECK( .. == false) ?
    • CHECK_EQUAL — э?
    • STRCMP_EQUAL — стоп, серьезно? Но я ведь сам могу strcmp написать.
    • STRNCMP_EQUAL — да ладно
    • LONGS_EQUAL — CppUTest остановись, ты пьян!
    • UNSIGNED_LONGS_EQUAL — аааа.
      Нет, я все понимаю, у специализированных макросов обычно более понятный вывод, но это уже явно перебор! Да и Catch2 как-то справляется с одним REQUIRE .
  • к тому же, мне весь этот более понятный вывод был до лампочки, поскольку я почти сразу начал практиковать TDD; в 90% случаев я заранее знал, какая именно проверка должна провалиться
  • когда тест валился, в терминал выводилось сообщение типа такого:

и потом провалившуюся проверку приходилось отыскивать по имени теста и номеру строки

  • несколько раз тесты падали, потому что где-то в глубинах фреймворка не хватало стека или кучи, но понять это было очень сложно; неопытному мне из прошлого код казался очень запутанным

Разумеется, почти все эти минусы совершенно субъективные. У CppUTest большая и преданная аудитория, которую, вероятно, все устраивает. Но я решил на этом не останавливаться и двинулся дальше. А дальше мне на глаза попался munit.

Munit

Как я уже упоминал когда-то давно в своем посте про юнит-тесты на чистом С, munit — это самый маленький фреймворк для юнит-тестирования; настолько маленький, что его можно привести прямо в тексте статьи целиком:

Для меня в тот момент это было просто откровение! Никакого С++, никаких классов для тестов, произвольные строки в качестве названий и просто return 0 , если все проверки пройдены! Никакой чехарды с исключениями!

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

Этим фреймворком мы даже успели попользоваться какое-то время, но после относительно масштабного проекта, так же написанного на С, но в ООП стиле, мы, вдоволь нахлебавшись виртуального наследования вручную, сказали ХВАТИТ ЭТО ТЕРПЕТЬ и решили переходить на С++.

Ну а раз вся разработка переходит на С++, то можно и тестовый фреймворк обновить и избавиться от этой мути с BOOST_PP_COUNTER .

UmbaCppTest

И так был рожден новый велосипедный фреймворк для юнит-тестирования! Ееее! Кратко пробегусь по ключевым фишкам:

  • автоматическая регистрация тестов (легко, когда есть конструкторы)
  • названия тестов — произвольные строки
  • не требуются исключения
  • не требуется динамическое выделение памяти
  • минимум макросов для проверок — фактически, только UMBA_CHECK( cond, text ) , но text — опциональный
  • фреймворк из двух файлов (один.срр и один .h)
  • «киллер-фича»: если проверка провалилась, то отладка останавливается на проблемной строке! Это тоже легко, симулятор корректно выполняет ассемблерную инструкцию BKPT ; очень удобно!
  • киллер-фича опциональная, можно как обычно — просто вывести в терминал сообщение, что тест провалился и выполнять тесты дальше
  • поскольку фреймворк самодельный, его можно было легко и быстро дорабатывать по ходу дела, допиливать фичи, которые нужны только нам

А запуск всех тестов выглядит как-то так:

При этом пользователь должен создать конфигурационный файл для фреймворка, который обязан называться umba_cpp_test_config.h и определить в нем несколько вещей:

Конечно, фреймворк очень простой, поэтому минусов у него тоже полно:

  • нет автоматической генерации моков, фаззинга и прочих классных фишек
  • проверочные макросы нельзя просто использовать во вспомогательных функциях, а не прямо внутри самого теста
  • немножечко опирается на порядок инициализации глобальных переменных. В целом, это исправить легко, но никак руки не доходят — а работать вроде и так работает -_-‘

Тесты интерфейсов связи

Как уже было сказано вначале, одним из самых удобных и полезных для юнит-тестирования вещей являются модули связи — всякие парсеры протоколов, модули общения с датчиками и тому подобное.

Как же их тестировать?

Возможны варианты. Для начала, что вообще нужно от интерфейса связи коду, который им пользуется? Например, от UART’a в самом минимуме нужно не так уж много:

Принятый байт можно «скармливать» пользовательскому коду как параметр у функции, а для отправки пользоваться одним коллбэком.
Но отправлять один байт не всегда удобно, хочется отправлять массив. Окей, не проблема, для этого тоже хватит одного коллбэка.
Потом оказывается, что иногда нужно можно отправлять массив блокирующе (т.е. колбэк может не возвращать управление, пока не отправит массив целиком), а иногда нельзя.
Иногда массив локальный — и тогда в неблокирующем варианте его приходится куда-то копировать — а иногда он слишком большой, чтобы его куда-то копировать, но зато статический.

Читайте также:  Windows 10 сборка для слабых ноутбуков 2021

Потом вдруг выясняется, что конкретный датчик может при первом включении работать на любом из 10 разных бодрейтов.

Короче, передавать 10 разных коллбэков по одному как-то глупо. Само собой напрашивается решение — сделать класс «обертка над UART’ом», а в нем методы. И тогда для каждого UART’a мы просто создаем экземпляр такой обертки.

Допустим. Но как же их тогда тестировать? Нам нужно, чтобы в тестовой сборке методы делали одно, а в релизной — другое.

По факту, нам нужен полиморфизм. И вот тут начинаются вопросы.

  • С одной стороны, для этого не нужен полиморфизм времени выполнения, т.е. можно обойтись ifdef’ами
  • С другой, это как-то не красиво; у класса фактически поменяется вся реализация от одного ifdef’a. Некрасиво.
  • С третьей, есть относительно идиоматичное решение — CRTP, которое дает полиморфизм на этапе компиляции с помощью шаблонов.
  • А с четвертой есть «обычный» полиморфизм из С++ — через виртуальное наследование.

Отметая вариант с ifdef’ами по эстетическим соображениям, давайте посмотрим на CRTP.
Для тех, кто не в курсе, выглядит это как-то так:

Соответственно, класс Amount тут является статическим интерфейсом, а класс Constant42 его реализует.

К сожалению, у такого подхода есть 2 большие проблемы:

  • Amount — это не класс, это шаблон класса. Соответственно, мы не можем передать указатель на него в клиентский код. Мы вынуждены делать клиентский код шаблонным! И вот это реально проблема, потому что Keil не очень хорошо позволяет отлаживать шаблоны и прочий код в заголовочных файлах — на него иногда невозможно поставить точку останова, невозможно прошагать в отладке. И насколько мне известно, у многих IDE такая проблема есть, в той или иной степени.
  • Нельзя забывать, что мои коллеги в тот момент, когда эти вопросы в первый раз поднимались, только-только начали переползать на С++. Обычные шаблоны выглядят страшно, а ЭТО вызывало легкую панику.

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

И да, действительно, это несколько дороже, чем хотелось бы. Тем не менее:

  • таблица виртуальных методов — если этих методов не слишком много — весит пару сотен байт.
  • виртуальный вызов дороже на несколько десятков команд. Поскольку у МК обычно нет кэш-памяти, виртуальный вызов не ведет к кэш-промаху, соответственно время вызова увеличивается незначительно.

Но самое главное, что код выглядит просто и понятно — он не требует выворачивать себе мозг каждый раз. И шаблоны не распространяются на всю кодовую базу, как чума.

У такого подхода, как ни странно, есть менее очевидный минус. В Keil’e есть простая оптимизация, которая очень существенно сокращает размер бинарника — выбрасывание неиспользуемых функций.

И вот она-то от наличия таблицы виртуальных методов ломается — ведь на метод есть указатель! Значит, он используется. С этим можно бороться, но как показала практика — особого смысла в этом нет.

Если бы мы пользовались слабыми МК с небольшим количеством памяти и низкой тактовой частотой, то разговор скорее всего был бы совсем иной. Но даже весьма бюджетные модели STM вполне справляются. Да, средний размер бинарника вырос — ну… и ладно? До тех пор, пока в условия технического задания мы вписываемся — кому какая разница?

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

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

В релизной прошивке в пользовательский код передаются объекты нормального класса, а в тестовом — моки (mock). Скажем, в мок для UART’a можно передать массив, который пользовательский класс потом «примет» через метод для чтения входящих байт.

Инициализация и деинициализация

Как известно, юнит-тесты не должны зависеть от порядка, в котором их запускают. Поэтому каждый тест должен работать с «чистым» состоянием тестируемого объекта.

Собственно, для этого почти во всех тестовых фреймворках используются функции setup и teardown ; которые инициализируют и деинициализируют объект соответственно.

Как можно инициализировать объект?

Казалось бы, предпочтительнее инициализация в конструкторе — ее невозможно забыть, т.е. невозможно создать объект в невалидном состоянии.

К сожалению, в embedded иногда приходится создавать глобальные объекты — в основном, чтобы взаимодействовать с прерываниями. А выполнять какой-то сложный код в конструкторе глобального объекта чревато static initialization order fiasco, т.е. можно нарваться на зависимость от какого-то другого глобального объекта из другой единицы трансляции.

Но на самом деле не так важно, как объект инициализировать; в функциии setup мы просто создадим объект (и может быть вызываем для него метод init). А вот как его деинициализировать?

И вот тут возникает неприятность. В embedded очень многие объекты имеют бесконечное время жизни — потому что main никогда не завершается. Прошивка должна работать, пока на устройстве есть питание; а когда питания нет — уже завершаться поздно.

Поэтому большинству объектов не нужны ни деструкторы, ни методы для деинициализации. А раз они не нужны — писать их специально для тестов как-то не очень хочется.
При этом вполне вероятно, что объект не владеет ресурсами, которые реально нужно деинициализировать, было бы вполне достаточно просто сконструировать объект заново.

Как сконструировать объект заново?

  • просто вызвать init еще раз
  • создать новый объект, а старый удалить

Подход с методом init обладает неприятным моментом — таким образом не получится заново заполнить константные или ссылочные поля. А чтобы создавать и удалять объекты, нужна динамическая память — традиции ембедеров не велят мне использовать ее… Впрочем, в тестовой сборке это вполне допустимо, а иногда даже обязательно — например, CppUTest без кучи работать не хочет.

Тем не менее, если вам тоже неприятно использовать динамическую память, есть другой способ переконструировать объект — placement new ! Это относительно малоизвестная фича С++, которая, по-факту, позволяет вызывать конструктор явным образом для заранее выделенного куска памяти.

У placement new есть свои подводные камни (скажем, не совсем понятно, как таким образом конструировать массивы), но для наших целей это не существенно — вполне достаточно создать статический объект файле с тестами, а перед каждым тестом (или после каждого теста) переконструировать его in place.

Это удобно делать с помощью небольшой вспомогательной функции (которую, кажется, невозможно в общем виде написать без С++11):

У такого варианта тоже есть один неприятный момент — компилятор armcc (он же Arm Compiler 5), который все еще является компилятором по-умолчанию при создании проекта в Keil’e, поддерживает С++11 очень странно.
Из языковых конструкций поддерживается почти все, но вот стандартная библиотека осталась от С++03. Поэтому std::forward в нем использовать не получится — только если свой писать.

Соответственно, в Keil’e придется или использовать placement new напрямую, или перелезать на «шестую версию компилятора», которая на самом деле clang, или обходиться без forward’a. Ну или написать свой forward.

Если у объекта все-таки есть необходимость в какой-то деинициализации, то при таком подходе нужно не забывать явно вызывать деструктор!

Тестирование assert’ов

На случай, если кто-то не в курсе: ассерты — это проверки условий, которые должны быть истинными; проваленный ассерт как правило означает ошибку в логике программы.
Как правило, ассерт программу экстренно завершает с помощью вызова std::terminate , exit(1) или чего-то вроде того.

Зачем нужны ассерты? В основном для проверок предусловий (т.е. корректности входных параметров у функций) — и постусловий (корректности результатов).

Для встраиваемых систем возникают обычные вопросы:

  • что делать, если ассерт провалился?
  • допустимо ли оставлять ассерты в «релизной» прошивке?
  • допустимо ли их вообще писать или все ошибки должны обрабатываться?
  • какой ассерт использовать?
  • нужно ли их тестировать и, если да, то как?

Что делать, если ассерт провалился

Самое простое, что можно сделать — это пустой бесконечный цикл:

Насколько я знаю, примерно так и поступают почти всегда, потому что сходу непонятно, что еще можно сделать — даже вызов std::terminate в конце концов упрется в пустой цикл, который сгенерировал компилятор.

Пустой цикл не очень хорош по двум причинам:

  • не сразу понятно, что ассерт сработал, даже под отладкой
  • прерывания могут прерывать этот пустой цикл и продолжать что-то делать

Обе эти проблемы решаются тривиально — прерывания в этом пустом цикле можно запретить, а отладку остановить с помощью уже известной нам инструкции BKPT .

Допустимо ли оставлять ассерты в «релизной» прошивке

Тут каждый решает сам, в зависимости от сроков и серьезности проекта. Я часто оставляю, потому что:

  • лучше пусть сработает ассерт, чем прошивка продолжи выполнение с бредовыми данными
  • от зависания намертво помогает watchdog
  • далеко не всегда есть способ просигнализировать об ошибке хоть как-то

Допустимо ли вообще писать ассерты или все ошибки должны обрабатываться

Ответ практически повторяет предыдущий пункт, но к этому можно добавить следующую мысль.

Если используются велосипедные библиотеки (или просто какой-то общий код), то в них ассерты становятся нужны для гарантии правильного использования.

Какой ассерт использовать

Ассерт времени выполнения:

Чисто теоретически, есть заголовочный файл assert.h , но как-то я особо не видел, чтобы им в embedded пользовались.
Вероятно, потому что реализация поведения при срабатывании ассерта будет зависимой от компилятора.

Поэтому — опять велосипеды. Но это ведь С++, тут все привыкли, что у каждой библиотеки свои тайпдефы над стандартными типами, свой ассерт и т.д 🙂

Ассерт времени компиляции:

Тут все просто. Если у вас есть возможность использовать С++11 и выше, то есть стандартный static_assert . Если нет, то используется конструкция аля:

Нужно ли тестировать ассерты (и как)

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

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

Соответственно, если мы пишем библиотеку, то желательно удостовериться в корректности проверок для аргументов.
Окей, решили что ассерты тестить нужно. Как?

Ассерт времени компиляции

Вроде бы такая классная штука, проверка на этапе компиляции! Но как его тестировать, если он компиляцию останавливает?

Медитации на stackoverflow (и вот это видео Roland Bock) показали мне три пути:

  • Тесты с помощью системы сборки, скажем, в CMake можно ожидать, что файл не должен компилироваться

Имхо способ не очень удобный. Каждый тест нужно будет пихать в отдельный файл или заворачивать в ifdef; ну и к системе сборки привязываться не очень хочется. Не говоря уже о том, что Кейл так не сумеет.

  • Вариант, который предлагает Roland Bock — с опорой на то, что decltype это unevaluated context.

Имхо, выглядит это не очень красиво, приходится делать вспомогательный тип на каждый ассерт, перемазываться макросами. А еще этот способ не очень работает на clang’e (по крайней мере, на момент видеозаписи).

К тому же, у Кейла (точнее, у компилятора armcc) не очень все хорошо с decltype .

  • Заменять static_assert на обычный.

Roland этот способ отбросил сходу без особых пояснений, но я лично не вижу в нем ничего плохого (если вы видите — то расскажите, пожалуйста). Просто не используем static_assert напрямую, а прячем его за макросом. И в тестовой сборке этот макрос может делать обычный, рантаймовый assert, таким образом сводя этот вопрос к следующему.

Не нужно никаких особых ухищрений или поддержки со стороны системы сборки, не нужно создавать кучу вспомогательных типов. Разве что можно случайно не constant expression в ассерте написать, но это быстро вскроется в релизной сборке.

Читайте также:  Linux send to com port

Если вы знаете какой-то еще способ, то, пожалуйста, расскажите.

Ассерт времени выполнения

Если оставить реализацию с бесконечным циклом, то тоже никак! Выполнение просто уйдет в этот бесконечный цикл и все.

К счастью, исправить это достаточно легко — для тестовой конфигурации изменить макрос ассерта так, чтобы он не уводил выполнение в бесконечный цикл, а, например, бросал исключение.

Это ведь тестовая сборка, почему бы и нет? Весь окружающий код при этом не меняется.

И с помощью небольшого дополнительного макроса в нашем тестовом фреймворке, это исключение можно поймать и проверить:

Для теста нам потребуется еще один макрос, доводя таким образом суммарное количество проверяющих макросов до 4, но это все еще меньше, чем в CppUTest 🙂

Ремарка про стек и кучу

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

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

В Кейле размеры ОЗУ и ПЗУ задаются в настройках конфигурации проекта ( Project->Options->Target ), поэтому их легко сделать разными для разных конфигураций. А вот размеры стека и кучи как правило задаются в ассемблерном стартап-файле, как-то так:

Можно, конечно, держать по два стартапа — один для релиза, а другой для тестов, но это как-то глупо. Удобнее эти строки в стартапе закомментировать, а константы передавать через опцию к ассемблеру (аналогично опции -D для компилятора) — на вкладке Project->Options->Assembler в поле Define.

Тесты и ОСРВ

Еще один интересный вопрос — как тестировать код, написанный под ОСРВ?

Краткий ликбез: ОСРВ — операционные системы реального времени. В случае микроконтроллеров они как правило поставляются в исходных кодах и дают вам возможность создавать потоки выполнения на основе функций. В результате прошивка начинает выглядеть примерно как многопоточное приложение под десктоп.

Основной затык тут следующий — в embedded потоки, как правило, никогда не завершаются. Они создаются на основе функций вида:

Опять бесконечный цикл мешает тестированию!

Обойти это препятствие можно очевидным образом — сделать цикл небесконечным в тестовой конфигурации:

А во фреймворке заведем функции:

Теперь мы сможем выставить количество оборотов «бесконечного» цикла в тесте — и при желании, назначить коллбек, который будет дергаться при каждом обороте.

Соответственно, в тесте поток можно просто вызывать, как обычную функцию.

Ну, а если функция-поток делает какие-то блокирующие системные вызовы (например, повисает на семафоре), то этот семафор нужно освободить заранее.

Еще пара моментов:

  • перед входом в цикл потока может быть этап инициализации; в тестах нужно каждый раз делать де-инициализацию
  • запуск тестов приходится тоже выносить в отдельный поток и запускать весь этот ужас под ОСРВ, иначе примитивы синхронизации работать не будут

У меня не очень большой опыт использования ОСРВ, поэтому что еще тут сказать, я не знаю. Спрашивайте 🙂

Тесты на ПК

Время шло, вселенные вставали и рушились, разработчики вылуплялись из куколок, и Кейл как IDE перестал нас удовлетворять.

Начались поиски другого решения, которые пока что привели к гибриду: код пишется в Eclipse-CDT с привязанным arm-none-eabi-gcc, но отлаживается и прошивается в Кейле. Тесты по-прежнему можно выполнять в симуляторе Кейла.

Такой подход обладает своими плюсами и минусами, расписывание которых еще больше раздует и так неприлично большой пространный пост.

Остановимся на главном — Eclipse, в отличие от Кейла, умеет не только кросс-компилятор вызывать.

Это дает возможность скомпилировать тестовую сборку не только под симулятор, но и под десктоп. Зачем, спросите вы?

Ну, тесты под десктопом будут выполняться существенно быстрее. А еще в Линуксе можно использовать Google Sanitizer’ы! Это замечательный набор инструментов, который позволяет отлавливать типичные ошибки в плюсовом коде:

  • выходы за границы массивов
  • переполнения знаковых целых
  • и прочие виды неопределенного поведения разных сортов

К тому же, тесты не в симуляторе должны выполняться гораздо быстрее. Теоретически.

Чтобы собрать тесты под Линукс, нужно создать тестовую конфигурацию (на этот раз в Eclipse), которая будет отличаться от обычной только компилятором и каким-нибудь специальным дефайном.

  • эта конфигурация должна быть для обычного gcc (не cross)
  • из нее нужно убрать все файлы, которые приколочены к архитектуре — например, ассемблерные стартапы
  • весь платформозависимый код, разумеется, нужно будет поифдефать
  • если мы таки хотим санитайзеры, то нужно прописать соответствующие ключи к компилятору

По желанию, можно добавить и других ключей — скажем, -fdiagnostics-color=always для цветного вывода и -ftrapv -fstack-protector-all -fstack-check для дополнительных проверок переполнения стека.

Запуск тестов тоже придется немножко доработать — по факту, просто разрешить main’у завершаться с кодом возврата

Велика так же вероятность, что обычный gcc будет выдавать много предупреждений на микроконтроллерные библиотеки — например, на заголовочные файлы CMSIS’a, которые любят кастовать адреса к uint32_t , а не к uintptr_t — даже если вы в тестируемом коде регистры не трогаете.

Это можно исправить, например, если подключать эти библиотеки не через -I (как это обычно делает эклипс), а через -isystem . К сожалению, для этого придется прописывать пути уже как опции компилятора, а не в диалоге для Include Paths.

Вообще же, в репозитории есть пример, который собирается в Keil’e и в Eclipse, так что за совсем подробными подробностями — прошу туда.

Далее, нам потребуется ПК с Линуксом. Можно, конечно, всем разработчикам поставить по виртуалке. Но если мы хотим делать красиво, то нужен отдельный сервер для автоматического запуска тестов! Ну, как у больших мальчиков, после каждого коммита.

У нас сервер был, но — опять же, по историческим причинам — на нем стоит Windows Server 2012; со связкой Jenkins-Redmine-Gitblit.

Но тем не менее. Что нам понадобится, чтобы запускать тесты таким образом?

  • Docker
  • Сборка Eclipce CDT, которая согласится в нем заработать
  • VirtualBox
  • Немножко воображения и костылей, чтобы все это запускалось из-под Windows
  • Еще подкрутить наш фреймворк (и код), чтобы докер не подавился

Начнем с последнего. Из всех тестов и тестового фреймворка придется убрать явные обращения к регистрам и ассемблерные вставки. Какое счастье, что как раз обращения к регистрам-то мы и не тестировали 🙂

К сожалению, в коде также нельзя будет использовать прерывания (не очень-то и хотелось) и ОСРВ. Точнее, можно, но надо изобретать еще какой-то дополнительный уровень абстракции, до которого пока что руки не дошли.

Если писать тесты «правильно», передавать платформозависимые вещи снаружи и оборачивать их в обертки, а не хардкодить, то особых проблем переезд на другую платформу вызывать не должен.

Фреймворк придется немного подправить, чтобы убрать явные зависимости от инструкции BKPT и запрета прерываний, вынесем это все в конфигурационный файл:

Отмечу, что далее будет просто приблизительное описание того, как оно у нас сделано. Скорее всего, оно сделано неправильно, но, к сожалению, devops’ов у нас нет.
Поэтому тут я лучше все под спойлеры спрячу 🙂

К сожалению, кэш докера по невыясненным причинам приходится каждый раз очищать. Поэтому быстрее чем в симуляторе не получилось. Ну, как сказать? Сами тесты выполняются моментально 🙂 А вот разворачивается вся эта штука минут 15. Да.

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

Ну или не очень хорошо, если все же санитайзер что-то обнаружил:

А если он ничего не нашел, то еще и глубокое спокойствие — ведь теперь мы знаем, что в нашей прошивке нет неопределенного поведения!
Ну, наверное, нет. Скорее всего ._.

Разумеется, к этому можно далее прикручивать любые вещи — автоматическую сборку hex-файла прошивки с добавлением в архив (чтобы для прошивания платы программиста не дергали), дополнительную проверку с помощью PVS-Studio, что угодно, бесконечность не предел.

(ремарка: я помню, что представители PVS-Studio рекомендуют встраивать проверку в обычный процесс сборки, а не выполнять ее только по праздникам; над этим мы тоже работаем 🙂

Известные проблемы

Основная проблема использования юнит-тестов вообще, с которой лично я столкнулся, это когда где-то надо немножко сменить API (потому что оно было придумано N лет назад, и накопились проблемы) — но все покрыто тестами… И прям руки опускаются при мысли, что надо 100+ тестов тоже править.

Но в какой-то момент все же пересиливаешь себя, меняешь API и видишь, что упало-то всего тестов 10.

Остальное — скорее проблемы «фреймворка», поэтому я их лучше под спойлер спрячу.

BKPT не всегда останавливает выполнение «красиво»; зачастую оно останавливается где-то в дизассемблере, а не на строчке в срр-файле. Это сильно зависит от настроек компилятора, поэтому как-то в общем виде это решить не получается. К счастью, обычно достаточно немножко поскроллить вверх-вниз дизасемблер, чтобы Keil смог показать нужный срр-файл.

Т.к. названия функций-тестов генерируются с помощью директивы __LINE__ , это название не очень-то человекочитаемое. Поэтому по call-stack’у не очень понятно, что за тест такой — doTest56 .

И очень, на мой взгляд, удобное окно в Eclipse — outline — которое показывает список всех функций в файле, становится менее удобным.

Отчасти, эти проблемы друг друга дополняют 🙂 Не знаешь, что такое doTest56 — находишь его в outline и переходишь к нему.

Ну и Collapse All (по хоткею Ctrl+Shift+/ ) тоже сильно облегчает жизнь, хотя после него придется пару раз кликнуть на плюсик. Судя по всему, использовать строковые литералы в качестве названий тестов — не всегда хорошо.

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

А если вспомогательная функция — лямбда, то из нее нельзя возвращать 0 или nullptr, иначе слишком суровый вывод типов вынудит вас указывать тип возвращаемого значения явно как const char * . Для небольшого облегчения заведен макрос UMBA_TEST_OK .

При ловле ассертов ломается красивый вертикальный столбик из ОК’ов 🙁

Личный опыт

Один из «бета-чтецов» (которым отдельное большое спасибо) порекомендовал мне добавить акцента на полезности тестов, потому что после столь продолжительного описания всяческих проблем у читающих может возникнуть мысль — а может ну его?

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

Большой проект для военных, только отечественная элементная база. У большинства приборов срок поставки полгода, у некоторых — год. А код надо писать уже сейчас, потом время будет только на отладку. Как это делать без юнит-тестов — я даже не знаю. Просто вслепую писать и надеятся, что сходу заработает?

Загрузчик. Да, каждый embedded-разработчик должен написать свой загрузчик, это как обряд взросления 🙂 В моем случае загрузчик должен поддерживать несколько разных микроконтроллеров и интерфейсов связи; но при этом основная логика прошивания остается неизменной. Поскольку эта логика покрыта тестами, багов в ней не находилось уже очень-очень давно, проблемы появляются в основном при необходимости поддержки нового микроконтроллера или когда пользователь неправильно конфигурацию выполняет.

Еще один проект для военных, опять «отечка», необходимость общения по протоколу заказчика. Вся «высокоуровневая» логика протокола протестирована, поэтому проблемы с коммуникацией были только из-за Ethernet на Миландре -_-‘

Подведем итоги

Внезапно даже в embedded можно использовать юнит-тесты и для этого совершенно необязательно наличие дорогущего оборудования, ложа с гвоздями или чего-то подобного.

При этом, разные стратегии тестирования не обязательно противоречат, а вполне могут дополнять друг друга. Так, тестирование в симуляторе, встроенному в IDE, гораздо лучше подходит для TDD, а тестирование на билд-сервере позволяет проводить более полные и/или сложные проверки.

Разумеется, такое тестирование ни в коем случае не является всеобъемлющим! Это подход именно к юнит-тестированию отдельным программных модулей, а не всего устройства в целом — они не замена дорогущему оборудованию для изделия в целом, ложу с гвоздями для плат или приемочным испытаниям у заказчика.

Тем не менее, по своему опыту скажу, что юнит-тесты вынуждают вас писать более модульный код и действительно вселяют уверенность в том, что код работает. Баги в ужасе мигрируют из покрытого тестами кода в отдаленные закоулки 🙂

Источник

Оцените статью