[GO] Lassen Sie uns mit TDD ~ Intermediate Review ~ etwas entwickeln, das dem Integrierten nahe kommt

Einführung

Mit googletest / googlemock entwickle ich Software, die unter Embedded Linux mit TDD läuft. Da die mit TDD tatsächlich entwickelte Prozedur so geschrieben ist, wie sie in Echtzeit ist, gibt es einen zufälligen Teil. Ich hoffe, Sie genießen den Prozess auch. Wenn Sie Fragen oder Fehler haben, kommentieren Sie bitte. Es wird ermutigend sein.

Wenn Sie mehr über die bisherige Geschichte erfahren möchten, lesen Sie bitte die vorherigen Artikel. Versuchen Sie, mit TDD ~ Preparation ~ etwas zu entwickeln, das nahezu eingebettet ist Versuchen Sie, etwas zu entwickeln, das mit TDD nahezu eingebettet ist ~ Problemauslösung ~ Versuchen Sie, mit TDD ~ file open ~ etwas zu entwickeln, das dem Integrierten nahe kommt Versuchen Sie, mit TDD ~ libevdev Initialisierungs- / Beendigungsverarbeitung ~ etwas zu entwickeln, das nahezu eingebettet ist Entwickeln Sie mit TDD-Key Input Detection- etwas in der Nähe des Integrierten

Dieser Artikel ist möglicherweise nicht hilfreich. Der nächste Artikel wird interessant sein.

LED-Steuerung

Wie ich letztes Mal angekündigt habe, habe ich die LED-Steuerung hinter den Kulissen vorgenommen. Da es nichts Besonderes zu erwähnen gab, werden nur die Ergebnisse veröffentlicht. Natürlich habe ich es mit TDD implementiert. Ich habe ein wenig übersprungen und den Fehler beim Schreiben der LED nicht behandelt. Imitiere keinen guten Jungen! Auch hier ist die Implementierung so, dass die interne Struktur für den Benutzer dieses LED-Steuermoduls nicht sichtbar ist. Es werden auch mehrere Instanzen unterstützt. Ich denke, dass es viele Fälle gibt, in denen mehrere LEDs gesteuert werden. Wenn Sie es also so machen, wird es später skaliert.

Testcode

Produktcode


typedef struct LedDriverStruct {
  int fd;
  LedStatus status;
} LedDriverStruct;

LedDriver CreateLedDriver() {
  LedDriver led = calloc(1, sizeof(LedDriverStruct));
  led->fd = -1;
  led->status = LED_UNKNOWN;

  return led;
}

int InitLedDriver(LedDriver self, const char* device_file) {
  self->fd = IO_OPEN(device_file, O_WRONLY|O_NONBLOCK);
  self->status = LED_TURN_OFF;
  if (self->fd < 0) {
    return LED_DRIVER_INIT_ERROR;
  }

  return LED_DRIVER_SUCCESS;
}

void TurnOnLed(LedDriver self) {
  if (self == NULL) return;
  self->status = LED_TURN_ON;
  IO_WRITE(self->fd, "1\n", 2);
}

void TurnOffLed(LedDriver self) {
  if (self == NULL) return;
  self->status = LED_TURN_OFF;
  IO_WRITE(self->fd, "0\n", 2);
}

void ToggleLed(LedDriver self) {
  if (self == NULL || self->status == LED_UNKNOWN) return;

  if (self->status == LED_TURN_OFF) {
    TurnOnLed(self);
  } else {
    TurnOffLed(self);
  }
}

int CleanupLedDriver(LedDriver self) {
  if (self == NULL) return LED_DRIVER_CLEANUP_ERROR;

  int rc = IO_CLOSE(self->fd);
  if (rc < 0) {
    return LED_DRIVER_CLEANUP_ERROR;
  }
  return LED_DRIVER_SUCCESS;
}

void DestroyLedDriver(LedDriver self) {
  if (self == NULL) return;

  free(self);
  self = NULL;
}

Code erstellt in Problem Raising

Was halten Sie nach langer Zeit von diesem Code?

main.c


#define KEYBOARD_DEVICE "/dev/input/event2"
#define LED_DEVICE      "/sys/class/leds/input2::capslock/brightness"

#define KEY_RELEASED 0
#define KEY_PRESSED 1

