[JAVA] Relation entre le test et l'enregistrement de l'interface utilisateur, méthode de mise en œuvre

Cet article est le 20e jour du Calendrier de l'Avent Recruit Lifestyle 2017.

introduction

Je m'appelle @AHA_oretama et mon premier enfant est né avant-hier. J'ai été occupé par divers événements privés, et j'ai été un peu en retard dans la publication, mais pardonnez-moi s'il vous plaît!

Je recrute et fais généralement une activité appelée R-SET. Le mot SET lui-même est un rôle préconisé par Google et a été introduit dans Google Testing Blog. Je suis. L'extrait de la partie explicative de SET est le suivant.

The SET or Software Engineer in Test is also a developer role except their focus is on testability.

En résumé, c'est «l'ingénieur se concentrant sur la testabilité → SET».

R-SET est un mot inventé créé pour améliorer la recherche et est défini comme suit.

R-SET is Recruit Lifestyle's SET.

Chez R-SET, en augmentant la testabilité des services de style de vie de recrutement

Est répertorié comme une mission.

Maintenant que nous l'avons présenté, j'aimerais parler de l'enregistrement du test d'interface utilisateur de l'application.

À propos des caractéristiques de test de l'interface utilisateur et de la fonction d'enregistrement

Tout d'abord, assurez-vous que vous connaissez le test d'interface utilisateur mentionné ici.

スクリーンショット 2017-12-20 18.57.53.png

Connaissez-vous le mot pyramide de test? ?? La pyramide des tests est un diagramme montrant les caractéristiques des tests à automatiser, et les tests d'interface utilisateur, les tests d'intégration et les tests unitaires sont classés dans l'ordre du haut. En général, le test d'interface utilisateur fait souvent référence au test de bout en bout avec tous les modules et services externes connectés.

Cette fois, celui qui correspond à cette pyramide de test s'appelle le test de l'interface utilisateur.

En tant que caractéristique de la pyramide, la taille de la pyramide représente le rapport idéal du nombre de cas de test pour l'automatisation. En d'autres termes, on dit qu'il est idéal de faire de nombreux tests unitaires et pas trop de tests d'interface utilisateur. Il présente également les caractéristiques d'être instable, de prendre plus de temps à exécuter les tests et de plus coûteux à mesure que vous montez dans la pyramide.

Et les caractéristiques de ce test d'interface utilisateur entraînent la nécessité d'un enregistrement.

L'automatisation présente un certain nombre d'avantages, dont l'un est la réduction des coûts. Cependant, en raison de ses caractéristiques, les tests d'interface utilisateur ont tendance à être des tests instables. Si vous avez effectué des tests d'interface utilisateur, vous avez expérimenté des choses comme, parfois le test réussit et parfois le test échoue, même si vous n'avez pas changé la source. N'est-ce pas? Si un test échoue, vous devrez identifier la cause de l'échec du test, mais il s'agit d'un test instable et il n'est pas toujours possible de rejouer le test ayant échoué. Si vous ne l'exécutez pas encore et encore, le même état ne peut pas être reproduit et il faudra du temps pour en rechercher la cause. Une autre caractéristique des tests d'interface utilisateur est qu'ils prennent beaucoup de temps à s'exécuter. Plus le test échoué est proche de la fin du scénario de test, plus il faudra de temps pour atteindre cet état.

As-tu remarqué? Même si les avantages de l'automatisation sont des économies de coûts, la maintenance des tests automatisés est coûteuse et les avantages sont perdus!

Mais que faire si le test échoué a été enregistré à ce moment-là?

Par exemple

Vous pourrez souvent en comprendre la cause.

En bref, l'enregistrement est une réponse à l'instabilité des tests de l'interface utilisateur et à l'augmentation des coûts dus aux longs délais d'exécution, ce qui est nécessaire pour bénéficier efficacement de l'automatisation.

Comment enregistrer

Maintenant que nous connaissons l'importance de l'enregistrement dans le test de l'interface utilisateur, parlons de la façon d'enregistrer. Cette fois, je me concentrerai sur les applications, je voudrais donc vous présenter le WEB s'il y a une autre opportunité.

