0 Abonnés · 20 Publications

Les tests de logiciels correspondent à une enquête menée pour fournir aux parties prenantes des informations sur la qualité du produit ou du service logiciel testé.

Article Sylvain Guilbaud · Oct 7, 2025 5m read

Aperçu Je suis ravi d'annoncer la sortie de testcontainers-iris-node, une bibliothèque Node.js qui facilite le lancement de conteneurs InterSystems IRIS temporaires pour l'intégration et les tests E2E. Ce projet vient naturellement compléter la gamme existante d'adaptateurs Testcontainers pour IRIS, notamment testcontainers-iris-python et testcontainers-iris-java.

Pourquoi testcontainers-iris-node? En tant que développeur Node.js travaillant avec InterSystems IRIS, j'ai souvent été confronté à des difficultés lors de la configuration d'environnements de test imitant la production. testcontainers-iris-node résout ce problème en exploitant le framework testcontainers-node pour créer des environnements IRIS isolés à la demande.

Ceci est particulièrement important pour:

  • Les tests d'intégration avec les bases de données IRIS
  • Les tests de pipelines de données ou de microservices
  • L'automatisation des environnements de test dans les pipelines CI

Fonctionnalités

  • Lancement d'IRIS dans des conteneurs Docker à l'aide de Testcontainers
  • Prise en charge des images et de la configuration Docker personnalisées
  • Stratégies d'attente pour s'assurer qu'IRIS est prêt avant le début des tests
  • Désinstallation de nettoyage entre les exécutions de tests

Pour commencer

npm install testcontainers-iris --save-dev

Exemple d'utilisation

import { IRISContainer } from"testcontainers-iris";
import { createConnection } from"@intersystems/intersystems-iris-native";

const IMAGE = "containers.intersystems.com/intersystems/iris-community:latest-preview"; const container = awaitnew IRISContainer(IMAGE).start(); const connection = createConnection(container.getConnectionOptions()); const iris = connection.createIris(); const version = iris.classMethodString("%SYSTEM.Version", "GetNumber");

Fonctionnement En interne, la bibliothèque étend GenericContainer à partir de testcontainers, ajoute des stratégies d'attente spécifiques à IRIS et fournit des méthodes auxiliaires pour la génération de chaînes de connexion et le remplacement des configurations.

Scénarios pris en charge

  • Suites de tests basées sur Jest ou Mocha
  • Environments CI (GitHub Actions, GitLab CI, Jenkins, etc.)
  • Développement et débogage locaux

Exemple de tests basées sur Mocha Vous pouvez également utiliser cette bibliothèque pour des tests d'intégration robustes avec Mocha. Voici un exemple de configuration:

test-setup.ts

import"source-map-support/register"import"reflect-metadata"import { IRISContainer, StartedIRISContainer } from"testcontainers-iris"import { IRISNative } from"../../src"import chai from"chai"import sinonChai from"sinon-chai"import chaiAsPromised from"chai-as-promised"
declare global {
    var container: StartedIRISContainer | undefinedvar connectionOptions: {
        host: string
        port: number
        user: string
        pwd: string
        ns: string
    }
}

process.env.TZ = "UTC" chai.should() chai.use(sinonChai) chai.use(chaiAsPromised)

before(async () => { console.log("Setting up test environment...") const image = process.env["IRIS_IMAGE"] let connectionOptions = { host: "localhost", port: 1972, user: "_SYSTEM", pwd: "SYS", ns: "USER", } if (image) { const container: StartedIRISContainer = awaitnew IRISContainer(image) .withNamespace("TEST") .start() console.log(IRIS container started at <span class="hljs-subst">${container.getConnectionUri()}</span>) global.container = container connectionOptions = { host: container.getHost(), port: container.getMappedPort(1972), user: container.getUsername(), pwd: container.getPassword(), ns: container.getNamespace(), } } global.connectionOptions = connectionOptions IRISNative.createConnection({ ...connectionOptions, sharedmemory: false }) })

after(async () => { console.log("Cleaning up test environment...") if (global.container) { await global.container.stop() } delete global.container })

Cas de test:

import { IRISNative, IRISConnection } from"../src/IRISNative"
describe("IRISNative test", () => {
    let connection: IRISConnection
    before(() => {
        const connectionOptions = global.connectionOptions
        connection = IRISNative.createConnection({ ...connectionOptions })
    })
    after(() => {
        if (connection) {
            connection.close()
        }
    })
    it("should work", async () => {
        const res = await connection.query(
            "SELECT 1 AS test1, '2' AS test2",
            [],
        )
        res.rows.should.be.an("array")
        res.rows.should.have.lengthOf(1)
        res.rows[0].should.be.an("object")
        res.rows[0].should.have.property("test1")
        res.rows[0].should.have.property("test2")
        res.rows[0].should.have.property("test1", 1)
        res.rows[0].should.have.property("test2", "2")
    })
})

Utilisation dans typeorm-iris Cette bibliothèque est également utilisée dans mon projet typeorm-iris qui fournit une prise en charge expérimentale de TypeORM pour InterSystems IRIS. testcontainers-iris-node alimente la configuration des tests d'intégration pour ce projet, aidant à valider la fonctionnalité ORM par rapport à des instances IRIS réelles.

Problèmes liés à l'adoption de la bibliothèque En tant que développeur chargé de l'adoption de la bibliothèque, l'un de mes plus grands défis consiste à tester plusieurs versions d'InterSystems IRIS. Cet outil simplifie considérablement ce processus en permettant de basculer facilement et d'automatiser des environnements conteneurisés avec des versions IRIS différentes.

Comparaison avec d'autres liaisons linguistiques Alors que testcontainers-iris-python et testcontainers-iris-java sont matures et prennent en charge des fonctionnalités avancées telles que les montages en bind et les scripts de démarrage personnalisés, la variante Node.js est rationalisée pour les environnements JavaScript/TypeScript et vise la simplicité et l'ergonomie pour les développeurs.

Contributions et commentaires Je suis ouvert à tous commentaires, problèmes et demandes de modification via GitHub: testcontainers-iris-node.

Pour concluretestcontainers-iris-node facilite la réalisation de tests automatisés et robustes des applications basées sur IRIS dans l'écosystème Node.js. Que vous développiez des API, des tâches ETL ou des services d'intégration, cet outil vous aide à garantir la fiabilité de vos flux de travail IRIS.

0
1 17
Article Guillaume Rongier · Sept 11, 2025 6m read

img

Connaissant désormais bien Python et ses fonctionnalités, voyons comment nous pouvons tirer parti de Python dans IRIS.

Balise de langue

La balise de langue est une fonctionnalité d'IRIS qui vous permet d'écrire du code Python directement dans vos classes ObjectScript.

Cette fonctionnalité est utile pour le prototypage rapide ou lorsque vous souhaitez utiliser les fonctionnalités de Python sans créer de script Python séparé.

Utilisation

Pour utiliser la balise de langue, il faut définir une méthode de classe avec l'attribut Language = python. Exemple:

Class Article.LanguageTagExample Extends %RegisteredObject
{

ClassMethod Run() [ Language = python ]
{
        import requests

        response = requests.get("https://2eb86668f7ab407989787c97ec6b24ba.api.mockbin.io/")

        my_dict = response.json()

        for key, value in my_dict.items():
            print(f"{key}: {value}") # print message: Hello World
}

}

Quels sont donc les avantages et les inconvénients de l'utilisation de la balise de langue?

Avantages

  • Simplicité : vous pouvez écrire du code Python directement dans vos classes ObjectScript sans avoir à créer de fichiers Python séparés.
  • Prototypage rapide : idéal pour le prototypage rapide ou le test de petits fragments de code Python.
  • Intégration : vous pouvez facilement intégrer du code Python à votre code ObjectScript.

Inconvénients

  • Code mixte : le mixage de code Python et ObjectScript peut rendre votre code plus difficile à lire et à maintenir.
  • Débogage : vous ne pouvez pas déboguer à distance le code Python écrit dans la balise de langage, ce qui peut constituer une limitation pour les applications complexes.
  • Tracebacks : les tracebacks Python ne s'affichent pas, vous ne voyez qu'un message d'erreur ObjectScript, ce qui peut rendre le débogage plus difficile.

Conclusion

La balise de langue est une fonctionnalité puissante qui vous permet d'écrire du code Python directement dans vos classes ObjectScript. Cependant, elle a ses limitations et il est important de l'utiliser de manière raisonnable. Pour les projets plus importants ou lorsque vous devez déboguer votre code Python, il est préférable de créer des scripts Python séparés et de les importer dans vos classes ObjectScript.

Importation de modules Python (modules pypi)

Maintenant que nous avons une bonne compréhension de la balise de langue, voyons comment importer des modules Python et les utiliser dans ObjectScript.

Tout d'abord, nous allons nous limiter aux modules intégrés et aux modules tiers provenant de PyPI, tels que module de demande requests, module numpy, etc.

Utilisation

Ici, nous allons faire la même chose, mais en utilisant uniquement le module de demande de PyPI.

Class Article.RequestsExample Extends %RegisteredObject
{

ClassMethod Run() As %Status
{
    set builtins = ##class(%SYS.Python).Import("builtins")
    Set requests = ##class(%SYS.Python).Import("requests")

    Set response = requests.get("https://2eb86668f7ab407989787c97ec6b24ba.api.mockbin.io/")
    Set myDict = response.json()

    for i=0:1:builtins.len(myDict)-1 {
        set key = builtins.list(myDict.keys())."__getitem__"(i)
        set value = builtins.list(myDict.values())."__getitem__"(i)
        write key, ": ", value, !
    }
}

}

Exécutons-le:

iris session iris -U IRISAPP '##class(Article.RequestsExample).Run()'

Vous verrez le résultat:

message: Hello World

Avantages

  • Accès aux bibliothèques Python : vous pouvez utiliser toutes les bibliothèques Python disponibles sur PyPI, ce qui vous donne accès à un vaste écosystème de bibliothèques et d'outils.
  • Un seul type de code : vous n'écrivez que du code ObjectScript, ce qui facilite la lecture et la maintenance.
  • Débogage : vous pouvez déboguer votre code ObjectScript comme s'il s'agissait uniquement de code ObjectScript, ce qui est le cas :)

Inconvénients

  • Bonne connaissance de Python : vous devez avoir une bonne compréhension de Python pour utiliser efficacement ses bibliothèques.
  • Consultez des exemples dans les articles sur les méthodes dunder.
  • Pas d'écriture de code Python : vous n'écrivez pas de code Python, mais du code ObjectScript qui appelle du code Python, ce qui évite le sucre syntaxique de Python.

Conclusion

En conclusion, l'importation de modules Python dans ObjectScript peut considérablement améliorer les capacités de votre application en tirant parti du vaste écosystème de bibliothèques Python. Cependant, il est essentiel de comprendre les compromis impliqués, tels que la nécessité d'une solide maîtrise de Python.

Importation de modules Python (modules personnalisés)

Continuons avec le même exemple, mais cette fois-ci, nous allons créer un module Python personnalisé et l'importer dans ObjectScript.

Cette fois-ci, nous utiliserons Python autant que possible, et ObjectScript ne sera utilisé que pour appeler le code Python.

Utilisation

Créons un module Python personnalisé dénommé my_script.py avec le contenu suivant:

import requests

def run():
    response = requests.get("https://2eb86668f7ab407989787c97ec6b24ba.api.mockbin.io/")

    my_dict = response.json()

    for key, value in my_dict.items():
        print(f"{key}: {value}") # print message: Hello World

Maintenant, nous allons créer une classe ObjectScript pour importer et exécuter ce module Python:

Class Article.MyScriptExample Extends %RegisteredObject
{
    ClassMethod Run() As %Status
    {
        set sys = ##class(%SYS.Python).Import("sys")
        do sys.path.append("/irisdev/app/src/python/article")  // Adjust the path to your module

        Set myScript = ##class(%SYS.Python).Import("my_script")

        Do myScript.run()

        Quit $$$OK
    }
}

Maintenant, exécutons-le:

iris session iris -U IRISAPP '##class(Article.MyScriptExample).Run()'

⚠️ N'oubliez pas de modifier votre session iris afin de vous assurer que vous disposez de la dernière version du code. Pour plus d'informations, consultez le premier article. Vous verrez le résultat:

message: Hello World

Voici une démonstration de l'importation d'un module Python personnalisé dans ObjectScript et de l'exécution de son code.

Avantages

  • Modularité : vous pouvez organiser votre code Python en modules, ce qui facilite sa gestion et sa maintenance.
  • Syntaxe Python : vous pouvez écrire du code Python en utilisant sa syntaxe et ses fonctionnalités.
  • Débogage : cette fonctionnalité n'est pas disponible pour le moment, mais dans le prochain article, nous verrons comment déboguer du code Python dans IRIS.

Inconvénients

  • Gestion des chemins : vous devez gérer le chemin d'accès à votre module Python. Pour plus d'informations, consultez l'article [https://community.intersystems.com/post/introduction-python-modules] sur sys.path.
  • Connaissances Python : vous devez toujours avoir une bonne compréhension de Python pour écrire et maintenir vos modules.
  • Connaissances ObjectScript : vous devez savoir comment utiliser ObjectScript pour importer et appeler vos modules Python.

Conclusion

En conclusion, l'importation de modules Python dans ObjectScript peut considérablement améliorer les capacités de votre application en tirant parti du vaste écosystème de bibliothèques Python. Cependant, il est essentiel de comprendre les compromis impliqués, tels que la nécessité d'une solide maîtrise de Python.

0
0 21
Article Iryna Mykhailova · Fév 3, 2025 2m read

