[GO] Lassen Sie uns etwas entwickeln, das mit TDD ~ file open edition ~ nahezu eingebettet ist

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 kann nicht sagen, dass die allein geöffnete Datei eine solche Menge sein wird ...). 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 ~

Erstellen Sie eine Aufgabenliste

Ich bin eine Person, die den Grundlagen treu bleibt. Befolgen Sie also die Lehren von Kent Beck und James W. Grenning, um eine Aufgabenliste zu erstellen.

Key input event

LED brightnesscontrol

Ich habe sie grob arrangiert, als ich sie mir ausgedacht habe. Mir fällt jetzt nichts anderes ein. Während Sie mit der Implementierung fortfahren, werden Sie neue Elemente finden.

Wo ist es leicht zu berühren? Jetzt haben wir ein Verständnis von libevdev. Es scheint gut, zunächst ein Tastenanschlag-Ereignis zu implementieren.

Schreiben Sie einen Test der erwarteten Nutzung

Wie soll KeyInputEvent verwendet werden? Stellen Sie sich das endgültige Format vor und schreiben Sie einen virtuellen Test. Dieser Test kann ein echter Test oder ein Fix im Entwicklungsprozess sein. Es ist nur ein Ausgangspunkt.

TEST_F(KeyInputEventTest, AbstractUse) {
  EXPECT_TRUE(InitKeyInputDevice());
  SetKeyInputEventListener(condition);

  for (int i = 0; i < 10; i++)
    EXPECT_FALSE(EventDetected());
  EXPECT_TRUE(EventDetected());

  EXPECT_TRUE(FinalizeKeyInputDevice());
}

Hier erfahren Sie, wie Sie das in diesem Test angenommene Eingabeereignismodul verwenden.

  1. Die Initialisierung des Geräts gibt true zurück.
  2. Stellen Sie das zu erkennende Ereignis ein (drücken Sie die Taste "A").
  3. EventDetected () gibt false zurück, bis die Taste "A" gedrückt wird.
  4. EventDetected () gibt true zurück, wenn die Taste "A" gedrückt wird.
  5. Fahren Sie das Gerät herunter.

Es gibt keine besonderen Beschwerden. Kommentieren Sie diesen Test aus und fahren Sie mit dem ersten Test fort.

Erster Test

Die Initialisierung des Eingabeereignisses ist in die folgenden zwei Elemente unterteilt. Wir werden einen Test für die Initialisierung von Eingabeereignissen erstellen, aber zuerst werden wir die Gerätedatei öffnen.

Testen Sie die Initialisierung des Eingabeereignisses

Der Test ist wie folgt. Erwarten Sie, dass true zurückkommt, wenn Sie die Initialisierung aufrufen.

TEST_F(KeyInputEventTest, InitKeyInputDevice) {
  EXPECT_TRUE(InitKeyInputDevice());
}

Wenn ich zu diesem Zeitpunkt versuche, den Test auszuführen, wird er nicht einmal kompiliert. Die Implementierung zum Bestehen dieses Tests ist wie folgt. Zuerst können Sie es implementieren, indem Sie einfach true zurückgeben. Dieses Mal ist der zu implementierende Inhalt klar (Datei geöffnet!), Daher werde ich diesen detaillierten Schritt nicht ausführen.

#define DEVICE_FILE "/dev/input/event2"

bool InitKeyInputDevice() {
  int fd = open(DEVICE_FILE, O_RDONLY|O_NONBLOCK);
  if (fd < 0) return false;

  return true;
}

Erstellen Sie den Code und führen Sie ihn aus. (Abhängig von der Umgebung ist eine Root-Berechtigung erforderlich.) Der Test besteht.

Zu diesem Zeitpunkt wurde ich plötzlich neugierig. Soll dieses Modul eine einzelne Instanz sein? Ich habe noch keine Antwort. Fügen Sie der Aufgabenliste Fragen hinzu, um fortzufahren.

Im Abschnitt zur Fragestellung war unklar, ob die Fehlerbehandlung funktionieren würde. Wie wäre es mit dieser Zeit? Zum Beispiel möchte ich, dass der folgende Test erfolgreich ist. Zu diesem Zweck muss die Initialisierung fehlschlagen.

