Développons quelque chose de proche de celui intégré avec TDD ~ édition ouverte de fichier ~

introduction

En utilisant googletest / googlemock, je développe un logiciel qui fonctionne sur Linux embarqué avec TDD. Puisque la procédure réellement développée avec TDD est écrite telle quelle en temps réel, il y a une partie aléatoire (je ne peux pas dire que le fichier ouvert seul sera un tel montant ...). J'espère que vous apprécierez également le processus. Si vous avez des questions ou des erreurs, veuillez commenter. Ce sera encourageant.

Si vous souhaitez en savoir plus sur l'histoire jusqu'à présent, veuillez consulter les articles précédents. Essayez de développer quelque chose de proche de celui intégré avec TDD ~ Preparation ~ Essayez de développer quelque chose de proche de celui intégré avec TDD-Problem raising-

Faire une liste de choses à faire

Je suis une personne fidèle aux bases. Alors, suivez les enseignements de Kent Beck et James W. Grenning pour créer une liste de choses à faire.

Key input event

LED brightnesscontrol

Je les ai arrangés grossièrement comme je l'ai trouvé. Je ne peux penser à rien d'autre maintenant. Au fur et à mesure de la mise en œuvre, vous créerez de nouveaux éléments.

Maintenant, où est-il facile de toucher? Maintenant, nous avons une compréhension de libevdev. Il semble bon de commencer par implémenter un événement de frappe.

Rédiger un test de l'utilisation attendue

Comment KeyInputEvent doit-il être utilisé? Imaginez le format final et écrivez un test virtuel. Ce test peut être un vrai test ou un correctif en cours de développement. C'est juste un point de départ.

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());
}

Voici comment utiliser le module d'événement d'entrée supposé dans ce test.

  1. L'initialisation du périphérique renvoie true.
  2. Réglez l'événement à détecter (appuyez sur la touche "A").
  3. EventDetected () renvoie false jusqu'à ce que la touche "A" soit enfoncée.
  4. EventDetected () renvoie true lorsque la touche "A" est enfoncée.
  5. Arrêtez l'appareil.

Il n'y a pas d'inconfort particulier. Commentez ce test et passez au premier test.

Premier test

L'initialisation de l'événement Input est subdivisée en deux éléments suivants. Nous allons créer un test pour l'initialisation de l'événement d'entrée, mais nous allons d'abord ouvrir le fichier du périphérique.

Tester l'initialisation de l'événement Input

Le test est le suivant. Attendez-vous à ce que true revienne lorsque vous appelez l'initialisation.

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

Si j'essaie d'exécuter le test à ce stade, il ne se compile même pas. La mise en œuvre pour réussir ce test est la suivante. Au début, vous pouvez l'implémenter en retournant simplement true. Cette fois, le contenu à implémenter est clair (fichier ouvert!), Donc je ne ferai pas cette étape détaillée.

#define DEVICE_FILE "/dev/input/event2"

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

  return true;
}

Générez et exécutez le code. (L'autorité racine est requise en fonction de l'environnement) Le test réussit.

À ce stade, je suis soudainement devenu curieux. Ce module est-il censé être une seule instance? Je n'ai pas encore de réponse. Ajoutez des questions à la liste des tâches pour continuer.

Dans la section soulevant des questions, il n'était pas clair si la gestion des erreurs fonctionnerait. Et cette fois? Par exemple, je veux que le test suivant réussisse. Pour cela, il est nécessaire d'échouer l'initialisation.

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

Il est difficile de générer une erreur avec le code produit actuel. Si vous supprimez le fichier de périphérique d'événement, vous obtiendrez une erreur, mais vous ne pouvez pas détruire le système pour un test unitaire réussi. Le problème est clair. C'est parce que la macro code en dur le chemin du fichier de périphérique dans le code produit.

Par conséquent, l'interface de la fonction d'initialisation est modifiée. Donnez le fichier de l'appareil à ouvrir de l'extérieur.

bool InitKeyInputDevice(const char *device_file);

Voici le code qui teste l'échec de l'initialisation: Si vous passez le chemin d'un fichier de périphérique impossible, vous devriez obtenir une erreur avec ENOENT (le fichier spécifié est introuvable).

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

Le test réussit. La prise en compte d'une implémentation testable a rendu le code plus flexible. Cela fait du bien.

À ce stade, j'ai soudainement pensé. Dans le test où l'initialisation est réussie, j'ai volontairement ouvert un véritable périphérique événement d'entrée. Je ne peux pas penser à une raison logique d'utiliser la vraie chose. Les tests d'initialisation réussie sont désormais équivalents.

TEST_F(KeyInputEventTest, CanInitInputDevice) {
  std::ofstream("./test_event");
  EXPECT_TRUE(InitKeyInputDevice("./test_event"));
  std::remove("./test_event");  //Ne pas rendre les traces d'oiseaux debout boueuses
}

Le test réussit. D'accord, vous n'avez plus besoin de privilèges root pour exécuter le test. Le test sera exécuté des dizaines de milliers de fois. Cela devrait être facile à réaliser, même un peu.

Et ensuite je pense comme ça. La seule erreur d'ouverture de fichier qui peut être testée avec le code actuel est ENOENT. Dois-je tester d'autres erreurs? Vous devez rédiger les tests dont vous avez besoin pour votre produit (mais en temps et en consultation). Cette fois-ci, nous savons qu'un véritable périphérique d'événement d'entrée ne peut pas être ouvert sans autorisation. Ce serait bien de vous déconnecter du fait que vous ne pouvez pas ouvrir sans autorisation. Ajoutez les éléments suivants à la liste des tâches.