Bonjour ! J'ai étendu mon référentiel de démonstration, andreas5588/demo-dbs-iris, pour faciliter le test des fonctionnalités FOREIGN SERVER et FOREIGN TABLE dans IRIS.

Pour y parvenir, j'ai créé un espace de noms appelé FEDERATION. L'idée est la suivante :

  1. Configurez des connexions JDBC pour chaque espace de noms.
  2. Créez un FOREIGN SERVER dans l'espace de noms FEDERATION pour chaque connexion.
  3. Définissez une FOREIGN TABLE au moins pour une table basée sur chaque serveur étranger.

Le script :  demo-dbs-iris/src/sql/02_create_foreign_server.sql

0
0 30
Article Lorenzo Scalese · Jan 6, 2025 6m read

Salut la Communauté,

image
Dans cet article, je présenterai mon application iris-HL7v2Gen.

IRIS-HL7v2Gen est une application CSP qui facilite la génération dynamique de messages de test HL7. Ce processus est essentiel pour tester, déboguer et intégrer les systèmes de données de soins de santé. L'application permet aux utilisateurs de générer une grande variété de types de messages HL7, de valider leur structure par rapport aux spécifications HL7, d'explorer la hiérarchie des messages et de transmettre les messages par TCP/IP aux systèmes de production. Ces fonctionnalités sont particulièrement utiles dans les contextes où la conformité aux normes HL7 est obligatoire pour assurer l'interopérabilité entre différents organismes ou systèmes de soins de santé.

Fonctionnalités de l'application

  • Génération Dynamique de Messages HL7: Création instantanée de messages HL7 pour une gamme de types de messages, facilitant ainsi les tests complets.
  • Exploration de la structure des messages: Visualisation de la structure des messages générés sur la base des spécifications HL7.
  • Visualisation des jeux de valeurs: Visualisation des jeux de valeurs codées prédéfinis pour des champs spécifiques.
  • Validation des messages: Validation des messages par rapport aux normes HL7 pour garantir la conformité.
  • Communication TCP/IP: Transmission facile de messages à la production à l'aide de paramètres TCP/IP.
  • Prise en charge d'un grand nombre de types de messages: Prise en charge de 184 types de messages HL7, garantissant la polyvalence pour les différents besoins d'intégration des soins de santé.
  • ClassMethod: Génération d'un message de test par l'invocation d'une méthode de classe
  • Version prise en charge: Actuellement, la version 2.5 de HL7 est prise en charge
0
0 55
InterSystems officiel Adeline Icard · Oct 3, 2024

Nous avons récemment mis à disposition une nouvelle version d'InterSystems IRIS dans le cadre du programme d'accès anticipé à la recherche vectorielle, comprenant un nouvel index Approximate Nearest Neighbor basé sur l'algorithme d'indexation Hierarchical Navigable Small World (HNSW). Cet ajout permet des recherches de voisins les plus proches très efficaces et approximatives sur de grands ensembles de données vectorielles, améliorant considérablement les performances et l'évolutivité des requêtes.

0
0 43
Article Robert Barbiaux · Sept 7, 2024 2m read

Introduction

Cet article décrit comment exécuter les tests unitaires lorsque la fonctionnalité intégrée à l'extension InterSystems ObjectScript n'est pas disponible car la version de IRIS utilisée est antérieure à 2023.3.

Au passage, l'extension ne semble pas supporter les tests pour les productions d'interopérabilité (classes qui étendent %UnitTest.TestProduction) , mais c'est seulement lié à la manière de déterminer si la classe comporte une méthode de test. Il suffit d'ajouter une méthode dont le nom commence par 'Test' pour que la classe soit reconnue par l'extension.

Préparation

0
0 98
Article Guillaume Rongier · Sept 29, 2023 11m read

Parfois, nous devons savoir avec certitude si l'environnement actuel dispose de suffisamment de noyaux, de mémoire et de bande passante pour prendre en charge le nombre prévu d'utilisateurs et les accords de niveau de service tels que la latence, le temps de réponse et la disponibilité. C'est le cas pour les bases de données et les backends. C'est pourquoi il est obligatoire, pour les applications et les bases de données critiques, de simuler les demandes simultanées/concurrentes des utilisateurs et de collecter des données sur les performances et la disponibilité.

L'outil le plus populaire pour effectuer des tests de charge et de performance s'appelle JMeter. Il s'agit d'un outil open-source permettant de définir des requêtes de base de données et de backend avec plusieurs fils d'exécution (un par utilisateur) et de collecter des mesures telles que la latence et le temps de réponse afin de les afficher dans des rapports, des fichiers et des tableaux de bord HTML.

Qu'est-ce que JMeter ?

Selon son site web, l'application Apache JMeter™ est un logiciel open-source, une application 100 % pur Java conçue pour tester la charge et le comportement fonctionnel et mesurer la performance. Elle a été conçue à l'origine pour tester les applications Web, mais elle a ensuite été étendue à d'autres fonctions de test.
Apache JMeter peut être utilisé pour tester les performances des ressources statiques et dynamiques ainsi que des applications Web dynamiques. Il peut être utilisé pour simuler une charge importante sur un serveur, un groupe de serveurs, un réseau ou un objet afin de tester sa résistance ou d'analyser les performances globales sous différents types de charge. Les fonctionnalités d'Apache JMeter (celles qui sont en gras seront détaillées dans cet article) sont les suivantes :

  • Capacité à tester la charge et les performances de nombreux types d'applications/serveurs/protocoles différents :
    • Web - HTTP, HTTPS (Java, NodeJS, PHP, ASP.NET, …);
    • SOAP / Services REST;
    • FTP;
    • Base de données via JDBC;
    • LDAP;
    • Middleware orienté message (MOM) via JMS;
    • Messagerie - SMTP(S), POP3(S) et IMAP(S) ;
    • Commandes natives ou scripts shell ;
    • TCP;
    • Objets Java.
  • IDE test complet qui permet d'enregistrer rapidement des plans de test (à partir de navigateurs ou d'applications natives), de les construire et de les déboguer.
  • Le mode CLI (mode ligne de commande qui était auparavant appelé mode Non-GUI ou headless) pour les tests de charge à partir de n'importe quel système d'exploitation compatible avec Java (Linux, Windows, Mac OSX).
  • Un rapport HTML dynamique complet et prêt à être présenté.
  • Une corrélation facile grâce à la capacité d'extraire des données à partir des formats de réponse les plus populaires, HTML, JSON, XML, ou tout autre format textuel.
  • Le concept de multithreading permet l'échantillonnage simultané par de nombreux fils d'exécution et l'échantillonnage simultané de différentes fonctions par des groupes de fils d'exécution distincts**.
  • Mise en cache et analyse/lecture hors ligne des résultats des tests.

Téléchargement et configuration du Java

Si Java est installé, passez cette section, sinon, exécutez les actions suivantes :

  1. Téléchargez Java à partir de https://jdk.java.net/archive/ (choisissez n'importe quelle version de Java, de la 11 à la plus récente, en fonction des spécificités de votre système d'exploitation).
  2. Décompressez le fichier dans n'importe quel dossier de votre ordinateur.
  3. Mémorisez le nom du dossier d'installation pour l'utiliser ultérieurement.

Téléchargement et configuration de JMeter

Pour obtenir et installer JMeter, suivez les étapes ci-dessous :

  • Téléchargez JMeter à partir de https://jmeter.apache.org/download_jmeter.cgi (sélectionnez un fichier zip pour Windows et un fichier TGZ pour les autres systèmes d'exploitation).
  • Décompressez le fichier dans n'importe quel dossier de votre ordinateur.
  • Mémorisez l'emplacement du dossier d'installation car nous en aurons besoin par la suite.

Téléchargement et configuration de l'application IRIS à tester