TEST_F(KeyInputEventTest, FailToInitInputDevice) {
  EXPECT_FALSE(InitKeyInputDevice());
}

Es ist schwierig, mit dem aktuellen Produktcode einen Fehler zu generieren. Wenn Sie die Ereignisgerätedatei löschen, wird eine Fehlermeldung angezeigt, Sie können das System jedoch nicht für einen erfolgreichen Komponententest zerstören. Das Problem ist klar. Dies liegt daran, dass der Gerätedateipfad im Makro im Produktcode fest codiert ist.

Daher wird die Schnittstelle der Initialisierungsfunktion geändert. Geben Sie die Gerätedatei von außen zu öffnen.

bool InitKeyInputDevice(const char *device_file);

Hier ist der Code, der die Initialisierung auf Fehler testet: Wenn Sie den Pfad einer unmöglichen Gerätedatei übergeben, sollte bei ENOENT eine Fehlermeldung angezeigt werden (die angegebene Datei wurde nicht gefunden).

TEST_F(KeyInputEventTest, FailToInitInputDevice) {
  EXPECT_FALSE(InitKeyInputDevice("/dev/input/not_found"));
}

Der Test ist erfolgreich. Durch die Prüfung einer testbaren Implementierung wurde der Code flexibler. Das fühlt sich ziemlich gut an.

An diesem Punkt dachte ich plötzlich. In dem Test, in dem die Initialisierung erfolgreich ist, öffne ich absichtlich ein echtes Eingabeereignisgerät. Ist es notwendig? Ich kann mir keinen logischen Grund vorstellen, die reale Sache zu benutzen. Die Tests für eine erfolgreiche Initialisierung sind jetzt gleichwertig.

TEST_F(KeyInputEventTest, CanInitInputDevice) {
  std::ofstream("./test_event");
  EXPECT_TRUE(InitKeyInputDevice("./test_event"));
  std::remove("./test_event");  //Die stehenden Vogelspuren nicht trüben
}

Der Test besteht. Okay, jetzt brauchen Sie keine Root-Rechte mehr, um den Test auszuführen. Der Test wird zehntausende Male durchgeführt. Es sollte leicht sein, auch nur ein wenig durchzuführen.

Und als nächstes denke ich so. Der einzige Fehler beim Öffnen von Dateien, der mit dem aktuellen Code getestet werden kann, ist ENOENT. Muss ich andere Fehler testen? Sie sollten die Tests schreiben, die Sie für Ihr Produkt benötigen (aber Zeit und Beratung). Dieses Mal wissen wir, dass ein echtes Eingabeereignisgerät nicht ohne Erlaubnis geöffnet werden kann. Es wäre schön, die Tatsache abzumelden, dass Sie nicht ohne Erlaubnis öffnen konnten. Fügen Sie der Aufgabenliste die folgenden Elemente hinzu.

Es ist jedoch schwierig, die POSIX-Standardbibliothek zu verspotten. Die Definition von write () erfolgt in libc, das beim Erstellen eines C-Sprachprogramms automatisch verknüpft wird. Sie können Ihr Bestes tun, um den Linker zu täuschen, aber es scheint einfacher zu sein, eine Wrapper-Funktion vorzubereiten.

  int IO_OPEN(const char *pathname, int flags);

Verknüpfen Sie im Test das Modell von IO_OPEN () und rufen Sie im Produktcode open () in IO_OPEN () auf.

Der Test mit dem Mock ist wie folgt. Bei Erfolg wird eine positive Anzahl von Dateideskriptoren zurückgegeben. Normalerweise sollte es 3 oder mehr sein. Als Randnotiz zu EXPECT_CALL () testet dieser Test, dass IO_OPEN () einmal aufgerufen wird. Wenn der Produktcode IO_OPEN () aufruft, gibt er 3 als Rückgabewert zurück.

TEST_F(KeyInputEventTest, CanInitInputDevice) {
  EXPECT_CALL(*mock_io, IO_OPEN(_, _)).WillOnce(Return(3));
  EXPECT_TRUE(InitKeyInputDevice("./test_event"));
}