Enregistrement sur des appareils Android

L'enregistrement sur les appareils Android peut être exécuté avec la commande ADB suivante.

adb shell screenrecord {filename}

Vous pouvez l'arrêter avec Ctrl + C.

Ici, le fichier de sortie est enregistré sur l'appareil Android. Puisque le nom de fichier est le nom du fichier sur l'appareil Android, il ressemble à / sdcard / demo.mp4.

Je pense que les fichiers enregistrés sur les appareils Android sont souvent enregistrés sur le PC local. Vous pouvez créer un fichier sur votre appareil Android sur votre PC local avec la commande ADB suivante.

adb pull {remote} {local}

Selon le Guide de l'utilisateur, la fonction d'enregistrement est prise en charge pour Android 4.4 (API niveau 19) ou version ultérieure. ,Faites attention. Notez également que la durée d'enregistrement maximale est de 180 secondes (3 minutes) au maximum.

Enregistrement sur simulateur iOS

L'enregistrement du simulateur iOS peut être exécuté par la commande de l'utilitaire de ligne de commande Xcode suivante.

xcrun simctl io booted recordVideo {filename}

Dans le cas de iOS Simulator, la destination de sauvegarde est le PC local. iOS peut également être arrêté avec Ctrl + C.

Selon Notes de publication Veuillez noter que cette fonction d'enregistrement est une fonction de Xcode 8.2.

Implémentation en Java

Nous implémenterons la fonction de création d'un test d'interface utilisateur à l'aide d'Appium et d'enregistrement du test en Java. Bien qu'il existe de nombreux documents japonais pour Ruby dans Appium, il existe peu de documents japonais pour d'autres langues (bien que le site original en anglais le soit également ...), je pense donc qu'il est logique d'écrire des fonctions Appium en Java. Je vais.

Voici une mise en œuvre concrète.

C'est un environnement au cas où, mais je l'ai confirmé dans l'environnement suivant.

Introduisons l'implémentation.

public class RecordFactory {

    public static RecordFactory factory = new RecordFactory();

    public static RecordFactory getInstance() {
        return factory;
    }

    public Record createRecord(AppiumDriver driver, String fileName, String output) {
        if (driver.getPlatformName().equals("Android")) {
            return new AdbRecord(fileName, output);
        } else {
            return new iOSSimulatorRecord(fileName, output);
        }
    }
}
interface Record extends Closeable {
  void start()
}