Nous allons tester le modèle FHIR
(https://openexchange.intersystems.com/package/iris-fhir-template). Pour l'obtenir et l'installer, suivez les étapes ci-dessous :

  1. Si vous utilisez ZPM/IPM à partir de votre instance IRIS for Health actuelle, exécutez la commande à partir de la console IRIS :
     
zpm "install fhir-server"
  1. Si vous utilisez Docker (un docker desktop ou un serveur est nécessaire) :
    a. Exécutez clone/git pull du référentiel dans n'importe quel répertoire local :
     
git clone https://github.com/intersystems-community/iris-fhir-template.git

b. Ouvrez le terminal dans le répertoire suivant et exécutez :
 

docker-compose up -d

Tests manuels préalables

Avant de créer les tests automatisés, n'oubliez pas d'exécuter des tests manuels à l'aide de Postman ou d'un autre client HTTP pour vous assurer que l'API que vous souhaitez tester fonctionne. Appelez une méthode GET pour http://localhost:32783/fhir/r4/Patient/1 et observez les résultats comme illustré ci-dessous :  

 

Exécution de JMeter

Pour lancer JMeter, procédez comme suit :

  1. Modifiez le fichier jmeter.bat (pour Windows) ou jmeter.sh (pour les autres systèmes d'exploitation) et ajoutez le JAVA_HOME (dans mon cas C:\Java\jdk-20) après la ligne setlocal :

 

La ligne dont vous avez besoin est : établir "JAVA_HOME=<dossier où vous avez décompressé le Java>"

  1. Sauvegardez le fichier jmeter.bat (pour Windows) ou jmeter.sh (pour les autres systèmes d'exploitation) et exécutez-le.
  2. Dans quelques secondes, vous devriez voir l'interface graphique de JMeter :

 

Création d'un test de performance pour une API REST

To create a REST API performance test, proceed with the following steps:

  1. Sélectionnez le deuxième bouton ( templates) pour utiliser un modèle afin de tester les API REST :

 

  1. Sélectionnez la requête HTTP simple et cliquez sur le bouton Create (Créer) :

 

  1. Ne modifiez pas les valeurs par défaut et cliquez sur le bouton Create (Créer) :

 

  1. À ce stade, vous avez créé plusieurs éléments JMeter :

 

  1. Sélectionnez Variables définies par l'utilisateur et remplacez les variables par les valeurs indiquées ci-dessous :

 

  1. Choisissez la section Groupe de fils et modifiez le Nombre de fils (utilisateurs) de 1 à 5 :

 

  1. Accédez au gestionnaire d'en-têtes HTTP et créez le paramètre "Accept" avec la valeur */*:

 

  1. Une fois tous les paramètres configurés, prenez une minute pour sauvegarder notre plan de test. Cliquez sur le bouton Enregistrer () et sélectionnez le nom et le dossier de votre choix :

 
 
9. Sélectionnez à nouveau le Groupe de fils et cliquez sur le bouton Start () pour exécuter notre premier test :

 

  1. Vous pouvez consulter les résultats de chaque demande dans la section Voir l'arborescence des résultats > l'onglet des résultats de l'échantillonneur :

## 

Vérification des résultats du test dans un tableau de bord HTML

  1. Sélectionnez la section Voir l'arborescence des résultats et définissez un Nom de fichier arbitraire :

 

  1. Ensuite, cliquez sur le bouton Configurer et cochez toutes les options, à l'exception de Save as XML (Sauvegarder en tant que XML), puis cliquez sur le bouton Done (Terminé) :

 

  1. Sauvegardez le projet (button )
  2. Exécutez à nouveau le plan de test (bouton ) pour écrire les résultats dans le fichier CSV.
  3. Consultez le fichier CSV contenant les résultats du test :

 

  1. Les résultats CSV nous aideront à générer le tableau de bord HTML.
  2. Accédez à Outils > Générer un rapport HTML, définissez les valeurs telles que citées vers le bas, et cliquez sur le bouton Générer un rapport
  • Fichier de résultats (CSV ou JTL) : chemin d'accès au fichier CSV configuré ci-dessus.
  • user.properties file: <JMeter installation folder>\bin\user.properties
  • Répertoire de sortie : n'importe quel dossier

 

  1. Au bout d'un certain temps, nous devrions avoir un message de réussite (Rapport créé) :

 

  1. À ce stade, accédez au répertoire Output et ouvrez le fichier index.html avec votre navigateur web :

 

  1. Vous devriez obtenir le graphique circulaire récapitulatif de la demande et un menu latéral pour voir les résultats du test :

 

  1. Cliquez sur le menu Graphiques et analysez les graphiques de durée (Over Time), de débit (Throughput) et de temps de réponse (Response Times) :

 

  1. Augmentez le nombre de fils, exécutez à nouveau le plan de test, générez à nouveau le rapport HTML Dashboard (n'oubliez pas de supprimer d'abord le contenu du dossier de sortie) et vérifiez l'index.html pour voir les résultats avec 100 utilisateurs simultanés :

 
13. Répétez les étapes susmentionnées pour 10 000 utilisateurs simultanés ou tout autre nombre souhaité.

Test de votre base de données

Dans cette section, vous apprendrez à tester les performances et à charger les commandes SQL. Pour ce faire, exécutez les étapes indiquées ci-dessous :

  1. Téléchargez le pilote JDBC d'InterSystems IRIS à partir de l'URL suivante : https://raw.githubusercontent.com/intersystems-community/iris-driver-distribution/main/JDBC/JDK18/intersystems-jdbc-3.6.1.jar
  2. Copiez le fichier illustré dans le dossier d'installation de JMeter (dans mon cas, il s'agit de C:\NJMeter\Napache-jmeter-5.5\Nlib) :

 

  1. Lorsque vous avez terminé, fermez votre fenêtre JMeter, ouvrez-la à nouveau et exécutez le dossier d'installation de JMeter (<JMeter installation folder>\bin\jmeter.bat ou jmeter.sh).
  2. Vous verrez que JMeter est prêt pour un nouveau plan de test à ce stade :

 

  1. Sélectionnez le deuxième bouton ( templates) pour utiliser un modèle afin de tester les requêtes JDBC :

 

  1. Sélectionnez Test de charge JDBC (JDBC Load Test) et cliquez sur le bouton Créer (Create) :

 

  1. JMeter devrait créer les éléments de test tels que présentés dans l'image :

 

  1. Sélectionnez Configuration de la connexion JDBC et établissez les propriétés comme indiqué ci-dessous :
  • URL de la base de données : jdbc:IRIS://localhost:32782/FHIRSERVER
  • Classe du pilote JDBC (sélectionnez Editer (Edit) et mettez la valeur) : com.intersystems.jdbc.IRISDriver
  • Nom d'utilisateur : _SYSTEM
  • Mot de passe : SYS

 
 
9. Sélectionnez la requête JDBC (JDBC Request) et définissez les propriétés suivantes :

  • Type de requête : Déclaration de sélection
  • Requête : SELECT ID1, Key, MDVersion, VersionId, _id, _lastUpdated, _profile, _security, _source, _tag, active, address, addressCity, addressCountry,addressPostalcode, addressState, addressUse, birthdate, deathDate, deceased, email, family, gender, generalPractitioner, given, identifier, "language", link, name, organization, phone, phonetic, telecom FROM HSFHIR_X0001_S.Patient
  1. Sélectionnez Voir l'arborescence des résultats et définissez les propriétés suivantes :
  • Nom de fichier (tout nom de dossier et de fichier) : C:\JMeter\Test Plans\IrisForHealthSQL.csv

 

  1. Cliquez sur le bouton Configurer (Configure) et sélectionnez toutes les options à l'exception de Sauvegarder en tant que XML (Save as XML), puis cliquez sur le bouton Terminé (Done) :

 

  1. Il est temps d'immortaliser votre projet. Cliquez sur le bouton Sauvegarder (Save) () et stockez votre projet dans n'importe quel dossier et fichier :

 

  1. Sélectionnez le groupe de fils et modifiez le nombre de fils (utilisateurs) à 5 :

 

  1. Sauvegardez les modifications () et exécutez le test ().
  2. Accédez à Outils > Générer des rapports HTML :

 

  1. Établissez les valeurs documentées ci-dessous pour générer le rapport HTML :
  • Fichier de résultats (CSV ou JTL) : dossier et fichier pour les résultats CSV
  • Fichier user.properties : Dossier d'installation de JMeter : user.properties
  • Dossier de sortie : n'importe quel dossier

 

  1. Accédez au répertoire Output et ouvrez le fichier index.html :

 
18. Découvrez les résultats de votre test pour l'instruction SQL testée :
     

Nous pouvons inclure de nombreuses demandes d'API SQL ou REST dans les mêmes plans de test et écrire des scripts personnalisés pour effectuer des tests avancés. Pour en savoir plus, consultez la documentation de JMeter : https://jmeter.apache.org/usermanual/index.html.

Bonne lecture !

1
0 551
Article Iryna Mykhailova · Juil 17, 2024 4m read

Parmi les points problématiques de la maintenance des interfaces HL7 figure la nécessité d'effectuer un test de régression fiable lors du déploiement dans de nouveaux environnements et après les mises à jour. La classe %UnitTest permet de créer des tests unitaires et de les intégrer au code de l'interface. Les données de test peuvent également être enregistrées au sein de la classe de test unitaire, ce qui permet de réaliser rapidement et facilement des tests de fumée et des tests de régression.

##Ressources:

##Scénario: Une classe de test unitaire sera créée pour chaque flux entrant en fonction des exigences en matière de profilage, de routage et de mappage des données.

  • Exigences relatives aux échantillons:*

Functional Requirements

Pour tester chaque scénario, nous devons lancer un échantillon pour chaque type d'événement et confirmer les règles de routage. Il nous faut également confirmer les exigences spécifiques en matière de mappage et trouver des exemples de données pour chaque scénario.

##Production HL7

L'objectif de ce UnitTest-RuleSet est de tester un pipeline HL7 : Service métierRègles de routageTransformation.

Dans cet exemple créé pour la production illustrée ci-dessous, le flux de processus ADT passe par plusieurs sauts supplémentaires. Une classe étendant UnitTest.RuleSet.HL7 a été modifiée pour permettre de traiter des flux de processus plus complexes.

Flux de processus: FromHSROUTER.TESTAdtFromTESTAdt.ReorgSegmentProcessRouteTESTAdtTEST.ADTTransformHS.Gateway.HL7.InboundProcess

HL7 Production

##Création d'une Classe de Test Unitaire

Créez une classe TestAdtUnitTest qui étend UnitTest.RuleSet.HL7. (UnitTest.RuleSet.Example dans le référentiel UnitTest-RuleSet contient un exemple d'une telle classe.

*Remarque: Dans cet exemple, tout a été déplacé sous HS.Local dans HSCUSTOM pour faciliter les tests. *

Signature:Class HS.Local.EG.UnitTest.TestAdtUnitTest Extends HS.Local.Util.UnitTest.RuleSet.HL7

##Aperçu de la structure des classes

La classe de test unitaire est organisée de la manière suivante:

Unit Test Class Layout

##Paramètres de configuration

Ces paramètres de classe sont utilisés pour configurer les tests.

// Espace de noms où la production est située
Parameter Namespace = "EGTEST";

// Répertoire de base pour les tests unitaires

Parameter TestDirectory = "/tmp/unittest";

// nom du sous-répertoire pour les tests unitaires

Parameter TestSuite = "TEST-HL7ADT";

/// Remplacement pour un schéma différent
Parameter HL7Schema = "2.5.1:ADT_A01";

/// Remplacement par le nom du service existant en production
Parameter SourceBusinessServiceName = "FromHSROUTER.TESTAdt";

/// Remplacer par le nom du moteur de routage de processus métier existant en production
Parameter TargetConfigName = "FromTESTAdt.ReorgSegmentProcess";

/// Nom du processus de routage primaire en production
Parameter PrimaryRoutingProcessName = "RouteTESTAdt";

/// Nom du processus de routage secondaire en production
Parameter SecondaryRoutingProcessName = "TEST.ADTTransform";

Remarque: Les processus de routage primaire et secondaire sont référencés dans les différentes méthodes de test afin de tester la sortie et les résultats de deux processus de routage enchaînés.

##Création d'exemples de messages d'entrée

Pour créer une classe de test unitaire réutilisable, il faut enregistrer des échantillons anonymes pour chaque type d'événement et tout message supplémentaire nécessaire à la réalisation des scénarios de test dans la classe de test unitaire, dans des blocs XDATA au bas du fichier.

  • Exemple du bloc XDATA:*

Le nom *SourceMessageA01 * est utilisé pour référencer le bloc XDATA spécifique.

XData SourceMessageA01 { <![CDATA[ MSH|^~\&|Epic|TEST||TEST|20230911060119|RUBBLE|ADT^A01|249509431|P|2.2 EVN|A01|20230911060119||ADT_EVENT|RUBBLE^MATTHEW^BARNEY^D^^^^^TEST^^^^^UH|20230911060000|PHL^PHL^ADTEDI ZVN||LS23450^LS-23450^^^^RTL PID|1|000163387^^^MRN^MRN|000163387^^^MRN^MRN||FLINTSTONE^ANNA^WILMA||19690812|F|MURRAY^ANNA~FLINTSTONE^ANNA^R~FLINTSTONE^ANNA|B|100 BEDROCK WAY^^NORTH CHARLESTON^SC^29420-8707^US^P^^CHARLESTON|CHAR|(555)609-0969^P^PH^^^555^6090969~^NET^Internet^ANNAC1@YAHOO.COM~(555)609-0969^P^CP^^^555^6090969||ENG|M|CHR|1197112023|260-61-5801|||1|||||Non Veteran|||N ZPD||MYCH||AC|||N||N PD1|||TEST HOLLINGS CANCER CENTER^^10003|1134107873^LINK^MICHAEL^J^^^^^EPIC^^^^PNPI ROL|1|UP|GENERAL|1134107873^LINK^MICHAEL^J^^^^^EPIC^^^^PNPI|20211115 NK1|1|GABLE^BETTY|PARENT||(555)763-5651^^PH^^^555^7635651||Emergency Contact 1 NK1|2|FLINTSTONE^FRED|Spouse|100 Bedrock way^^REMBERT^SC^29128^US|(888)222-2222^^PH^^^888^2222222|(888)222-3333^^PH^^^888^2223333|Emergency Contact 2 PV1|1|O|R1OR^RTOR^07^RT^R^^^^TEST RT OR|EL|||1386757342^HALSTEAD^LUCINDA^A.^^^^^EPIC^^^^PNPI|1386757342^HALSTEAD^LUCINDA^A.^^^^^EPIC^^^^PNPI||OTO||||PHYS|||1386757342^HALSTEAD^LUCINDA^A.^^^^^EPIC^^^^PNPI|SO||BCBS|||||||||||||||||||||ADMCONF|||20230911060000 PV2||PRV||||||20230911||||HOSP ENC|||||||||N|N||||||||||N ZPV||||||||||||20230911060000 OBX|1|NM|PRIMARYCSN|1|1197112023||||||F AL1|1|DA|900525^FISH CONTAINING PRODUCTS^DAM|3|Anaphylaxis|20210823 AL1|2|DA|568^PEANUT^HIC|3|Anaphylaxis|20221209 AL1|3|DA|12753^TREE NUT^HIC|3|Anaphylaxis|20221209 AL1|4|DA|1193^TREE NUTS^DAM|3|Anaphylaxis|20130524 AL1|5|DA|1554^HYDROCODONE^HIC||Other|20210728 AL1|6|DA|3102^POLLEN EXTRACTS^HIC||Other|20201204 AL1|7|DA|11754^SHELLFISH DERIVED^HIC||Other|20210728 DG1|1|I10|Q85.02^Neurofibromatosis, type 2^I10|Neurofibromatosis, type 2||ADMISSION DIAGNOSIS (CODED) DG1|2|I10|D33.3^Benign neoplasm of cranial nerves^I10|Benign neoplasm of cranial nerves||ADMISSION DIAGNOSIS (CODED) DG1|3|I10|J38.01^Paralysis of vocal cords and larynx, unilateral^I10|Paralysis of vocal cords and larynx, unilateral||ADMISSION DIAGNOSIS (CODED) DG1|4||^NF2 (neurofibromatosis 2) [Q85.02]|NF2 (neurofibromatosis 2) [Q85.02]||ADMISSION DIAGNOSIS (TEXT) DG1|5||^Acoustic neuroma [D33.3]|Acoustic neuroma [D33.3]||ADMISSION DIAGNOSIS (TEXT) DG1|6||^Unilateral complete paralysis of vocal cord [J38.01]|Unilateral complete paralysis of vocal cord [J38.01]||ADMISSION DIAGNOSIS (TEXT) GT1|1|780223|FLINTSTONE^ANNA^WILMA^^^^L||100 BEDROCK WAY^^NORTH CHARLESTON^SC^29420-8707^US^^^CHARLESTON|(555)609-0969^P^PH^^^555^6090969~(555)763-5651^P^CP^^^555^7635651||19690812|F|P/F|SL|248-61-5801|||||^^^^^US|||Full ZG1||||1 IN1|1|BL90^BCBS/STATE EMP^PLANID||BCBS STATE|ATTN CLAIMS PROCESSING^PO BOX 100605^COLUMBIA^SC^29260-0605||(800)444-4311^^^^^800^4444311|002038404||||20140101||NPR||FLINTSTONE^THOMAS^^V|Sp|19661227|3310 DUBIN RD^^NORTH CHARLESTON^SC^29420^US|||1|||||||||||||1087807|ZCS49984141|||||||M|^^^^^US|||BOTH IN3|1|||2||20230911|20230911|RUBBLE^MATTHEW^BARNEY^D|||NOT|||||(800)999-0000^^^^^800^9990000~(888)444-5555^^^^^888^4445555 ZIN|||||||FLINTSTONE^THOMAS^^V|||||16871492 ]]> }

Exemple de code permettant d'extraire des données d'un bloc XDATA pour les utiliser dans le cadre de tests :

set xdata=##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||"_XDataName,0)

Voici un exemple de code de ** GetMessage**, une méthode d'assistance utilisée pour lire et renvoyer les données d'un bloc XDATA en fonction du nom du bloc.

ClassMethod GetMessage(XDataName As %String) As EnsLib.HL7.Message { #dim SourceMessage as EnsLib.HL7.Message set xdata=##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||"XDataName,0) quit:'$IsObject(xdata) $$$NULLOREF set lines="" while 'xdata.Data.AtEnd { set line=$ZSTRIP(xdata.Data.ReadLine(),"<w") continue:line="" continue:$Extract(line,1)="<" // ignorer les balises XML ouvrantes ou fermantes et commencer la balise CData continue:$Extract(line,1)="]" // ignorer ]]> closing CDATA set lines=lines($S($L(lines)=0:"",1:$C(..#NewLine)))_line } set SourceMessage=##class(EnsLib.HL7.Message).ImportFromString(lines,.tSC) quit:$$$ISERR(tSC) $$$NULLOREF set SourceMessage.DocType=..#HL7Schema set tSC=SourceMessage.PokeDocType(..#HL7Schema) quit SourceMessage }

##Création de méthodes de test

La classe de test unitaire contient également une méthode de test qui configure chaque test de manière programmatique, injecte le message dans la production et vérifie que le message transformé qui en résulte correspond à ce qui est attendu.

L'exemple de test contient les méthodes de test suivantes :

  • TestMessageA01
  • TestMessageA02
  • TestMessageA03
  • TestMessageA04
  • TestMessageA05
  • TestMessageA06
  • TestMessageA08
  • TestMessageA28
  • TestMessageA31
  • TestCorrectAssigningAuthorityForA01
  • TestEncoutnerNumberPresent
  • TestPD1LocationMapped

Exemple de méthode: TestMessageA01 Méthode permettant de vérifier que les messages A01 sont traités sans erreur et routés vers la transformation correcte.

Method TestMessageA01() { Set ReturnA01 = ..#SecondaryRoutingProcessName_":HS.Local.EG.ProfSvcs.Router.Base.ADT.TransformA01" #dim message as EnsLib.HL7.Message

    // Télécharger un nouveau message HL7 par UnitTest
set ..HL7Message=..GetMessage("SourceMessageA01")
quit:'$IsObject(..HL7Message) $$$ERROR(5001,"Failed to correlate Xdata for Source Message")

set routingProcess = ..#PrimaryRoutingProcessName

set message=..HL7Message.%ConstructClone(1)
do message.PokeDocType(message.DocType)
do message.SetValueAt("SYSA","MSH:3.1")
set expectSuccess=1
set expectReturn="send:"_ReturnA01
set expectReason="rule#8"
do ..SendMessageToRouter(message,"TestMessageA01",routingProcess, expectSuccess, expectReturn, expectReason)

}

Remarque: SendMessageToRouter() est une méthode mise en œuvre dans le paquet UnitTest-RuleSet.

Exemple de méthode: TestEncounterNumberPresent

La méthode ci-dessous vérifie la présence de valeurs spécifiques dans le message après la transformation.

Method TestEncounterNumberPresent()
{
	#dim message as EnsLib.HL7.Message

    // Télécharger un nouveau message HL7 par UnitTest
set ..HL7Message=..GetMessage("SourceMessageA01")
quit:'$IsObject(..HL7Message) $$$ERROR(5001,"Failed to correlate Xdata for Source Message")

// source du processus de transformation
set routingProcess = ..#SecondaryRoutingProcessName
set message=..HL7Message.%ConstructClone(1)
do message.PokeDocType(message.DocType)
do message.SetValueAt("SYSA","MSH:3.1")
// Vérifier que la sortie PV1:19 n'est pas vide
set expectSuccess=1
set expectElement="PV1:19"
set expectReturnVal="1197112023"
do ..SendMessageReturnOutput(message,"TestMessageA01", routingProcess, expectSuccess, expectElement, expectReturnVal)
 }

Remarque: SendMessageReturnOutput() est une méthode modifiée pour renvoyer le message résultatif pour évaluation.

##Comment exécuter le UnitTest

Les mises en œuvre de la classe %UnitTest dépend de la globale ^UnitTestRoot pour l'emplacement des dossiers de travail des tests unitaires.

Il y a deux paramètres utilisés pour définir les dossiers:

 // Répertoire de base pour les tests unitaires
 Parameter TestDirectory = "/tmp/unittest";

 // nom du sous-répertoire pour les tests unitaires
 Parameter TestSuite = "TEST-HL7ADT";

Dans ce cas, la classe s'attend à ce que le dossier : /tmp/unittest/TEST-HL7ADT soit présent dans l'environnement où la classe de test unitaire est exécutée.

En outre, la mise en œuvre personnalisée définit la globale ^UnitTestRoot et passe à l'espace de noms spécifique dans le paramètre d'espace de noms Namespace.

Pour le lancement depuis le terminal:

HSCUSTOM> do ##class(HS.Local.EG.UnitTest.TestAdtUnitTest).Debug()

Les résultats du lancement seront envoyés à la console. Si l'un des tests échoue, tout le test est considéré comme ayant échoué. Outre le terminal, on peut également consulter les résultats à partir du portail de gestion. Pour ouvrir l'URL, utilisez le lien figurant dans le résultat.

##Extrait du résultat de la Console

TestMessageA31 passed TestPD1LocationMapped() begins ... 14:50:20.104:...t.TestAdtUnitTest: TestPD1Location Sent LogMessage:SessionId was 130 LogMessage:Source Config Name name is :TEST.ADTTransform LogMessage:Message Body Id was 94 LogMessage:Expect Success is 0 LogMessage:Testing for value PV1:39 LogMessage:Found value AssertNotEquals:Expect No Match:TestPD1Location:ReturnValue= (failed) <<==== FAILED TEST-HL7ADT:HS.Local.EG.UnitTest.TestAdtUnitTest:TestPD1LocationMapped LogMessage:Duration of execution: .033106 sec. TestPD1LocationMapped failed HS.Local.EG.UnitTest.TestAdtUnitTest failed Skipping deleting classes TEST-HL7ADT failed

Use the following URL to view the result: http://192.168.1.229:52773/csp/sys/%25UnitTest.Portal.Indices.cls?Index=3&$NAMESPACE=EGTEST Some tests FAILED in suites: TEST-HL7ADT

##Affichage des résultats dans le portail de gestion

Utilisez l'URL affichée sur le terminal et copiez-la dans un navigateur pour voir les résultats. En cliquant sur le nom du test, vous obtiendrez des détails supplémentaires.

Unit Test in Management Portal

En cliquant sur le lien de chaque test, vous obtiendrez plus d'informations sur le test :

UnitTest Results

##Conclusion La création de classes de tests unitaires pour chaque interface HL7 et leur intégration dans le code de l'interface permettent de documenter le processus de test et les échantillons de données, ainsi que de tester rapidement la régression de toute mise à jour ou modification des données ou de leur mise en œuvre.

Lors du déploiement, la classe de test unitaire sert de test de fumée autonome pour confirmer que l'interface est complète et fonctionnelle.

##Considérations supplémentaires

  • Le cadre UnitTest_RuleSet peut être adapté aux tests unitaires du CCDA. Les tests de routage sont relativement semblables à l'exemple HL7. Pour répondre aux exigences de mappage, il faut mettre en œuvre la classe de test unitaire pour gérer XSLT et XPaths.

  • Les responsables de l'assurance qualité peuvent recueillir des exigences et des échantillons de données pour configurer la classe de test unitaire avant le début de la mise en œuvre.

  • Les tests unitaires pour toutes les interfaces peuvent être regroupés dans le gestionnaire de tests unitaires (Unit Test Manager), de sorte que l'ensemble des tests peut être exécuté en une seule fois pour valider la configuration et le code existants.

0
0 50
Article Guillaume Rongier · Mai 22, 2024 6m read

Il me semble que c'était hier, lorsque nous avons réalisé un petit projet en Java pour tester les performances d'IRIS, PostgreSQL et MySQL (vous pouvez consulter l'article que nous avons écrit en juin à la fin de cet article). Si vous vous souvenez bien, IRIS était supérieur à PostgreSQL et dépassait considérablement MySQL en termes d'insertions, sans grande différence au niveau des requêtes.

Peu de temps après , @Dmitry Maslennikov m'a demandé "Pourquoi ne le testez-vous pas à partir d'un projet Python ?". Voici donc la version Python des tests que nous avons effectués précédemment en utilisant les connexions JDBC.

Tout d'abord, sachez que je ne suis pas un expert en Python, donc si vous remarquez des choses qui pourraient être améliorées, n'hésitez pas à me contacter.

Pour cet exemple, j'ai utilisé Jupyter Notebook, qui simplifie considérablement le développement en Python et nous permet de voir étape par étape ce que nous faisons. En annexe de cet article vous trouverez l'application qui vous permettra d'effectuer vos propres tests.

Avertissement pour les utilisateurs de Windows

Si vous clonez le projet GitHub dans Visual Studio Code, pour déployer correctement les conteneurs, il vous faudra peut-être changer la configuration de fin de ligne par défaut de CRLF à LF:

Si vous souhaitez reproduire les tests sur vos ordinateurs, vous devez tenir compte des éléments suivants : Docker Desktop demandera des autorisations d'accès aux dossiers de vos ordinateurs dont il a besoin pour déployer le projet. Si vous n'avez pas configuré l'autorisation d'accès à ces dossiers avant de lancer les conteneurs Docker, la création initiale de la table de test dans PostgreSQL échouera, donc avant de lancer le projet, vous devez configurer l'accès partagé aux dossiers du projet dans votre DockerDesktop.

Pour ce faire, vous devez accéder à Paramètres -> Ressources -> Partage de fichiers ("Settings -> Resources -> File sharing") et ajouter à la liste le dossier dans lequel vous avez cloné le projet:

Vous êtes prévenus!

 

Test de performance

Pour ces tests, nous utiliserons une table relativement simple contenant les informations les plus basiques possibles sur un patient. Ici vous pouvez voir la commande pour créer la table en SQL:

CREATETABLE Test.Patient (
    NameVARCHAR(225),
    Lastname VARCHAR(225),
    Photo VARCHAR(5000),
    Phone VARCHAR(14),
    Address VARCHAR(225)    
)

Comme vous pouvez le voir, nous avons défini la photo du patient comme un VARCHAR(5000), puisque nous allons inclure (théoriquement) les informations vectorisées de la photo. Il y a quelques mois, j'ai publié un article expliquant comment utiliser Embedded Python pour implémenter IRIS, un système de reconnaissance faciale (ici) où vous pouvez voir comment les images sont transformées en vecteurs pour une comparaison ultérieure. La question de la vectorisation vient du fait que ce format vectoriel est la norme dans de nombreux modèles d'apprentissage automatique et qu'il est toujours utile de tester avec quelque chose de similaire à la réalité (juste quelque chose)./p>

 

Paramètres de Jupyter Notebook

Pour simplifier au maximum le développement du projet en Python, j'ai utilisé un outil magnifique, le Jupyter Notebook, qui permet de développer pas à pas chacune des fonctionnalités dont nous aurons besoin.

Voici un aperçu de notre Jupyter:

Jetons un coup d'œil aux points les plus intéressants de celui-ci:

Importation de bibliothèques:

import iris
import names
import numpy as np
from datetime import datetime
import psycopg2
import mysql.connector
import matplotlib.pyplot as plt
import random_address
from phone_gen import PhoneNumber

Connexion aux bases de données:

IRIS:

connection_string = "iris:1972/TEST"
username = "superuser"
password = "SYS"
connectionIRIS = iris.connect(connection_string, username, password)
cursorIRIS = connectionIRIS.cursor()
print("Connected")

PostgreSQL:

connectionPostgres = psycopg2.connect(database="testuser",
                        host="postgres",
                        user="testuser",
                        password="testpassword",
                        port="5432")
cursorPostgres = connectionPostgres.cursor()
print("Connected")

MySQL:

connectionMySQL = mysql.connector.connect(
  host="mysql",
  user="testuser",
  password="testpassword"
)
cursorMySQL = connectionMySQL.cursor()
print("Connected")

Génération des valeurs à insérer

phone_number = PhoneNumber("USA")
resultsIRIS = []
resultsPostgres = []
resultsMySQL = []
parameters =  []
for x in range(1000):
    rng = np.random.default_rng()
    parameter = []
    parameter.append(names.get_first_name())
    parameter.append(names.get_last_name())
    parameter.append(str(rng.standard_normal(50)))
    parameter.append(phone_number.get_number())
    parameter.append(random_address.real_random_address_by_state('CA')['address1'])
    parameters.append(parameter)

print("Parameters built")

Insertion dans IRIS

date_before = datetime.now()

cursorIRIS.executemany("INSERT INTO Test.Patient (Name, Lastname, Photo, Phone, Address) VALUES (?, ?, ?, ?, ?)", parameters) connectionIRIS.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsIRIS.append(difference.total_seconds())

Insertion dans PostgreSQL

date_before = datetime.now()

cursorPostgres.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters) connectionPostgres.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsPostgres.append(difference.total_seconds())

Insertion dans MySQL

date_before = datetime.now()

cursorMySQL.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters) connectionMySQL.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsMySQL.append(difference.total_seconds())

Pour notre test, j'ai décidé d'insérer les valeurs suivantes dans chaque base de données:

  • 1 insertion de 1000 patients.
  • 1 insertion de 5000 patients.
  • 1 insertion de 20 000 patients.
  • 1 insertion de 50 000 patients.

Gardez à l'esprit, lors de l'exécution des tests, que le processus le plus long est la création des valeurs à insérer par Python. Pour se rapprocher de la réalité, j'ai lancé plusieurs tests à l'avance afin que les bases de données disposent déjà d'un ensemble significatif d'enregistrements (environ 200 000 enregistrements)./p>

Résultats des tests

Insertion de 1000 patients:

  • InterSystems IRIS: 0,037949 seconde.
  • PostgreSQL: 0,106508 seconde.
  • MySQL: 0,053338 seconde.

Insertion de 5,000 patients:

  • InterSystems IRIS: 0,162791 seconde.
  • PostgreSQL: 0,432642 seconde.
  • MySQL: 0,18925 seconde.

Insertion de 20,000 patients:

  • InterSystems IRIS: 0,601944 seconde.
  • PostgreSQL: 1,803113 seconde.
  • MySQL: 0,594396 seconde.

Insertion de 50,000 patients:

  • InterSystems IRIS: 1.482824 seconde.
  • PostgreSQL: 4,581251 secondes.
  • MySQL: 2,162996 secondes.

Bien qu'il s'agisse d'un test assez simple, il est très significatif car il nous permet de voir la tendance de chaque base de données en ce qui concerne les performances d'insertion.

Conclusions

Si nous comparons les performances des tests effectués avec le projet Java et le projet actuel en Python, nous constatons qu'à cette occasion, PostgreSQL est clairement inférieur au projet Python, étant 4 fois plus lent qu'InterSystems IRIS, tandis que MySQL s'est amélioré par rapport à la version Java.

InterSystems IRIS reste incontestablement le meilleur des trois, présentant un comportement plus linéaire et une meilleure performance d'insertion, quelle que soit la technologie utilisée.

Caractéristiques techniques de l'ordinateur portable utilisé pour les tests:

  • Système d'exploitation: Microsoft Windows 11 Pro.
  • Processeur: 13th Gen Intel(R) Core(TM) i9-13900H, 2600 Mhz.
  • Mémoire RAM: 64 Go.
0
0 79
Article Guillaume Rongier · Mai 20, 2024 11m read

En tant qu'ancien développeur de JAVA, j'ai toujours eu du mal à décider quelle base de données était la plus appropriée pour le projet à développer. L'un des principaux critères que j'utilisais était la performance, ainsi que les capacités de configuration de HA (haute disponibilité). Eh bien, il est maintenant temps de mettre IRIS à l'épreuve en ce qui concerne certaines bases de données les plus couramment utilisées, j'ai donc décidé de créer un petit projet Java basé sur SpringBoot qui se connecte via JDBC avec une base de données MySQL, avec une autre base de données PostgreSQL et enfin avec une base de données IRIS.

Nous allons profiter du fait de disposer d'images Docker de ces bases de données pour les utiliser dans notre projet et vous permettre de l'essayer vous-même sans avoir à procéder à une quelconque installation. Nous pouvons vérifier la configuration du docker dans notre fichier docker-compose.yml

version:"2.2"services:# mysql  mysql:    build:      context:mysql    container_name:mysql    restart:always    command:--default-authentication-plugin=mysql_native_password    environment:      MYSQL_ROOT_PASSWORD:SYS      MYSQL_USER:testuser      MYSQL_PASSWORD:testpassword      MYSQL_DATABASE:test    volumes:    -./mysql/sql/dump.sql:/docker-entrypoint-initdb.d/dump.sql    ports:      -3306:3306# postgres  postgres:    build:      context:postgres    container_name:postgres    restart:always    environment:      POSTGRES_USER:testuser      POSTGRES_PASSWORD:testpassword    volumes:    -./postgres/sql/dump.sql:/docker-entrypoint-initdb.d/dump.sql    ports:      -5432:5432  adminer:    container_name:adminer    image:adminer    restart:always    depends_on:      -mysql      -postgres    ports:      -8081:8080# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52773:52773      -1972:1972    command:--check-capsfalse# tomcat  tomcat:    init:true    container_name:tomcat    build:      context:.      dockerfile:tomcat/Dockerfile    volumes:      -./tomcat/performance.war:/usr/local/tomcat/webapps/performance.war    ports:      -8080:8080

D'un coup d'œil rapide, nous constatons que nous utilisons les images suivantes :

  • IRIS: Instance de la Communauté IRIS à laquelle nous nous connecterons par JDBC.
  • Postgres: Image de base de données PostgreSQL sur le port 5432.
  • MySQL: Image de base de données MySQL sur le port 3306
  • Tomcat: Image Docker configurée avec un serveur d'application Apache Tomcat sur lequel nous allons déployer le fichier WAR de notre application.
  • Adminer: Administrateur de base de données qui nous permettra de consulter les bases de données Postgres et MySQL.

Comme vous pouvez le voir, nous avons configuré les ports de listening de manière à ce qu'ils soient également mappés sur notre ordinateur, et pas seulement au sein de Docker. Pour les bases de données, ce n'est pas nécessaire, car la connexion se fait dans les conteneurs Docker, donc si vous avez des problèmes avec les ports, vous pouvez supprimer la ligne de prots de votre fichier docker-compose.yml.

Chaque image de base de données exécute un pré-script qui créera les tables nécessaires aux tests de performance. Examinons l'un des fichiers dump.sql

CREATESCHEMAtest;

DROPTABLEIFEXISTS test.patient;

CREATETABLE test.country ( idINT PRIMARY KEY, nameVARCHAR(225) );

CREATETABLE test.city ( idINT PRIMARY KEY, nameVARCHAR(225), lastname VARCHAR(225), photo BYTEA, phone VARCHAR(14), address VARCHAR(225), country INT, CONSTRAINT fk_country FOREIGN KEY(country) REFERENCES test.country(id) );

CREATETABLE test.patient ( idINTGENERATEDBYDEFAULTASIDENTITY PRIMARY KEY, nameVARCHAR(225), lastname VARCHAR(225), photo BYTEA, phone VARCHAR(14), address VARCHAR(225), city INT, CONSTRAINT fk_city FOREIGN KEY(city) REFERENCES test.city(id) );

INSERTINTO test.country VALUES (1,'Spain'), (2,'France'), (3,'Portugal'), (4,'Germany');

INSERTINTO test.city VALUES (1,'Madrid',1), (2,'Valencia',1), (3,'Paris',2), (4,'Bordeaux',2), (5,'Lisbon',3), (6,'Porto',3), (7,'Berlin',4), (8,'Frankfurt',4);

Nous allons créer 3 tables pour nos tests: patient, ville et pqys, ces deux dernières vont avoir des données préchargées de villes et de pays.

Parfait, maintenant nous allons étudier comment établir les connexions avec la base de données.

Pour ce faire, nous avons créé notre projet Java à partir d'un projet Spring Boot préconfiguré disponible dans Visual Studio Code qui nous fournit la structure de base.

Ne vous inquiétez pas si vous ne comprenez pas immédiatement la structure du projet, le but n'est pas d'apprendre Java, mais nous allons tout de même expliquer un peu plus en détail les documents principaux.

MyDataSourceFactory.java

Cette classe Java permet d'ouvrir les connexions aux différentes bases de données.

PerformancerController.java

Contrôleur chargé de publier les endpoints que nous appellerons depuis Postman.

application.properties

Fichier de configuration avec les différentes connexions aux bases de données déployées dans notre Docker.

Comme vous pouvez le constater, les URL de connexion utilisent le nom du conteneur puisque, lorsqu'elles sont déployées dans un conteneur Tomcat, les bases de données ne seront accessibles par notre application Java qu'avec le nom du conteneur correspondant. Nous pouvons également vérifier la manière dont l'URL établit une connexion via JDBC à nos bases de données. Les bibliothèques Java utilisées dans le projet sont définies dans le fichier pom.xml.

Si vous modifiez le code source, il vous suffit d'exécuter la commande suivante :

mvn package

Cela va générer un fichier performance-0.0.1-SNAPSHOT.war, renommez-le en performance.war et déplacez-le dans le répertoire /tomcat, en remplaçant le fichier existant.

Puisque le projet est sur GitHub, il suffit de le cloner sur notre ordinateur depuis Visual Studio et d'exécuter les commandes suivantes dans le terminal:

docker-compose build
docker-compose up -d

Vérifions le portail Docker:

Génial! Les conteneurs Docker fonctionnent. Vérifions maintenant depuis notre Adminer et le portail de gestion IRIS que nos tables sont correctement créées.

 Accédons d'abord à la base de données MySQL. Si vous consultez le fichier docker-compose.yml vous verrez que le nom d'utilisateur et le mot de passe définis pour MySQL et PostgreSQL sont les mêmes: testuser/testpassword

Les trois tables se trouvent dans notre base de données Test, regardons notre base de données PostgreSQL:

Sélectionnons la base de données testuser et le schéma test:

Nous avons ici nos tables parfaitement créées dans PostgreSQL. Vérifions enfin si tout est bien configuré dans IRIS:

Tout est correct, nous avons créé nos tables dans l'espace de noms USER Namespace sous le schéma Test.

Très bien, une fois les vérifications effectuées, c'est parti ! Pour ce faire, nous utiliserons Postman, dans lequel nous chargerons le fichier attaché au projet: performance.postman_collection.json

Voici les différents tests que nous allons lancer, nous commencerons par des insertions et nous continuerons par des requêtes sur la base de données. Je n'ai inclus aucun type d'index autre que ceux qui sont créés automatiquement avec la définition des clés primaires dans les différentes bases de données.

Insertion

Appel REST: GET http://localhost:8080/performance/tests/insert/{database}?total=1000

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

Et l'attribut total sera celui que nous modifierons pour indiquer le nombre total d'insertions que nous voulons faire.

La méthode qui sera invoquée s'appelle insertRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, vous pouvez voir qu'il s'agit d'une insertion extrêmement simple:

INSERTINTO test.patient VALUES (null, ?, ?, null, ?, ?, ?)

La première valeur est nulle car il s'agit de la clé primaire autogénérée et la deuxième valeur nulle correspond à un champ de type BLOB/BYTEA/LONGVARBINARY dans lequel nous enregistrerons une photo ultérieurement.

Nous allons lancer les lots de commandes type push suivants : 100, 1000, 10000, 20000 et nous allons vérifier les temps de réponse que nous recevons dans Postman. Pour chaque mesure, nous effectuerons 3 tests et nous calculerons la moyenne des 3 valeurs obtenues.

 10010001000020000
MySQL0.7548.91 s88 s192 s
PostgreSQL0.23 s2.24 s20.92 s40.35 s
IRIS0.07 s0.33 s2.6 s5 s

Représentons-le graphiquement.

Insertion avec un fichier binaire

Dans l'exemple précédent, nous avons fait des insertions simples, allons donc accélérer les choses en incluant dans notre insertion une image de 50 kB qui servira de photo pour nos patients.

Appel REST: GET http://localhost:8080/performance/tests/insertBlob/{database}?total=1000

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

Et l'attribut total sera celui que nous modifierons pour indiquer le nombre total d'insertions que nous voulons faire.

La méthode qui sera invoquée s'appelle insertBlobRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, vous pouvez vérifier qu'il s'agit d'une insertion similaire à la précédente à l'exception du fait d'introduire le fichier dans l'insertion:

INSERTINTO test.patient (Name, Lastname, Photo, Phone, Address, City) VALUES (?, ?, ?, ?, ?, ?)

Modifions un peu le nombre d'insertions ci-dessus pour éviter que le test ne prenne une éternité, puis nettoyons le Docker des images pour recommencer à zéro avec une égalité totale des chances.

 1001000500010000
MySQL1.87 s17 s149 s234 s
PostgreSQL0.6 s5.22 s23.93 s60.43 s
IRIS0.13 s0.88 s4.58 s12.57 s

Examinons le graphe:

 

Selection

Testons les performances avec une simple requête qui récupère tous les enregistrements de la table Patient.

Appel REST: GET http://localhost:8080/performance/tests/select/{database}

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle selectRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, la requête est extrêmement simple:

SELECT * FROM test.patient

Nous allons tester la requête avec le même ensemble d'éléments que nous avons utilisé pour notre premier test d'insertion.

 10010001000020000
MySQL0.03 s0,02 s0.03 s0.04 s
PostgreSQL0.03 s0.02 s0.04 s0.03 s
IRIS0.02 s0.02 s0.04 s0.05 s

Et graphiquement:

Sélection d'un groupe par

Testons les performances avec une requête incluant une jointure gauche ainsi que des fonctions d'agrégation. 

Appel REST: GET http://localhost:8080/performance/tests/selectGroupBy/{database}

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle selectGroupBy et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, examinons donc la requête:

SELECTcount(p.Name), c.Name FROM test.patient p leftjoin test.city c on p.City = c.Id GROUPBY c.Name

Nous allons tester la requête encore une fois avec le même ensemble d'éléments que nous avons utilisé pour notre premier test d'insertion.

 10010001000020000
MySQL0.02 s0.02 s0.03 s0.03 s
PostgreSQL0.02 s0.02 s0.02 s0.02 s
IRIS0.02 s0.020.03 s0.04 s

Et graphiquement:

Mise à jour

Pour la mise à jour, nous allons lancer une requête associée à une sous-requête dans le cadre de ses conditions.

Appel REST: GET http://localhost:8080/performance/tests/update/{database}

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle UpdateRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, examinons donc la requête:

UPDATE test.patient SET Phone = '+15553535301'WHERENamein (SELECTNameFROM test.patient whereNamelike'%12')

Lançons la requête et examinons les résultats.

 10010001000020000
MySQLXXXX
PostgreSQL0.02 s0.02 s0.02 s0.03 s
IRIS0.02 s0.02 s0.02 s0.04 s

Nous constatons que MySQL ne permet pas ce type de sous-requêtes dans la même table que celle que nous allons mettre à jour, de sorte que nous ne pouvons pas mesurer leurs durées dans des conditions égales. Ici, nous n'utiliserons pas le graphe, car il est très simple.

Suppression

Pour la suppression, nous allons lancer une requête associée à une sous-requête dans le cadre de ses conditions.

Appel REST: GET http://localhost:8080/performance/tests/delete/{database}

The variable {database} may have the following values:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle DeleteRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, examinons donc la requête:

DELETE test.patient WHERENamein (SELECTNameFROM test.patient whereNamelike'%12')

Lançons la requête et examinons les résultats.

 10010001000020000
MySQLXXXX
PostgreSQL0.01 s0.02 s0.02 s0.03 s
IRIS0.02 s0.02 s0.02 s0.04 s

Nous constatons encore une fois que MySQL ne permet pas ce type de sous-requêtes dans la même table que nous allons supprimer, de sorte que nous ne pouvons pas mesurer leurs durées dans des conditions égales.

Conclusions

Nous pouvons affirmer qu'ils sont tous très au point en ce qui concerne les requêtes de données, ainsi que la mise à jour et la suppression d'enregistrements (à l'exception de MySQL). La plus grande différence se trouve dans la gestion des insertions. IRIS est en effet le meilleur parmi les trois, étant 6 fois plus rapide que PostgreSQL et jusqu'à 20 fois plus rapide que MySQL lors de l'ingestion de données.

Pour travailler avec de grands ensembles de données, c'est IRIS qui est sans aucun doute la meilleure option dans les tests effectués.

Alors... nous avons déjà un champion! IRIS A GAGNÉ!

PS: Il ne s'agit que de quelques exemples de tests que vous pouvez effectuer, n'hésitez pas à modifier le code comme vous le souhaitez.

0
0 68
Article Sylvain Guilbaud · Mars 15, 2024 2m read

InterSystems rubrique FAQ 

Vous pouvez utiliser la classe %IndexBuilder pour effectuer la reconstruction d'index dans plusieurs processus parallèles.

Voici un exemple dans le but de définir l'index standard HomeStateIdx pour la colonne Home_State (informations de l'État de l'adresse du domicile) de Sample.Person.

Les étapes sont les suivantes:

1. Masquez le nom de l'index à ajouter/reconstruire à partir de l'optimiseur de requêtes.

>write$system.SQL.SetMapSelectability("Sample.Person","HomeStateIdx",0)
1
0
0 65
Article Sylvain Guilbaud · Fév 23, 2024 3m read

Bonjour la communauté !

Nous avons publié une nouvelle version d'IrisApiTesterIl intègre plusieurs nouveautés telles que :

  • Dépôts externes : nous avons ajouté la possibilité d'utiliser des collections d'un référentiel (GitHub ou Bitbucket) au lieu de faire glisser manuellement les fichiers de la collection vers votre page Web.
  • Pull and Run : nous avons créé un nouveau point de terminaison qui extrait automatiquement les modifications du référentiel et exécute des tests.
  • Tests unitaires/d'intégration : ajout d'exemples d'utilisation d'IrisApiTester dans les tests unitaires et d'intégration.
  • CI/CD : nous avons ajouté de nouveaux points de terminaison pour les flux de travail tels que GitHub afin d'extraire automatiquement les modifications du référentiel de collection à chaque validation terminée, d'exécuter des tests et d'envoyer les résultats à Google Chat.

 

0
0 42
Article Pierre LaFay · Jan 7, 2024 3m read

Le sous-système Windows pour Linux (WSL) est une fonctionnalité de Windows qui vous permet d'exécuter un environnement Linux sur votre ordinateur Windows, sans avoir besoin d'une machine virtuelle distincte ni d'un double démarrage.

WSL est conçu pour offrir une expérience transparente et productive aux développeurs qui souhaitent utiliser à la fois Windows et Linux**.

0
0 104
Article Pierre LaFay · Déc 30, 2023 6m read

Cela semble être hier lorsque nous avons réalisé un petit projet en Java pour tester les performances d'IRIS, PostgreSQL et MySQL (vous pouvez consulter l'article que nous avons écrit en juin à la fin de cet article). Si vous vous en souvenez, IRIS était supérieur à PostgreSQL et clairement supérieur à MySQL en termes d'insertions, sans grande différence en termes de requêtes.

0
0 125
Article Guillaume Rongier · Mars 1, 2023 4m read

Bonjour à tous, 

Nous voici de nouveau réunis. Nouvelle année, nouveau concours, nouveau projet, vieilles raisons.

Triple barre oblique "Triple Slash" est dans la place" !

1999, c'était l'année où j'ai appris à coder, mon premier "IF", mon premier "Bonjour le monde"

Je me souviens encore de mon professeur expliquant à tout le monde dans cette classe le simple "while" et comment nous pouvons déterminer si une condition spécifique a été atteinte @Renato Banzai vous en souvenez vous ? Le professeur Barbosa, quel homme unique.   

Depuis lors, j'aime l'idée de coder, de transformer des idées en projets, en quelque chose d'utile. Mais nous savons tous que pour créer quelque chose, nous devons nous assurer que cela fonctionne ; nous ne nous contentons pas de créer mais nous testons si cela fonctionne et si cela continue à fonctionner si nous ajoutons quelque chose de nouveau.

Et pour être honnête avec chacun d'entre vous, créer des tests, c'est ennuyeux. Du moins pour moi, mais si vous aimez le faire, je n'ai rien contre vous.

En utilisant une mauvaise analogie, je peux dire que la création de méthodes d'essai est comme le nettoyage de votre maison ou le repassage de vos vêtements. C'est ennuyeux, mais c'est nécessaire si vous voulez quelque chose de mieux.  

En gardant cela à l'esprit, pourquoi ne pas créer un moyen meilleur et plus facile pour les tests ? 

Ainsi, inspirés par le style elixir style and in et par cette idée d' InterSystems Ideas (Merci @Evgeny Shvarov)! Nous avons essayé d'améliorer le processus de test et de le transformer à nouveau en une tâche agréable.

Nous simplifions le %UnitTest et pour vous montrer comment utiliser TripleSlash pour créer vos tests unitaires, utilisons un exemple simple.

Disons que vous avez la classe et la méthode suivantes, pour lesquelles vous souhaitez écrire un test unitaire :

Class dc.sample.ObjectScript
{
ClassMethod TheAnswerForEverything() As%Integer
{

   Set a = 42Write"Bonjour le monde !",!

   Write"C'est InterSystems IRIS avec la version ",$zv,!

   Write"L'heure actuelle est : "_$zdt($h,2)

   Return a

}
}

Comme vous pouvez le constater, la méthode TheAnswerForEverything() ne fait que retranscrire le nombre 42. Indiquons donc dans la documentation de la méthode comment TripleSlash doit créer un test unitaire pour cette méthode :

/// Une méthode simple à des fins de tests./// /// /// Ecrivez ##class(dc.sample.ObjectScript).Test()/// 42/// ClassMethod TheAnswerForEverything() As%Integer
{
    ...
}

Les tests unitaires doivent être entourés de la balise <exemple></exemple>. Vous pouvez ajouter tout type de documentation, mais tous les tests doivent être contenus dans une telle balise.

Maintenant, démarrez une session de terminal IRIS, allez dans l'espace de noms IRISAPP créez une instance de la classe Core en passant le nom de la classe (ou son nom de paquet pour toutes ses classes) et exécutez ensuite la méthode Execute() :

USER>ZN"IRISAPP"
IRISAPP>Do##class(iris.tripleSlash.Core).%New("dc.sample.ObjectScript").Execute()

TripleSlash interprétera ceci comme "Étant donné le résultat de la méthode Test(), affirme qu'il est égal à 42". Ainsi, une nouvelle classe sera créée dans le test unitaire :

Class iris.tripleSlash.tst.ObjectScript Extends%UnitTest.TestCase
{

Method TestTheAnswerForEverything()
{
    Do$$$AssertEquals(##class(dc.sample.ObjectScript).TheAnswerForEverything(), 42)
}

}

Ajoutons maintenant une nouvelle méthode pour tester les autres moyens permettant d'indiquer à TripleSlash comment écrire vos tests unitaires.

Class dc.sample.ObjectScript
{

ClassMethod GuessTheNumber(pNumber As%Integer) As%Status
{
    Set st = $$$OKSet theAnswerForEveryThing = 42Try {
        Throw:(pNumber '= theAnswerForEveryThing) ##class(%Exception.StatusException).%New("Sorry, wrong number...")
    } Catch(e) {
        Set st = e.AsStatus()
    }

    Return st

}

}

Comme vous pouvez le constater, la méthode GuessTheNumber() attend un nombre, renvoie $$$OK uniquement si le nombre 42 est transmis ou une erreur pour toute autre valeur. Il faut donc indiquer dans la documentation de la méthode comment TripleSlash doit créer un test unitaire pour cette méthode :

/// Une autre méthode simple à des fins de test./// /// /// Exécutez ##class(dc.sample.ObjectScript).GuessTheNumber(42)/// $$$OK/// Do ##class(dc.sample.ObjectScript).GuessTheNumber(23)/// $$$NotOK/// ClassMethod GuessTheNumber(pNumber As%Integer) As%Status
{
    ...
}

Exécutez à nouveau la méthode Execute() et vous verrez une nouvelle méthode de test dans la classe de test unitaire iris.tripleSlash.tst.ObjectScript:

Class iris.tripleSlash.tst.ObjectScript Extends%UnitTest.TestCase
{

La méthode TestGuessTheNumber()
{

    Do$$$AssertStatusOK(##class(dc.sample.ObjectScript).GuessTheNumber(42))
    Do$$$AssertStatusNotOK(##class(dc.sample.ObjectScript).GuessTheNumber(23))
}

}

Actuellement, les assertions suivantes sont disponibles : $$$AssertStatusOK, $$$AssertStatusNotOK et $$$AssertEquals

TripleSlash nous permet de générer des tests à partir d'exemples de code trouvés dans les descriptions de méthodes. Il vous permet de faire d'une pierre deux coups, en améliorant la documentation de vos classes et en créant l'automatisation des tests. 

Remerciements

Une fois encore, nous vous remercions pour tout le soutien de la communauté dans chacune des applications que nous créons.

Si vous avez trouvé notre application intéressante et avez apporté votre contribution, veuillez voter pour iris-tripleslash et nous aider dans cette aventure !laugh

0
0 71
Article Irène Mykhailova · Fév 22, 2023 13m read

Dans les principales méthodologies de développement de logiciels, il y a toujours un chapitre consacré aux tests. Il s'agit d'une approche obligatoire pour obtenir la qualité des livraisons de manière continue.

On distingue deux types de tests:

  1. White Box Test : Ce sont des tests qui examinent la qualité du code source et des fonctionnalités de l'application. Dans ce type de test, nous avons :
    1. Analyse statique : on utilise des solutions d'analyse statique (il n'y a pas d'exécution de la fonctionnalité au moment du test) du code source, où les modèles de nommage, l'indentation, les variables déclarées et inutilisées, l'indice de couplage entre les composants, entre autres critères définis, sont évalués dans les solutions d'analyse. En général, dans l'environnement de développement, les lignes de code source présentant des problèmes de qualité sont signalées, ce qui permet au développeur d'agir pour résoudre le problème.
    2. Test unitaire : des classes de test ("Unité de test") sont créées, une pour chaque thème de fonctionnalité (Gestion des clients, Collecte des transactions, etc.), regroupées en Suites de test (ensemble d'unités de test), une suite pour chaque module d'application (Module d'enregistrement, Module de vente, etc.) ou pour l'application dans son ensemble. En général, un rapport HTML est émis avec les résultats obtenus.
  2. Black Box Test: tests effectués sur le binaire de l'application, sans accès au code source de l'application. Dans ce type, nous avons :
    1. Test de performance : Des scripts de chargement de données et d'exécution sont définis pour les principales fonctionnalités afin d'évaluer si l'application et l'environnement d'exécution supporteront le nombre de transactions et d'utilisateurs prévus.
    2. Test de pénétration - PenTest : des solutions d'analyse de vulnérabilité sont utilisées pour les adresses IP et les ports de l'application, les produits qui hébergent la solution (serveur web, application et base de données) et les points de terminaison de l'application (API, services web, répertoires d'échange de fichiers et autres points d'interaction). Généralement, un rapport PDF est émis avec les vulnérabilités trouvées.
    3. Tests fonctionnels : Les analystes et les testeurs écrivent et exécutent plusieurs scénarios de test à partir des interfaces visuelles de l'application, dans le but d'identifier les failles fonctionnelles (fonctionnalité présentant une erreur du point de vue de l'utilisateur final). Il existe aujourd'hui des solutions qui permettent également d'automatiser ces tests à partir des interfaces visuelles.

Un objectif important de la discipline des tests est de trouver le point idéal de couverture des tests (fonctionnalités couvertes par les tests). Dans les projets auxquels je participe, l'objectif est d'atteindre au moins 75% des fonctionnalités. Le grand secret est de concentrer vos tests sur la couche métier de l'application, car c'est là que se trouvent les règles métier et la mise en œuvre des exigences fonctionnelles fournies à l'utilisateur final. Les différentes interfaces visuelles et les points d'intégration finissent toujours par nécessiter la couche métier. De cette façon, en automatisant les tests de la couche fonctionnelle, la couverture des tests aura un taux pertinent.

Cet article présente comment automatiser les tests unitaires de la couche métier de votre application IRIS, afin de démontrer comment obtenir de bons taux de couverture des tests et une bonne qualité des applications IRIS livrées à l'utilisateur final.

Téléchargement de l'application à tester

Allez sur https://openexchange.intersystems.com/package/global-mindmap et cliquez sur le bouton Github. Suivez les étapes suivantes :

  1. Clonage du projet
$ git clone https://github.com/yurimarx/global-mindmap.git
  1. Faites le docker build dans le dossier racine du projet :
$ docker-compose build
  1. Executer le conteneur Docker Container:
$ docker-compose up -d

Découvrez l'application en action

Maintenant, passez aux tests

La classe affaires à tester

 
Classe Affaires Cible à tester
Classdc.globalmindmap.GlobalMindMapService
  <div>
    <span style="color: #ffffff;">{</span>
  </div>  

  <div>
    <span style="color: #80bd66;">///</span><span style="color: #6a9955;"> </span><span style="color: #80bd66;">Store mindmap node </span>
  </div>

  <div>
    <span style="color: #85a6ff;">ClassMethod</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">StoreMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%DynamicObject</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%Status</span>
  </div>

  <div>
    <span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Try</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>  

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"topic"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">topic</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"style"</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"fontSize"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">fontSize</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"style"</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"color"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">color</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"style"</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"background"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">background</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"parent"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">parent</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"tags"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">tags</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%ToJSON</span><span style="color: #ffffff;">(</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"icons"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">icons</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%ToJSON</span><span style="color: #ffffff;">(</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"hyperLink"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">hyperLink</span>
  </div>  

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">1</span>
  </div>  

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">}</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">Catch</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">write</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error name: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Name</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">            </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error code: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Code</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">            </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error location: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Location</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">            </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Additional data: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Data</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;"></span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #85a6ff;">ClassMethod</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">GetMindmap</span><span style="color: #ffffff;">(</span><span style="color: #85a6ff;">Output</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%DynamicArray</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%Status</span>
  </div>

  <div>
    <span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Try</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #ffffff;">]</span>
  </div>  

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Key</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">$ORDER</span><span style="color: #ffffff;">(</span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #d4b57c;">""</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Row</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;"></span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">While</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">'=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">""</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Push</span><span style="color: #ffffff;">(</span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Key</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">hyperLink</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"hyperLink"</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">icons</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"icons"</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">parent</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"parent"</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">background</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"style"</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"background"</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">color</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"style"</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"color"</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">fontSize</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"style"</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"fontSize"</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">tags</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"tags"</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Nodes</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Get</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Row</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">topic</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">,</span><span style="color: #d4b57c;">"topic"</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Row</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Row</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">+</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">1</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Key</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">$ORDER</span><span style="color: #ffffff;">(</span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">1</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">}</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">Catch</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Write</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error name: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Name</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error code: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Code</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error location: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Location</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Additional data: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Data</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;"></span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #80bd66;">///</span><span style="color: #6a9955;"> </span><span style="color: #80bd66;">Delete mindmap node</span>
  </div>

  <div>
    <span style="color: #85a6ff;">ClassMethod</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">DeleteMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">id</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%String</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%Status</span>
  </div>

  <div>
    <span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Try</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Kill</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ff75f4;">id</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">1</span>
  </div>  

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">}</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">Catch</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">write</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error name: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Name</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error code: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Code</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error location: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Location</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Additional data: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Data</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;"></span>
  </div>

  <div>
    <span style="color: #d4d4d4;">  </span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #80bd66;">///</span><span style="color: #6a9955;"> </span><span style="color: #80bd66;">Has content: 1 - yes, 0 - no</span>
  </div>

  <div>
    <span style="color: #85a6ff;">ClassMethod</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">HasContent</span><span style="color: #ffffff;">(</span><span style="color: #85a6ff;">Output</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Result</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%String</span><span style="color: #ffffff;">)</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%Status</span>
  </div>

  <div>
    <span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Try</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">key</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">$ORDER</span><span style="color: #ffffff;">(</span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #d4b57c;">""</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">If</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">key</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">""</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">            </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Result</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"0"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">}</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">Else</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">            </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ff75f4;">Result</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"1"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">        </span><span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">1</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">}</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">Catch</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Write</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error name: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Name</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error code: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Code</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Error location: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Location</span><span style="color: #ffffff;">,</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">          </span><span style="color: #ffffff;">!</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"Additional data: "</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">?</span><span style="color: #d4b57c;">20</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">err</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">Data</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">!</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">      </span><span style="color: #ffffff;">Return</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;"></span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #ffffff;">}</span>
  </div>
</div>

La classe affaires dispose de 3 fonctionnalités à tester :

  1. StoreMindmapNode : fonctionnalité utilisée pour enregistrer les données d'un nœud de carte heuristique dans la base de données sous forme de globales.
  2. GetMindmap : fonctionnalité utilisée pour retourner tous les noeuds de la carte mentale stockés dans les globales, c'est-à-dire la carte mentale dans son ensemble.
  3. DeleteMindmapNode : fonctionnalité utilisée pour supprimer un nœud de carte mentale de la base de données (supprime le nœud de globale qui stocke le nœud).

Création de la suite de tests - Un ensemble de cas de test

Il suffit de créer un répertoire à la racine du répertoire src, avec le nom de la suite de test, dans cet exemple le nom créé était UnitTests.

Création des classes de test de la suite de tests

Vous devez créer des classes de test qui héritent de %UnitTest.TestCase. Le nom de la classe, comme une bonne pratique, devrait être TestClassNameToBeTested + Test. Dans notre exemple, la classe s'appelle GlobalMindMapServiceTest. Découvrez le contenu de la classe :

 
Classe de cas à tester
ClassUnitTests.GlobalMindMapServiceTestExtends%UnitTest.TestCase
  <div>
    <span style="color: #ffffff;">{</span>
  </div>  

  <div>
    <span style="color: #85a6ff;">Method</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">TestStoreMindmapNode</span><span style="color: #ffffff;">()</span>
  </div>

  <div>
    <span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"TestId"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">topic</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"TestTopic"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">parent</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">""</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">tags</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #ffffff;">]</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">icons</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #ffffff;">]</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">background</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"black"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">fontSize</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"arial"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">color</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"white"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">hyperLink</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"intersystems.com"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">##class</span><span style="color: #ffffff;">(</span><span style="color: #4ec9b0;">dc</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">globalmindmap</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">GlobalMindMapService</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">StoreMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Key</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">$$$</span><span style="color: #dcdcaa;">AssertEquals</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">##class</span><span style="color: #ffffff;">(</span><span style="color: #4ec9b0;">dc</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">globalmindmap</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">GlobalMindMapService</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">DeleteMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #85a6ff;">Method</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">TestDeleteMindmapNode</span><span style="color: #ffffff;">()</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">As</span><span style="color: #d4d4d4;"> </span><span style="color: #4ec9b0;">%Status</span>
  </div>

  <div>
    <span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"TestDelete"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">topic</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"TestDelete"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">parent</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">""</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">tags</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #ffffff;">]</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">icons</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #ffffff;">]</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">background</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"black"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">fontSize</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"arial"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">color</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"white"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">hyperLink</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"intersystems.com"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">##class</span><span style="color: #ffffff;">(</span><span style="color: #4ec9b0;">dc</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">globalmindmap</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">GlobalMindMapService</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">StoreMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">##class</span><span style="color: #ffffff;">(</span><span style="color: #4ec9b0;">dc</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">globalmindmap</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">GlobalMindMapService</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">DeleteMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Key</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">$ORDER</span><span style="color: #ffffff;">(</span><span style="color: #64c9ff;">^mindmap</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">$$$</span><span style="color: #dcdcaa;">AssertEquals</span><span style="color: #ffffff;">(</span><span style="color: #d4b57c;">""</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Key</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #85a6ff;">Method</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">TestGetMindmap</span><span style="color: #ffffff;">()</span>
  </div>

  <div>
    <span style="color: #ffffff;">{</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"TestGet"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">topic</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"TestGet"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">parent</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">""</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">tags</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #ffffff;">]</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">icons</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">[</span><span style="color: #ffffff;">]</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">{</span><span style="color: #ffffff;">}</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">background</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"black"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">fontSize</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"arial"</span><span style="color: #d4d4d4;"> </span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">color</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"white"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">style</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">hyperLink</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">"intersystems.com"</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">##class</span><span style="color: #ffffff;">(</span><span style="color: #4ec9b0;">dc</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">globalmindmap</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">GlobalMindMapService</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">StoreMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">##class</span><span style="color: #ffffff;">(</span><span style="color: #4ec9b0;">dc</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">globalmindmap</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">GlobalMindMapService</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">GetMindmap</span><span style="color: #ffffff;">(</span><span style="color: #ffffff;">.</span><span style="color: #ade2ff;">Result</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Set</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Count</span><span style="color: #d4d4d4;"> </span><span style="color: #ffffff;">=</span><span style="color: #d4d4d4;"> </span><span style="color: #ade2ff;">Result</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">%Size</span><span style="color: #ffffff;">(</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #dcdcaa;">$$$</span><span style="color: #dcdcaa;">AssertEquals</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">Count</span><span style="color: #ffffff;">,</span><span style="color: #d4d4d4;"> </span><span style="color: #d4b57c;">1</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #d4d4d4;">    </span><span style="color: #ffffff;">Do</span><span style="color: #d4d4d4;"> </span><span style="color: #85a6ff;">##class</span><span style="color: #ffffff;">(</span><span style="color: #4ec9b0;">dc</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">globalmindmap</span><span style="color: #4ec9b0;">.</span><span style="color: #4ec9b0;">GlobalMindMapService</span><span style="color: #ffffff;">)</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">DeleteMindmapNode</span><span style="color: #ffffff;">(</span><span style="color: #ade2ff;">data</span><span style="color: #ffffff;">.</span><span style="color: #dcdcaa;">id</span><span style="color: #ffffff;">)</span>
  </div>

  <div>
    <span style="color: #ffffff;">}</span>
  </div>  

  <div>
    <span style="color: #ffffff;">}</span>
  </div>
</div>

Remarquez que pour chaque fonctionnalité à tester, nous avons une méthode avec la convention de dénomination suivante : Test +Nom de la méthode à tester. Il est important que chaque méthode de test fasse tout ce qui est nécessaire à l'exécution du test. Dans le test de suppression, par exemple, un enregistrement est d'abord créé, qui sera utilisé pour être supprimé et ainsi tester si la suppression fonctionne.

Pour chaque méthode, il doit y avoir au moins un appel à $$$Assert..... Cette macro est l'exécution et l'enregistrement de la condition de test elle-même. Dans notre exemple, nous utilisons $$$AssertEquals dans toutes les méthodes. Dans ce cas, si la condition de test est égale au résultat de l'exécution de la méthode métier, le test est marqué comme réussi, sinon il est marqué comme non réussi. Il existe plusieurs options Assert, voir plus de détails sur https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=TUNT_AssertX.

Il est également possible d'utiliser la méthode OnBeforeAllTests pour créer les données et les conditions nécessaires aux tests et d'utiliser OnAfterAllTests pour nettoyer les données et les environnements et les ramener à leur état initial.

Exécution des tests

Une fois la suite et les cas de test prêts, il est temps d'effectuer les tests. Pour cela, vous devez accéder au Terminal, dans l'espace de nom où se trouvent les classes, consultez les étapes de l'exemple dans cet article :

  1. Sur Terminal, espace de nom IRISAPP, il faut aller dans l'espace de nom qui contient les classes d'affaires et de test :
USER>zn "IRISAPP"
  1. Il est nécessaire de pointer vers le dossier où se trouve le dossier Test Suite (dans l'exemple, il s'agit du dossier UnitTests dans src)
IRISAPP>Set ^UnitTestRoot="/opt/irisbuild/src"
  1. Exécutez les tests unitaires
IRISAPP>do ##class(%UnitTest.Manager).RunTest("UnitTests")
  1. Découvrez les résultats ici: http://localhost:52773/csp/sys/%25UnitTest.Portal.Indices.cls?Index=1&$NAMESPACE=IRISAPP

Analyse des résultats

Le rapport de l'étape précédente revient dans cette mise en page :

Il est possible de savoir ce qui a réussi (vert) et ce qui a échoué (rouge). En cliquant sur le rouge, il est possible de savoir où l'erreur s'est produite :

Il ne reste plus qu'à appliquer les corrections.

Si vous avez aimé l'exemple d'application, votez pour elle à Global Contest.

0
0 129
Article Sylvain Guilbaud · Fév 15, 2023 8m read

Introduction

Dans un article précédent, j'ai abordé les modèles d'exécution des tests unitaires via le gestionnaire de paquets ObjectScript. Cet article va un peu plus loin, en utilisant les actions GitHub pour piloter l'exécution des tests et la création de rapports. Le cas d'utilisation qui nous motive est l'exécution du CI pour l'un de mes projets Open Exchange, AppS.REST (voir l'article d'introduction à ce projet ici). Vous pouvez voir l'implémentation complète dont les extraits de cet article ont été tirés sur GitHub ; elle pourrait facilement servir de modèle pour l'exécution de l'IC pour d'autres projets utilisant le gestionnaire de paquets ObjectScript.

Les fonctionnalités dont la mise en œuvre a été démontrée comprennent :

  • Compilation et test d'un paquet ObjectScript
  • Rapport sur la mesure de la couverture des tests (en utilisant le paquet TestCoverage) via codecov.io
  • Téléchargement d'un rapport sur les résultats des tests en tant qu'artefact de comppilation.

L'environnement de compilation

Il existe une documentation complète sur les actions GitHub ici. Dans le cadre de cet article, nous nous contenterons d'explorer les aspects présentés dans cet exemple.

Un flux de travail dans les actions GitHub est déclenché par un ensemble configurable d'événements et consiste en un certain nombre de tâches qui peuvent être exécutées séquentiellement ou en parallèle. Chaque tâche comporte un ensemble d'étapes - nous allons entrer dans le détail des étapes de notre exemple d'action plus tard. Ces étapes consistent en références à des actions disponibles sur GitHub, ou peuvent simplement être des commandes shell. Un extrait du modèle initial de notre exemple ressemble à ceci :

# Flux de travail d'intégration continue
name: CI

# Contrôle le moment où l'action sera exécutée. Déclenche le flux de travail sur les événements push ou pull request
# événements dans toutes les branches
on: [push, pull_request]

# Un flux de travail est composé d'une ou plusieurs tâches qui peuvent être exécutées séquentiellement ou en parallèle.
jobs:
  # Ce flux de travail contient une seule tâche appelé "build".
  build :
    # Le type d'exécuteur sur lequel le travail sera exécuté
    runs-on : ubuntu-latest

    env:
    # Variables d'environnement utilisables tout au long de la tâche de "compilation", par exemple dans les commandes au niveau du système d'exploitation.
package: apps.rest
    container_image : intersystemsdc/iris-community:2019.4.0.383.0-zpm
    # D'autres éléments seront abordés plus tard...

    # Les étapes représentent une séquence de tâches qui seront exécutées dans le cadre du travail.
     steps:
          # Ceux-ci seront montrés plus tard...

Dans cet exemple, un certain nombre de variables d'environnement sont utilisées. Pour appliquer cet exemple à d'autres paquets utilisant le gestionnaire de paquets ObjectScript, la plupart d'entre elles n'auront pas besoin d'être modifiées, alors que certaines le seront.

    env:
      # ** POUR UN USAGE GÉNÉRAL, IL FAUDRA PROBABLEMENT CHANGER : **
      package: apps.rest
      container_image: intersystemsdc/iris-community:2019.4.0.383.0-zpm

      # ** POUR UN USAGE GÉNÉRAL, IL FAUDRA PEUT-ÊTRE CHANGER : **
      build_flags: -dev -verbose # Télécharger en mode -dev pour obtenir le code de test unitaire préchargé
      test_package: UnitTest

      # ** POUR UN USAGE GÉNÉRAL, IL NE FAUDRA PAS CHANGER : **
      instance: iris
      # Remarque : la valeur test_reports est dupliquée dans la variable d'environnement test_flags.
      test_reports: test-reports
      test_flags: >-
       -verbose -DUnitTest.ManagerClass=TestCoverage.Manager -DUnitTest.JUnitOutput=/test-reports/junit.xml
       -DUnitTest.FailuresAreFatal=1 -DUnitTest.Manager=TestCoverage.Manager
       -DUnitTest.UserParam.CoverageReportClass=TestCoverage.Report.Cobertura.ReportGenerator
       -DUnitTest.UserParam.CoverageReportFile=/source/coverage.xml

Si vous voulez adapter cela à votre propre paquet, il suffit de déposer votre propre nom de paquet et votre image de conteneur préférée (doit inclure zpm - voir https://hub.docker.com/r/intersystemsdc/iris-community). Vous pourriez également vouloir changer le paquet de tests unitaires pour qu'il corresponde à la convention de votre propre paquet (si vous devez charger et compiler les tests unitaires avant de les exécuter pour gérer toutes les dépendances de chargement/compilation ; j'ai eu quelques problèmes bizarres spécifiques aux tests unitaires pour ce paquet, donc cela pourrait même ne pas être pertinent dans d'autres cas).

Le nom de l'instance et le répertoire test_reports ne devraient pas être modifiés pour d'autres utilisations, et les test_flags fournissent un bon ensemble de valeurs par défaut - ils permettent de faire en sorte que les échecs des tests unitaires signalent l'échec de la compilation, et gèrent également l'exportation des résultats des tests au format jUnit et un rapport de couverture de code.

Étapes de compilation

Vérification des référentiels GitHub

Dans notre exemple de motivation, deux dépôts doivent être vérifiés - celui qui est testé, et aussi mon fork de Forgery (parce que les tests unitaires en ont besoin).

    # Vérifie ce référentiel sous $GITHUB_WORKSPACE, afin que votre tâche puisse y accéder.
    - uses: actions/checkout@v2

    # Il faut aussi vérifier le timleavitt/forgery jusqu'à la version officielle installable via ZPM
    - uses: actions/checkout@v2
      with:
        repository: timleavitt/forgery
        path: forgery

$GITHUB_WORKSPACE est une variable d'environnement très importante, représentant le répertoire racine où tout cela fonctionne. Du point de vue des permissions, vous pouvez faire à peu près tout ce que vous voulez dans ce répertoire ; ailleurs, vous pouvez rencontrer des problèmes.

Exécution du conteneur IRIS d'InterSystems

Après avoir configuré un répertoire où nous finirons par placer nos rapports de résultats de tests, nous allons exécuter le conteneur InterSystems IRIS Community Edition (+ZPM) pour notre compilation.

    - name: Run Container
      run: |
        # Créer le répertoire test_reports pour partager les résultats des tests avant l'exécution du conteneur.
        mkdir $test_reports
        chmod 777 $test_reports
        # Lancer l'instance InterSystems IRIS
        docker pull $container_image
        docker run -d -h $instance --name $instance -v $GITHUB_WORKSPACE:/source -v $GITHUB_WORKSPACE/$test_reports:/$test_reports --init $container_image
        echo halt > wait
        # Attendez que l'instance soit prête
        until docker exec --interactive $instance iris session $instance &lt; wait; do sleep 1; done

Il y a deux volumes partagés avec le conteneur - l'espace de travail GitHub (pour que le code puisse être chargé ; nous y rapporterons également des informations sur la couverture des tests), et un répertoire séparé où nous placerons les résultats des tests jUnit.

Après la fin de "docker run", cela ne signifie pas que l'instance est complètement démarrée et prête à être commandée. Pour attendre que l'instance soit prête, nous continuerons à essayer d'exécuter une commande "halt" via la session iris ; cela échouera et continuera à essayer une fois par seconde jusqu'à ce que cela réussisse (éventuellement), indiquant que l'instance est prête.

Installation des bibliothèques liées aux tests

Pour notre cas d'utilisation motivant, nous utiliserons deux autres bibliothèques pour les tests - TestCoverage et Forgery. TestCoverage peut être installé directement via le Community Package Manager ; Forgery (actuellement) doit être chargé via zpm "load" ; mais les deux approches sont valables.

    - name: Install TestCoverage
      run: |
        echo "zpm \"install testcoverage\":1:1" > install-testcoverage
        docker exec --interactive $instance iris session $instance -B &lt; install-testcoverage
        # Solution aux problèmes de permissions dans TestCoverage (création d'un répertoire pour l'exportation des sources)
        chmod 777 $GITHUB_WORKSPACE

    - name: Install Forgery
      run: |
        echo "zpm \"load /source/forgery\":1:1" > load-forgery
        docker exec --interactive $instance iris session $instance -B &lt; load-forgery

L'approche générale consiste à écrire les commandes dans un fichier, puis à les exécuter en session IRIS. Le ":1:1" supplémentaire dans les commandes ZPM indique que la commande doit quitter le processus avec un code d'erreur si une erreur se produit, et s'arrêter à la fin si aucune erreur ne se produit ; cela signifie que si une erreur se produit, elle sera signalée comme une étape de compilation ayant échoué, et nous n'avons pas besoin d'ajouter une commande "halt" à la fin de chaque fichier.

Compilation et test du paquet

Enfin, nous pouvons effectivement compiler et exécuter des tests pour notre paquet. C'est assez simple - remarquez l'utilisation des variables d'environnement $build_flags/$test_flags que nous avons définies plus tôt.

    # Exécute un ensemble de commandes en utilisant l'exécuteur runners
    - name: Build and Test
      run: |
        # Exécution de compilation
        echo "zpm \"load /source $build_flags\":1:1" > build
        # Le paquet de test est compilé en premier comme solution de contournement pour certains problèmes de dépendance.
        echo "do \$System.OBJ.CompilePackage(\"$test_package\",\"ckd\") " > test
        # Exécution des tests
        echo "zpm \"$package test -only $test_flags\":1:1" >> test
        docker exec --interactive $instance iris session $instance -B < build && docker exec --interactive $instance iris session $instance -B < test && bash <(curl -s https://codecov.io/bash)

Cela suit le même schéma que nous avons vu, écrire des commandes dans un fichier puis utiliser ce fichier comme entrée de la session iris.

La dernière partie de la dernière ligne télécharge les résultats de la couverture du code sur codecov.io. Super facile !

Téléchargement des résultats des tests unitaires

Supposons qu'un test unitaire échoue. Il serait vraiment ennuyeux de devoir revenir en arrière dans le journal de compilation pour trouver ce qui n'a pas fonctionné, bien que cela puisse toujours fournir un contexte utile. Pour nous faciliter la vie, nous pouvons télécharger nos résultats formatés par jUnit et même exécuter un programme tiers pour les transformer en un joli rapport HTML.

    # Générer et télécharger le rapport HTML xUnit
    - name: XUnit Viewer
      id: xunit-viewer
      uses: AutoModality/action-xunit-viewer@v1
      if: always()
      with:
        # Avec -DUnitTest.FailuresAreFatal=1, un test unitaire qui échoue fera échouer la compilation avant ce point.
        # Cette action pourrait autrement mal interpréter notre sortie de style xUnit et faire échouer la compilation même si
        # tous les tests sont passés.
        fail: false
    - name: Atacher le rapport
      uses: actions/upload-artifact@v1
      if: always()
      with:
        name: ${{ steps.xunit-viewer.outputs.report-name }}
        path: ${{ steps.xunit-viewer.outputs.report-dir }}

Ces informations sont principalement tirées du fichier readme à l'adresse https://github.com/AutoModality/action-xunit-viewer.

Le résultat final

Si vous voulez voir les résultats de ce flux de travail, regardez :

Les journaux pour le job CI sur intersystems/apps-rest (y compris les artefacts de compilation) : https://github.com/intersystems/apps-rest/actions?query=workflow%3ACI
Rapports de couverture de test : https://codecov.io/gh/intersystems/apps-rest

N'hésitez pas à me faire savoir si vous avez des questions !

0
0 96
Article Evgeny Shvarov · Fév 10, 2023 6m read

Salut les développeurs !

Voici encore un court message qui a pour but de simplifier la vie des développeurs. Nous allons maintenant vous expliquer comment faire en sorte que GitHub exécute des tests unitaires à chaque poussée vers le référentiel en ajoutant un seul fichier au référentiel. Gratuitement. Dans le nuage de Github. Ça a l'air génial, n'est-ce pas ?

C'est possible et très facile à faire. Le mérite revient à @Dmitry Maslennikov (et son référentiel), gestionnaire de paquets "ZPM Package Manager", et aux GitHub Actions.  Voyons comment tout cela fonctionne !

Quelque chose pour rien de Robert Sheckley - YouTube

Considérons d'abord un référentiel avec des tests, par exemple le modèle IRIS de base

Supposons également que vous chargez votre code de développement dans le docker IRIS à l'aide de ZPM. Si ce n'est pas le cas, regardez la video sur la façon de le faire. 

Dans ce référentiel particulier, il s'agit de la ligne suivante et avec la présence de module.xml:

zpm "load /home/irisowner/irisbuild/ -v":1:1

Ensuite, ajoutons un scénario de GitHub Actions qui permettra de construire l'image et d'exécuter les tests :

name: unittest

on:
  push:
    branches:
      - maître
      - principale
  pull_request:
    branches:
      - maître
      - principale
  version:
    types:
      - libéré

tâches:
  build:
    runs-on: ubuntu-latest
    étapes:
      - utilisations: actions/checkout@v2
      - nom: Build and Test
        utilisations: docker/build-push-action@v2
        avec:
          contexte: .
          push: faux
          charge: vrai
          balises: ${{ github.repository }}:${{ github.sha }}
          build-args: TESTS=1

Ce scénario construit une image docker en utilisant Dockerfile du référentiel à chaque push ou pull-request vers la branche master ou main.

Voici une ligne supplémentaire qui transfère la variable TESTS=1 au Dockerfile :

build-args: TESTS=1

Cet argument est évaué dans le Dockerfile et si TESTS=1, il exécute les tests du paquet zpm du référentiel :

    ([ $TESTS -eq 0 ] || iris session iris -U $NAMESPACE "##class(%ZPM.PackageManager).Shell("test $MODULE -v -only",1,1)") && \

Cette ligne vérifie le paramètre $TESTS et s'il n'est pas égal à 0, elle ouvre la session iris dans $NAMESPACE (IRISAPP dans ce cas) et exécute la commande ZPM :

test $MODULE -v -only

L'indicateur -only exécute uniquement le test sans charger le module (car il a été chargé auparavant dans le scénario de construction de docker).

Le nom du module est défini via $MODULE="dc-sample-template" dans ce cas :

ARG MODULE="dc-sample-template"

La commande va exécuter tous les unittests que nous avons mentionnés dans le module ZPM. Dans ce cas, nous le présentons avec cette ligne:

&lt;UnitTest Name="/tests" Package="dc.sample.unittests" Phase="test"/>

ce qui signifie que les tests unitaires sont situés dans le dossier /tests du référentiel et que la ressource est dc.sample.unittests paquet de classe qui a deux classes.

Ce scénario va construire l'image de votre dépôt sur le nuage GitHub et exécuter des tests pour chaque push ou pull request vers la branche maître !

Voyons comment cela fonctionne !

La méthode Test42 prévoit que la méthode dc.sample.ObjectScript).Test() renvoie 42.

Changeons la ligne en 43 et envoyons pull-request. Nous pouvons voir (dans la section Actions ) que la pull-request a initié l'action Github. En effet, la construction a échoué :

Et les détails de la phase indiquent que Test42() a échoué sur AssertEquals 42 =43 :

Github envoie également des notifications par courriel en cas d'échec des constructions CI.

Remarque : pour exécuter les tests localement, il suffit d'appeler :

USER>zpm test "module-name"

Et si nous remettons la valeur de la variable à 42, les tests seront OK !

Et s'il s'agit de Pull-Request, vous pouvez voir le résultat de l'IC dans la section spéciale de contrôle Checks:

Et voilà !

Pour résumer, faire fonctionner GitHub (gratuitement !) pour les constructions de dockers et les unittests des projets InterSystems IRIS sur les pushes ou les pull-requests nécessite un scénario CI Github Actions scenario, qui sera le même pour chaque projet, et trois lignes dans le Dockerfile :

Le nom ZPM $MODULE - ceci doit être mis à jour avec chaque projet,

Le paramètre $TEST,

et la ligne RUN TEST.

Et utilisez le gestionnaire ZPM Package Manager!

Les commentaires et les réactions sont les bienvenus, et bon codage !

L'image du titre est liée à l'histoire de l'un de mes auteurs de SF préférés, Robert Sheckley, ["Quelque chose pour rien"] (https://en.wikipedia.org/wiki/Something_for_Nothing_(book)) - la voici en [audio aussi] (https://www.youtube.com/watch?v=CL0GQTtx-5A), profitez-en !

0
0 73