Cependant, il est difficile de se moquer de la bibliothèque standard POSIX. La définition de write () se fait dans la libc, qui est automatiquement liée lors de la création d'un programme en langage C. Vous pouvez faire de votre mieux pour tromper l'éditeur de liens, mais il semble plus facile de préparer une fonction wrapper.

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

Dans le test, liez la maquette de IO_OPEN (), et dans le code produit, appelez open () dans IO_OPEN ().

Le test utilisant le simulacre est le suivant. En cas de succès, un nombre positif de descripteurs de fichier est renvoyé. Normalement, il devrait être de 3 ou plus. Comme note complémentaire à EXPECT_CALL (), ce test teste que IO_OPEN () est appelé une fois. Lorsque le code produit appelle IO_OPEN (), il renvoie 3 comme valeur de retour.

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

En examinant le code du produit, InitKeyInputDevice () renvoie true lorsque IO_OPEN () renvoie un nombre supérieur ou égal à 0. La simulation renvoie 3 donc le test ↑ réussit.

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

  return true;
}

En cas d'échec d'ouverture de fichier, selon Page de manuel de OPEN

Renvoie -1 si une erreur se produit (errno est correctement défini dans ce cas).

Donc, pour tester l'échec de IO_OPEN (), il vous suffit de retourner -1 lorsque le mock est appelé.

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

Eh bien, l'introduction est devenue longue, mais ce que je vais faire maintenant, c'est ↓.

Nous voulons tester que errno change le comportement du code produit, nous définissons donc errno dans l'action d'appel simulé. Avec googlemock, il est possible d'exécuter des fonctions arbitraires lors de l'appel d'un simulacre. Cette fois, dans l'expression lambda, définissez EACCES sur errno afin que la fonction qui retourne -1 soit exécutée comme valeur de retour de IO_OPEN ().

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

Eh bien, c'est encore à mi-chemin. Comment puis-je tester la sortie du journal? Tout ce que vous avez à faire est de signaler la sortie du journal par le code du produit à l'espion.

TEST_F(KeyInputEventTest, FileOpenPermissionDenied) {
  //Paramètres d'espionnage
  char *spy[] {new char[128]};
  set_DEBUG_LOG_spy(spy, 128);

  InitKeyInputDevice("./event");  //Exécution de la cible de test
  EXPECT_STREQ("Fail to open file. You may need root permission.",
               spy);
}

Ce que fait l’espion n’est pas grave. Tout ce que vous avez à faire est d'attendre que le code produit appelle DEBUG_LOG () et de copier la sortie dans le tampon pré-passé.

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;
}

Le code produit est le suivant. Le fait est que le journal n'est pas sorti directement à l'aide d'une fonction telle que printf (). Il y a une chance pour les espions d'y entrer.

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;
}

Même dans le développement réel, vous utiliserez souvent une API de débogage uniquement qui contrôle le niveau de débogage. Utilisez l'éditeur de liens pour guider ces API vers les espions uniquement pendant les tests!

Organiser l'édition ouverte du fichier

Le statut de la liste des tâches est le suivant.

Le code à ce stade se trouve dans la balise FileOpen. Le code source créé cette fois est sous led_controller. https://github.com/tomoyuki-nakabayashi/TDDforEmbeddedSystem

Code de test

Il a passé trois tests.

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());
}

Code produit

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);
}

Si votre environnement nécessite des privilèges root pour ouvrir l'événement d'entrée, l'exécution du code produit sans privilèges root entraînera les résultats suivants:

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

Si vous spécifiez un chemin de fichier aléatoire, rien n'est généré. C'est ce que j'avais l'intention de tester à 100%. Vous devriez pouvoir avoir une grande confiance dans le fonctionnement de votre code produit.

Aperçu de la prochaine fois

Nous développerons l'initialisation et la terminaison du périphérique d'entrée avec TDD. La détection de la pression sur la touche «A» est considérée comme un volume pour un article.

Comme je l'ai écrit en haut, si vous avez des questions ou des erreurs, veuillez commenter.

Recommended Posts

Développons quelque chose de proche de celui intégré avec TDD ~ édition ouverte de fichier ~
Développons quelque chose qui se rapproche du TDD
Développons quelque chose de proche de intégré avec TDD ~ Revue intermédiaire ~
Développons quelque chose de proche de celui intégré avec TDD ~ Design pattern ~
Développons quelque chose de proche de celui intégré avec TDD ~ Version de détection d'entrée de clé ~
Développons quelque chose de proche de l'implémentation avec TDD ~ traitement d'initialisation / de terminaison de libevdev ~
Opération de fichier avec open - "../"
Développons quelque chose qui se rapproche du TDD
Développons quelque chose de proche de intégré avec TDD ~ Revue intermédiaire ~
Développons quelque chose de proche de celui intégré avec TDD ~ Design pattern ~
Développons quelque chose de proche de celui intégré avec TDD ~ édition ouverte de fichier ~
Développons quelque chose de proche de celui intégré avec TDD ~ Version de détection d'entrée de clé ~
Développons quelque chose de proche de l'implémentation avec TDD ~ traitement d'initialisation / de terminaison de libevdev ~
Étapes pour développer Django avec VSCode
[Introduction à WordCloud] Jouez avec le scraping ♬
[Introduction à Python] Utilisons foreach avec Python
Comment lire les données de problème avec Paiza
Développons un algorithme d'investissement avec Python 1
Étapes pour développer Django avec VSCode
[Python] Ecrire dans un fichier csv avec Python
Sortie vers un fichier csv avec Python
Ouvrir le fichier avec l'application par défaut
J'ai créé un capteur d'ouverture / fermeture (lien Twitter) avec TWE-Lite-2525A