static void mainloop() {
  struct libevdev *dev = NULL;
  int key_fd = open(KEYBOARD_DEVICE, O_RDONLY|O_NONBLOCK);
  int rc = libevdev_new_from_fd(key_fd, &dev);

  if (rc < 0) {
    fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc));
    exit(1);
  }

  int led_fd = open(LED_DEVICE, O_WRONLY|O_NONBLOCK);
  if (led_fd < 0) {
    fprintf(stderr, "Failed to init LED device.\n");
    exit(1);
  }

  bool led_on = false;
  do {
    struct input_event ev;
    rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
    if (rc == 0) {
      if (ev.type == EV_KEY && ev.code == KEY_A && ev.value == KEY_PRESSED) {
        led_on = !led_on;
        char buf[2];
        snprintf(buf, 2, "%d", led_on ? 1 : 0);
        write(led_fd, buf, 2);
      }
    }
  } while (rc == 1 || rc == 0 || rc == -EAGAIN);

  libevdev_free(dev);
  close(key_fd);
  close(led_fd);
}

int main() {
  mainloop();
  return 0;
}

Dies ist ein Code dieser Größe, aber wie testen Sie diesen Code? Es scheint, dass Sie das gesamte Programm manuell testen müssen. (Wenn Sie Ihr Bestes geben, können Sie einen Komponententest schreiben ...)

Wie können wir sicherstellen, dass beim Hinzufügen neuer Funktionen keine Entfettung stattfindet? Ohne Tests wissen Sie nicht einmal, ob Sie sich vorwärts oder rückwärts bewegen.

Können Sie den Sprung wagen, wenn Sie umgestalten möchten? Der Mut zum Refactor wird durch Tests geschaffen. Code, der nicht getestet wurde, kann nicht überarbeitet werden und verrottet allmählich.

Was ist, wenn die von Ihnen verwendete Bibliothek instabil ist? Dieses Mal verwende ich eine stabile Bibliothek namens libevdev. Wenn die verwendete Bibliothek instabil ist, kann gesagt werden, dass der Einflussbereich der Bibliotheksänderung das gesamte Programm ist (um genau zu sein, innerhalb der Mainloop-Funktion).

Was ist zum Beispiel, wenn Sie eine andere Tastatur anschließen und jetzt die NumLock-LED mit der Taste "B" steuern möchten? Wenn es so aussieht wie unten, gibt es bereits viel faulen Geruch.

static void mainloop() {
  struct libevdev *dev1 = NULL;
  struct libevdev *dev2 = NULL;
  int key_fd1 = open(KEYBOARD_DEVICE1, O_RDONLY|O_NONBLOCK);
  int key_fd2 = open(KEYBOARD_DEVICE2, O_RDONLY|O_NONBLOCK);
  int rc = libevdev_new_from_fd(key_fd1, &dev1);
  int rc2 = libevdev_new_from_fd(key_fd2, &dev2);
//Ich kann es nicht mehr ertragen zu schreiben
}

Wenn Sie etwas rationaler sind, können Sie Arrays verwenden. (Der Code ist angemessen. Ich habe nicht versucht zu kompilieren)

typedef struct LedStruct {
  struct libevdev *dev;
  int fd;
  bool led_on;
}