Beim Überprüfen des Produktcodes gibt InitKeyInputDevice () true zurück, wenn IO_OPEN () eine Zahl größer oder gleich 0 zurückgibt. Der Mock gibt 3 zurück, sodass der ↑ Test erfolgreich ist.

bool InitKeyInputDevice(const char *device_file) {
  int fd = IO_OPEN(device_file, O_RDONLY|O_NONBLOCK);
  if (fd < 0) return false;

  return true;
}

Im Falle eines Fehlers beim Öffnen der Datei gemäß Manpage of OPEN

Gibt -1 zurück, wenn ein Fehler auftritt (in diesem Fall ist errno entsprechend eingestellt).

Um auf IO_OPEN () zu testen, müssen Sie nur -1 zurückgeben, wenn der Mock aufgerufen wird.

TEST_F(KeyInputEventTest, FailToInitInputDevice) {
  EXPECT_CALL(*mock_io, IO_OPEN(_, _)).WillOnce(Return(-1));
  EXPECT_FALSE(InitKeyInputDevice("./file_not_found"));
}

Nun, die Einführung ist lang geworden, aber was ich jetzt tun werde, ist ↓.

Wir möchten testen, ob errno das Verhalten des Produktcodes ändert, also setzen wir errno in der Scheinaufrufaktion. Mit googlemock ist es möglich, beim Aufrufen eines Mocks beliebige Funktionen auszuführen. Setzen Sie diesmal im Lambda-Ausdruck EACCES auf errno, damit die Funktion, die -1 zurückgibt, als Rückgabewert von IO_OPEN () ausgeführt wird.

TEST_F(KeyInputEventTest, FileOpenPermissionDenied) {
  EXPECT_CALL(*mock_io, IO_OPEN(_, _)).WillOnce(
    Invoke([](constchar*,int){errno=EACCES;return-1;}));
  EXPECT_FALSE(InitKeyInputDevice("./event"));
}

Nun, es ist immer noch auf halbem Weg. Wie kann ich die Ausgabe des Protokolls testen? Sie müssen lediglich die Protokollausgabe des Produktcodes an den Spion melden.

TEST_F(KeyInputEventTest, FileOpenPermissionDenied) {
  //Spionageeinstellungen
  char *spy[] {new char[128]};
  set_DEBUG_LOG_spy(spy, 128);

  InitKeyInputDevice("./event");  //Ausführung des Testziels
  EXPECT_STREQ("Fail to open file. You may need root permission.",
               spy);
}

Was der Spion tut, ist keine große Sache. Sie müssen lediglich warten, bis der Produktcode DEBUG_LOG () aufruft, und die Ausgabe in den vorab übergebenen Puffer kopieren.

static char *buffer = NULL;
static int buffer_size = 0;

void DEBUG_LOG(const char *message) {
  strncpy(buffer, message, buffer_size-1);
}

void set_DEBUG_LOG_spy(char *b, const int size) {
  buffer = b;
  buffer_size = size;
}

Der Produktcode lautet wie folgt. Der Punkt ist, dass das Protokoll nicht direkt mit einer Funktion wie printf () ausgegeben wird. Es besteht die Möglichkeit, dass Spione dort hineinkommen.

bool InitKeyInputDevice(const char *device_file) {
  int fd = IO_OPEN(device_file, O_RDONLY|O_NONBLOCK);
  if (fd < 0) {
    if (errno == EACCES)
      DEBUG_LOG("Fail to open file. You may need root permission.");
    return false;
  }

  return true;
}

Selbst in der tatsächlichen Entwicklung verwenden Sie häufig eine Nur-Debug-API, die die Debug-Ebene steuert. Verwenden Sie einen Linker, um solche APIs nur während des Tests zu Spionen zu führen!

Datei Open Edition organisieren

Der Status der Aufgabenliste lautet wie folgt.

Der Code an dieser Stelle befindet sich im FileOpen-Tag. Der diesmal erstellte Quellcode befindet sich unter led_controller. https://github.com/tomoyuki-nakabayashi/TDDforEmbeddedSystem