Explication du code ci-dessus. ʻAppiumDriver.getPlatformName () `peut être utilisé pour obtenir le terminal d'exécution (Android ou iOS). Appium a l'avantage d'être un outil de test d'interface utilisateur multiplateforme, nous permettons donc ici de l'utiliser quel que soit le terminal d'enregistrement en créant une classe Factory qui détermine le terminal en cours d'exécution et crée une classe en fonction de celui-ci. Je vais. Puisqu'il y avait une image de «Closeable» dans le processus d'enregistrement, j'ai ajouté cette interface, mais je pense que c'est OK sans rien de particulier.

public class AdbRecord implements Record {

    private final String fileName;
    private final String outputDir;
    private Process recordProcess;
    private final String outputPath;

    AdbRecord(String fileName,String outputDir) {
        this.fileName = fileName.endsWith(".mp4") ? fileName : fileName + ".mp4";
        this.outputDir = outputDir;
        this.outputPath = outputDir + "/" + fileName;
    }

    @Override
    public void start() throws IOException {
        ProcessBuilder builder =
            new ProcessBuilder("adb", "shell", "screenrecord", "/sdcard/" + fileName);
        builder.redirectErrorStream(true);

        recordProcess = builder.start();
    }

    @Override
    public void close() throws IOException {
        if (recordProcess != null) {
            int pid = 0;
            try {
                Field field = recordProcess.getClass().getDeclaredField("pid");
                field.setAccessible(true);
                pid = field.getInt(recordProcess);
            } catch (IllegalAccessException | NoSuchFieldException e) {
                e.printStackTrace();
            }

            while(true) {
                ProcessBuilder builder = new ProcessBuilder("kill", "-2", String.valueOf(pid));
                builder.start();
                TestUtils.sleep(1000);
                if(!recordProcess.isAlive()) {
                    break;
                }
            }
        }

        File dir = new File(outputDir);
        if(!dir.exists()) {
            dir.mkdir();
        }

        //Mettre en veille car il y a un léger décalage jusqu'à ce que le fichier vidéo soit terminé
        TestUtils.sleep(3000);

        //Copier localement des fichiers vidéo sur des appareils Android
        execProcess("adb", "pull", "/sdcard/" + fileName, outputPath);
        //Supprimer les fichiers vidéo sur les appareils Android
        execProcess("adb", "shell", "rm", "-f","/sdcard/" + fileName);

        try (InputStream stream = Files.newInputStream(Paths.get(outputPath))) {
            Allure.addAttachment(fileName, stream);
        }
    }

    private void execProcess(String... args) throws IOException {
        ProcessBuilder builder = new ProcessBuilder(args);
        Process process = builder.start();
        try {
            process.waitFor(20, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class iOSSimulatorRecord implements Record {

    private Process recordProcess;
    private final String fileName;
    private final File outputDir;
    private final String outputPath;

    iOSSimulatorRecord(String fileName, String outputDir) {
        this.fileName = fileName.endsWith(".mov") ? fileName : fileName + ".mov";
        this.outputDir = new File(outputDir);
        this.outputPath = this.outputDir.getAbsolutePath() + "/" + fileName;
    }

    @Override
    public void start() throws IOException {
        if(!outputDir.exists()) {
            outputDir.mkdir();
        }

        ProcessBuilder builder =
            new ProcessBuilder("xcrun", "simctl", "io", "booted", "recordVideo", outputPath);
        builder.redirectErrorStream(true);

        recordProcess = builder.start();
    }

    @Override
    public void close() throws IOException {

        if (recordProcess != null) {
            int pid = 0;
            try {
                Field field = recordProcess.getClass().getDeclaredField("pid");
                field.setAccessible(true);
                pid = field.getInt(recordProcess);
            } catch (IllegalAccessException | NoSuchFieldException e) {
                e.printStackTrace();
            }

            while(true) {
                ProcessBuilder builder = new ProcessBuilder("kill", "-2", String.valueOf(pid));
                builder.start();
                TestUtils.sleep(1000);
                if(!recordProcess.isAlive()) {
                    break;
                }
            }
        }
    }
}

Android et iOS utilisent la classe Process de Java pour démarrer le processus d'enregistrement pour chaque terminal. En Java, le processus d'obtention du pid est si compliqué que c'est compliqué, mais tout ce que vous faites est d'obtenir le pid. Android est différent d'iOS en ce qu'il dispose d'un processus pour l'enregistrer sur le PC local après l'enregistrement.

L'utilisateur peut l'utiliser comme suit.

        try (Record record = recordFactory.createRecord(driver, fileName, BUILD_REPORTS_RECORDS)) {
            record.start();
            // "Exécuter le test de l'interface utilisateur"
        }

De cette façon, vous pouvez également enregistrer en Java.

finalement

Le développement de cas de test avec Appium en est encore à ses balbutiements, et je pense que diverses choses vont sortir dans le fonctionnement et le développement, je voudrais donc le présenter à nouveau si nécessaire.

Recommended Posts

Relation entre le test et l'enregistrement de l'interface utilisateur, méthode de mise en œuvre
Relation entre le package et la classe
Différence entre la méthode d'instance et la méthode de classe
Différence entre l'opérateur == et la méthode égale
Différence entre l'opérateur == et la méthode eqals
[Java] Relation entre H2DB et JDBC
[Java] Structure d'introduction Définition de classe Relation entre classe et instance Format de définition de méthode
Relation entre les modificateurs d'accès kotlin et java
Relation entre le plug-in Eclipse m2e et Maven
[Rails] Différence entre la méthode de création et la méthode nouvelle + sauvegarde
CRUD et méthode des ressources ~ 3 Pas une relation secrète ~
Comment envoyer des transactions Ethereum Différence entre send et sendAsync
À propos de la relation entre les méthodes HTTP, les actions et CRUD
Vérification de la relation entre l'image Docker et le conteneur