static void mainloop() {
  LedStruct leds[2];
  leds[0].fd = open(KEYBOARD_DEVICE1, O_RDONLY|O_NONBLOCK);
  leds[1].fd = open(KEYBOARD_DEVICE2, O_RDONLY|O_NONBLOCK);
...

  do {
    for (int i = 0; i < 2; i++) {
      struct input_event ev;
      rc = libevdev_next_event(leds[i].dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
      if (rc == 0) {
        if (ev.type == target_keys[i] && ev.code == target_codes[i] && ev.value == target_values[i]) {
          leds[i].led_on = !leds[i].led_on;
          char buf[2];
          snprintf(buf, 2, "%d", leds[i].led_on ? 1 : 0);
          write(leds[i].fd, buf, 2);
        }
      }
    }
  } while (rc == 1 || rc == 0 || rc == -EAGAIN);
}

Es wird viel einfacher. Im bedingten Ausdruck while überprüfen Sie den Rückkehrcode nur, wenn i 1 ist (haben Sie es bemerkt?).

Die Tatsache, dass der Beton (Gerätebetrieb) solide geschrieben ist und die Abstraktion behindert, ist auch ein großes Hindernis für die Erweiterung der Software.

Code mit TDD erstellt

Wir haben Unit-Tests mit TDD gut genutzt, um gute Designs zu leiten. Werfen wir einen Blick auf den Code, den wir bisher erstellt haben.

key_input_event.c


typedef struct KeyInputDeviceStruct {
  int fd;
  struct libevdev *evdev;
  struct input_event target_event;
} KeyInputDeviceStruct;

KeyInputDevice CreateKeyInputDevice() {
  KeyInputDevice dev = calloc(1, sizeof(KeyInputDeviceStruct));
  dev->fd = -1;
  dev->evdev = NULL;

  return dev;
}

int InitKeyInputDevice(KeyInputDevice dev, const char *device_file) {
  if(dev == NULL) return INPUT_DEV_INVALID_DEV;

  dev->fd = IO_OPEN(device_file, O_RDONLY|O_NONBLOCK);
  if (dev->fd < 0) {
    if (errno == EACCES)
      DEBUG_LOG("Fail to open file. You may need root permission.");
    return INPUT_DEV_INIT_ERROR;
  }

  int rc = libevdev_new_from_fd(dev->fd, &dev->evdev);
  if (rc < 0) return INPUT_DEV_INIT_ERROR;

  return INPUT_DEV_SUCCESS;
}

int SetKeyInputDetectCondition(KeyInputDevice dev, const struct input_event *ev) {
  if (dev == NULL) return INPUT_DEV_INVALID_DEV;
  // Should I validate ev, here?
  memcpy(&dev->target_event, ev, sizeof(struct input_event));
  return INPUT_DEV_SUCCESS;
}

static bool HasPendingEvent(struct libevdev *evdev, struct input_event *ev) {
  return libevdev_next_event(evdev, LIBEVDEV_READ_FLAG_NORMAL, ev)
          == LIBEVDEV_READ_STATUS_SUCCESS;
}

static bool IsTargetEvent(const struct input_event *target,
                          const struct input_event *ev) {
  return (target->type == ev->type
       && target->code == ev->code
       && target->value == ev->value);
}

int CheckKeyInput(KeyInputDevice dev) {
  if (dev == NULL || dev->evdev == NULL) return INPUT_DEV_INVALID_DEV;
  struct input_event ev = {};
  if (HasPendingEvent(dev->evdev, &ev) && IsTargetEvent(&dev->target_event, &ev)) {
    return INPUT_DEV_EVENT_DETECTED;
  }
  return INPUT_DEV_NO_EVENT;
}

int CleanupKeyInputDevice(KeyInputDevice dev) {
  if(dev == NULL) return INPUT_DEV_INVALID_DEV;

  libevdev_free(dev->evdev);
  dev->evdev = NULL;
  int rc = IO_CLOSE(dev->fd);
  if (rc < 0) return INPUT_DEV_CLEANUP_ERROR;

  return INPUT_DEV_SUCCESS;
}

void DestroyKeyInputDevice(KeyInputDevice dev) {
  if(dev == NULL) return;

  free(dev);
  dev = NULL;
}

led_driver.c


typedef struct LedDriverStruct {
  int fd;
  LedStatus status;
} LedDriverStruct;

LedDriver CreateLedDriver() {
  LedDriver led = calloc(1, sizeof(LedDriverStruct));
  led->fd = -1;
  led->status = LED_UNKNOWN;

  return led;
}

int InitLedDriver(LedDriver self, const char* device_file) {
  self->fd = IO_OPEN(device_file, O_WRONLY|O_NONBLOCK);
  if (self->fd < 0) {
    // TODO: Look into possible errors.
    return LED_DRIVER_INIT_ERROR;
  }

  return LED_DRIVER_SUCCESS;
}

void TurnOnLed(LedDriver self) {
  if (self == NULL) return;
  self->status = LED_TURN_ON;
  IO_WRITE(self->fd, "1\n", 2);
}

void TurnOffLed(LedDriver self) {
  if (self == NULL) return;
  self->status = LED_TURN_OFF;
  IO_WRITE(self->fd, "0\n", 2);
}

void ToggleLed(LedDriver self) {
  if (self == NULL || self->status == LED_UNKNOWN) return;

  if (self->status == LED_TURN_OFF) {
    TurnOnLed(self);
  } else {
    TurnOffLed(self);
  }
}

int CleanupLedDriver(LedDriver self) {
  if (self == NULL) return LED_DRIVER_CLEANUP_ERROR;

  int rc = IO_CLOSE(self->fd);
  if (rc < 0) {
    return LED_DRIVER_CLEANUP_ERROR;
  }
  return LED_DRIVER_SUCCESS;
}

void DestroyLedDriver(LedDriver self) {
  if (self == NULL) return;

  free(self);
  self = NULL;
}

main.c


#define KEYBOARD_DEVICE "/dev/input/event2"
#define LED_DEVICE      "/sys/class/leds/input2::capslock/brightness"

int main(void) {
  KeyInputDevice press_a = CreateKeyInputDevice();
  InitKeyInputDevice(press_a, KEYBOARD_DEVICE);
  struct timeval time = {};
  const struct input_event kPressA = {time, EV_KEY, KEY_A, INPUT_KEY_PRESSED};
  SetKeyInputDetectCondition(press_a, &kPressA);

  LedDriver caps_led = CreateLedDriver();
  InitLedDriver(caps_led, LED_DEVICE);

  while(1) {
    if(CheckKeyInput(press_a) == INPUT_DEV_EVENT_DETECTED)
      ToggleLed(caps_led);
  }

  CleanupKeyInputDevice(press_a);
  DestroyKeyInputDevice(press_a);

  CleanupLedDriver(caps_led);
  DestroyLedDriver(caps_led);

  return 0;
}

Da es Fehlerbehandlung, Unterstützung für mehrere Instanzen usw. umfasst, sind die Funktionen nicht gleichwertig, aber die Codemenge ist mehr als verdreifacht. Darüber hinaus enthält der Testcode mehr Zeilen als der Produktcode. Dies liegt daran, dass wir in kleinen Einheiten an dem Problem arbeiten, sodass leicht zu erkennen ist, was mit diesem Fall passieren wird. Diesmal denke ich, dass dies das Ergebnis einer ernsthaften Implementierung eines solchen Teils ist (bis zu einem gewissen Grad).

Die Gesamtmenge an Code ist groß, aber jede Funktion ist kurz und zusammenhängend und macht nur eines (Prinzip der Einzelverantwortung).

Wie können wir sicherstellen, dass beim Hinzufügen neuer Funktionen keine Entfettung stattgefunden hat?

Ich habe einen bestehenden Test. Wenn der vorhandene Test erfolgreich ist, bewegen wir uns vorwärts.

Wenn Sie umgestalten möchten, können Sie den Sprung wagen?

Verlassen Sie sich auf Tests für unerbittliches Refactoring. (Ich habe es in der nächsten Minute viel überarbeitet. Dies ist auch dem Test zu verdanken.)

Was ist, wenn die verwendete Bibliothek instabil ist?

Die Schlüsseleingabelogik ist in * key_input_event.c * geschlossen. Außerdem kennt main.c die libevdev-Datenstruktur nicht. Selbst wenn sich die Verwendung der Bibliothek in key_input_event.c geringfügig ändert, wird die Änderung im Modul geschlossen und nicht an das obere Modul weitergegeben (geschlossen nach dem Prinzip von offen / geschlossen).

Was ist, wenn Sie eine andere Tastatur anschließen und jetzt die NumLock-LED mit der Taste "B" steuern möchten?

Die Initialisierung muss individuell erfolgen. Der Detektionsteil ist jedoch ungefähr wie folgt. (Es ist etwas schlauer, wenn Sie das Array mit variabler Länge aus der Bibliothek holen.)

  while(1) {
    for (int i = 0; i < 2; i ++) {
      if(CheckKeyInput(key_events[i]) == INPUT_DEV_EVENT_DETECTED)
        ToggleLed(led_devices[i]);
    }
  }

Wenn Sie sich die API von key_input_event.c ansehen und über die folgende API verfügen, können Sie sich anscheinend mit derselben Schnittstelle vereinen.

Ich denke, wenn das Prinzip der Einzelverantwortung auf Funktionsebene auf diese Weise verwirklicht werden kann, können gemeinsame Teile leicht gefunden und die Abstraktion gefördert werden. Besonders für unreife Dinge wie mich dachte ich erst, als ich eine konkrete Sache machte: "Oh, wenn ich es so abstrahiere, wird es gut organisiert sein" oder umgekehrt: "Ich dachte, ich könnte es so abstrahieren." Es war nicht gut. " In Vorbereitung wollte ich einen Rückrufmechanismus, den ich aber im Moment nicht brauchte (ich mache mir Sorgen um eine Schleife mit 100% CPU). In diesem Fall reicht es aus, etwa 10 ms zu schlafen.

Quellcode

Es ist für die Öffentlichkeit als [Release1.0] zugänglich (https://github.com/tomoyuki-nakabayashi/TDDforEmbeddedSystem/tree/Release1.0).

Zukünftige Erweiterungen

Bisher haben wir folgende Spezifikationen erreicht.

Ändern Sie dies in die folgenden Spezifikationen. Sie benötigen einen Timer als Element.

Wir werden dies mit TDD erweitern und weiterentwickeln.

Recommended Posts

Lassen Sie uns mit TDD ~ Intermediate Review ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns mit TDD ~ Preparation ~ etwas entwickeln, das nahezu eingebettet ist
Versuchen Sie, etwas zu entwickeln, das der Einbettung mit TDD nahe kommt ~ Problemauslösung ~
Lassen Sie uns mit TDD ~ Design pattern ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns etwas entwickeln, das mit TDD ~ file open edition ~ nahezu eingebettet ist
Lassen Sie uns mit TDD ~ Key Input Detection Version ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns etwas entwickeln, das mit TDD ~ libevdev Initialisierungs- / Beendigungsverarbeitung ~ nahezu eingebettet ist
[Einführung in WordCloud] Spielen Sie mit Scraping ♬
[Einführung in Python] Verwenden wir foreach mit Python
Lassen Sie uns mit Python 1 einen Investitionsalgorithmus entwickeln