Testcode

Es hat drei Tests bestanden.

TEST_F(KeyInputEventTest, CanInitInputDevice) {
  EXPECT_CALL(*mock_io, IO_OPEN(_, _)).WillOnce(Return(3));
  EXPECT_TRUE(InitKeyInputDevice("./test_event"));
}

TEST_F(KeyInputEventTest, FailToInitInputDevice) {
  EXPECT_CALL(*mock_io, IO_OPEN(_, _)).WillOnce(
    Invoke([](constchar*,int){errno=ENOENT;return-1;}));
  EXPECT_FALSE(InitKeyInputDevice("./file_not_found"));
}

TEST_F(KeyInputEventTest, FileOpenPermissionDenied) {
  std::unique_ptr<char[]> spy {new char[128]};
  set_DEBUG_LOG_spy(spy.get(), 128);

  EXPECT_CALL(*mock_io, IO_OPEN(_, _)).WillOnce(
    Invoke([](constchar*,int){errno=EACCES;return-1;}));

  EXPECT_FALSE(InitKeyInputDevice("./test_event"));
  EXPECT_STREQ("Fail to open file. You may need root permission.",
               spy.get());
}

Produktcode

bool InitKeyInputDevice(const char *device_file) {
  int fd = IO_OPEN(device_file, O_RDONLY|O_NONBLOCK);
  if (fd < 0) {
    if (errno == EACCES)
      DEBUG_LOG("Fail to open file. You may need root permission.");
    return false;
  }

  return true;
}
int main(void) {
  InitKeyInputDevice("/dev/input/event2");
  return 0;
}

int IO_OPEN(const char *pathname, int flags) {
  return open(pathname, flags);
}

void DEBUG_LOG(const char *message) {
  printf("%s\n", message);
}

Wenn Ihre Umgebung Root-Berechtigungen zum Öffnen von Eingabeereignissen erfordert, führt das Ausführen des Produktcodes ohne Root-Berechtigungen zu folgenden Ergebnissen:

./led_controller
Fail to open file. You may need root permission.

Wenn Sie einen zufälligen Dateipfad angeben, wird nichts ausgegeben. Dies ist, was ich in 100% Tests beabsichtigt habe. Sie sollten in der Lage sein, viel Vertrauen in die Funktionsweise Ihres Produktcodes zu haben.

Vorschau beim nächsten Mal

Wir werden die Initialisierung und Beendigung des Eingabegeräts mit TDD entwickeln. Das Erkennen des Tastendrucks "A" wird als Volumen für einen Artikel betrachtet.

Wie ich oben geschrieben habe, kommentieren Sie bitte, wenn Sie Fragen oder Fehler haben.

Recommended Posts

Lassen Sie uns etwas entwickeln, das mit TDD ~ file open edition ~ nahezu eingebettet ist
Lassen Sie uns mit TDD ~ Preparation ~ etwas entwickeln, das nahezu eingebettet ist
Lassen Sie uns mit TDD ~ Intermediate Review ~ etwas entwickeln, das dem Integrierten nahe kommt
Lassen Sie uns mit TDD ~ Design pattern ~ etwas entwickeln, das dem Integrierten nahe kommt
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
Dateivorgang mit open - "../"
Lassen Sie uns mit TDD ~ Preparation ~ etwas entwickeln, das nahezu eingebettet ist
Lassen Sie uns mit TDD ~ Intermediate Review ~ etwas entwickeln, das dem Integrierten nahe kommt
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
Schritte zur Entwicklung von Django mit VSCode
[Einführung in WordCloud] Spielen Sie mit Scraping ♬
[Einführung in Python] Verwenden wir foreach mit Python
Wie man Problemdaten mit Paiza liest
Lassen Sie uns mit Python 1 einen Investitionsalgorithmus entwickeln
Schritte zur Entwicklung von Django mit VSCode
[Python] Mit Python in eine CSV-Datei schreiben
Ausgabe in eine CSV-Datei mit Python
Datei mit Standard-App öffnen
Ich habe mit TWE-Lite-2525A einen Öffnungs- / Schließsensor (Twitter-Link) erstellt