Gieneq Napisano Marzec 6, 2023 Udostępnij Napisano Marzec 6, 2023 Szukałem lepszego środowiska od Eclipsowego Espressif IDE - nie ma w nim nic złego, ale nie ma tego czego potrzebuję. Pomyślałem że dam szansę Platformio - makerska asocjacja trochę mnie odstraszała, ale wygląda że jest to naprawdę udane środowisko, a na pewno dynamiczniej rozwijane od Eclipsa. Ciekawe że Google nie podpowiedział mi tego tematu, może Platformio nie jest zbyt dobrze zaindeksowane. Potrzebowałem: wybrać pliki niezwiązane ze sprzętem i skompilować na maszynie hosta (windowsie) np. z G++, wykonać testy jednostkowe z GoogleTest wybranych plików, skompilować cały projekt dla targetu i wgrać na platformę docelową. Można to zrobić ręcznie: dodając osobny plik cmake wybierający pliki źródłowe do kompilacji i linkujący google test, podlinkować nagłówki w wybranym IDE np VSCode, odpalić testy poleceniem ctest. W platformio da się to jednak połączyć i najwyraźniej uprościć. Na pewno da się też więcej, ale póki co więcej nie potrzebuję. Przeglądamy 2 linki: GoogleTest w Platformio, podstawy gMock - nie dla hardwaru, dla interfejsow tj. wirtualnych klas i funkcji członkowskich. Po założeniu projektu z frameworkiem ESP-IDF edytuję plik .ini dla 2 wariantów: esp32-xx - kod dla platformy docelowej, native - kod kompilowany z G++ dla windowsa, np z MinGW, Oba warianty da siętestować, ale wariant dla ESP wymaga wgrania testów na płytkę i to trochę mija się z celem przyspieszenia testowania bez powolnej pętli: kompiluj, wgraj, sprawdź wyniki. [env:esp32-c3-devkitm-1] platform = espressif32 board = esp32-c3-devkitm-1 framework = espidf monitor_speed = 115200 test_framework = googletest test_ignore = test_desktop test_animations build_flags = -D USING_ESPIDF=1 [env:native] platform = native test_framework = googletest build_flags = -D USING_NATIVE=1 W katalogu test zakładam podkatalogi test_desktop test_animations z testami. Kod który testuję to kontroler animacji diody RGB. Składa się z kontrolera sterowanego czasem, kilku parametrów, callbacka do obiektu implementującego funkcję sterującą. Wydzielam z kontrolera bazę AnimationController którego zadaniem jest podstawowa funkcjonalność i jego klasa potomna z konkretnym przypadkiem. Animation.h #pragma once #include "algorithm" class AnimationController { public: virtual ~AnimationController() = default; void start(); void stop(); void tick(float dt); bool looping{false}; void set_interval(float interval); bool is_running(); protected: virtual void on_animation_frame(float progress_norm); bool running{false}; private: float interval{1000}; float accumulator{0}; }; Animation.cpp #include "Animation.h" void AnimationController::start() { running = true; accumulator = 0; } void AnimationController::stop() { running = false; } void AnimationController::tick(float dt) { if(running) { accumulator += dt; if(accumulator > interval) { accumulator -= interval; if(!looping){ accumulator = 0; stop(); } } float progress_norm = accumulator / interval; progress_norm = std::max(0.0F, progress_norm); progress_norm = std::min(1.0F, progress_norm); on_animation_frame(progress_norm); } } bool AnimationController::is_running() { return running; } void AnimationController::set_interval(float interval) { this->interval = interval; accumulator = std::min(accumulator, interval); } void AnimationController::on_animation_frame(float progress_norm) { // override } AnimationRGB.h #pragma once #include "Animation.h" #include "AnimationListener.h" enum class RGBAnimationType:int { NONE = -1, CYCLE = 0, }; class AnimationRGBController : public AnimationController { public: inline void set_animation_listener(AnimationRGBListener* listener) { this->listener = listener; } RGBAnimationType type{RGBAnimationType::NONE}; protected: virtual void on_animation_frame(float progress_norm) override; private: AnimationRGBListener* listener; }; AnimationRGB.cpp #include "AnimationRGB.h" void AnimationRGBController::on_animation_frame(float progress_norm) { if (listener) { if(type == RGBAnimationType::NONE) listener->on_animation(progress_norm, 0, 0, 0); else { float r{255}; float g{255}; float b{255}; //todo do it better listener->on_animation(progress_norm, r, g, b); } } } AnimationListener.h #pragma once #include "stdint.h" class AnimationRGBListener { public: virtual ~AnimationRGBListener() = default; virtual void on_animation(float progrs_norm, uint8_t r, uint8_t g, uint8_t b) = 0; }; Funkcja main może wyglądać tak - dla wariantu native musi być funkcja main bo się nie skompiluje: #ifdef USING_ESPIDF #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "Device.h" Device device; extern "C" { void app_main() { device.init(); while (1) { device.tick(); vTaskDelay(1); } } } #endif #ifdef USING_NATIVE int main(int argc, char **argv) { return 0; } #endif Testy Kod testujący w pliku test/test_animations/test_main.cpp #include <gtest/gtest.h> #include <gmock/gmock.h> #include "AnimationRGB.h" #include "AnimationListener.h" using ::testing::AtLeast; using ::testing::_; TEST(AnimationTest, ShouldCreateController) { AnimationController ctrl; EXPECT_FALSE(ctrl.looping); EXPECT_FALSE(ctrl.is_running()); } class MockAnimationRGBListener : public AnimationRGBListener { public: MOCK_METHOD( void, on_animation, (float progrs_norm, uint8_t r, uint8_t g, uint8_t b), (override) ); }; TEST(RGBAnimationTest, ShouldTickAnimationOnce) { AnimationRGBController rgb_ctrl; MockAnimationRGBListener device; EXPECT_CALL(device, on_animation(_,_,_,_)).Times(AtLeast(1)); rgb_ctrl.set_animation_listener(&device); rgb_ctrl.start(); rgb_ctrl.tick(10.0F); } TEST(RGBAnimationTest, UseDefaultAnimation) { constexpr float animation_interval{2000.0F}; AnimationRGBController rgb_ctrl; MockAnimationRGBListener device; EXPECT_EQ(rgb_ctrl.type, RGBAnimationType::NONE); EXPECT_CALL(device, on_animation(0.5F,0,0,0)).Times(AtLeast(1)); rgb_ctrl.set_interval(animation_interval); rgb_ctrl.set_animation_listener(&device); rgb_ctrl.start(); rgb_ctrl.tick(1000.0F); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); if (RUN_ALL_TESTS()){} return 0; } Odświeżam widok pod ikoną testów, daję filtr dla native i przechodzą: Test zajmuje 16 sekund - dużo mniej niż wgranie czegoś na płytkę i obejrzenie rezultatów czy odpalenie debugera. W kodzie widać użycie mockapu - myślę że to ciekawa koncepcja, bo w przypadku interface jak tu można sprawdzić ile razy callback został wykonany i jakie miał wartości: EXPECT_CALL(device, on_animation(0.5F,0,0,0)).Times(AtLeast(1)); Jak nie potrzeba jakiejś wartości, to można ją dopasować do jakiejkolwiek używając _. W tym cyklu rozwijania oprogramowania, gdy testy przejdą i mam skończoną funkcjonalność to wgrywam na platformę docelową: Mam jedynie wątpliwość jak działa dodawanie plików źródłowych. Używając samego cmake kompilacja i uruchomienie zdecydowanie większego kodu zajmuje z 4 sekundy. Tu mam za to bardzo ładne informacje zwrotne przypadku niepowodzenia: Jak ktoś ma swoje doświadczenia i lepszy pomysł na użycie Platformio to chętnie się zapoznam 🙂 2 Cytuj Link do komentarza Share on other sites More sharing options...
Gieneq Marzec 22, 2023 Autor tematu Udostępnij Marzec 22, 2023 Drobna aktualizacja - temat nieco komplikuje się gdy testowany kod jest komponentami ESP-IDF. Przygotowałem szablon który może być pomocny w postawieniu projektu. W repo jest opis jak krok po kroku przygotować build system: https://github.com/Gieneq/TemplateTargetNativeGTest 1 Cytuj Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Dołącz do dyskusji, napisz odpowiedź!
Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!