Instruments for Apple's Xcode est un outil pour analyser les performances d'une application iOS. Ils sont utilisés pour collecter et afficher les données nécessaires au débogage du code. L'année dernière, Apple a
présenté des instruments personnalisés. C'est l'occasion d'élargir l'ensemble standard d'outils pour le profilage des applications. Lorsque les outils existants ne suffisent pas, vous pouvez en créer vous-même - ils collecteront, analyseront et afficheront les données selon vos besoins.
Une année s'est écoulée et il n'y a presque pas de nouveaux outils publics et d'informations sur leur création sur le réseau. Nous avons donc décidé de rectifier la situation et de partager comment nous avons créé notre propre instrument personnalisé, qui détermine la raison de la faible isolation des tests unitaires. Il est basé sur la technologie des panneaux (nous l'avons écrit
dans l'article précédent ) et vous permet de déterminer rapidement et précisément où le test clignote.

Minimum théorique
Pour créer un nouvel outil pour Xcode, vous devez comprendre deux blocs théoriques. Pour ceux qui veulent le découvrir par eux-mêmes, nous donnerons immédiatement les liens nécessaires:
Pour le reste - ci-dessous est un bref résumé des sujets nécessaires.
Sélectionnez d'abord Fichier -> Nouveau -> Projet -> Catégorie macOS -> Package d'instruments. Le projet créé comprend un fichier avec l'extension .instrpkg, dans lequel un nouvel outil est déclaré de manière déclarative au format xml. Familiarisons-nous avec les éléments de balisage:
Quoi | Attributs | La description
|
Schémas de données
| schéma d'intervalle, schéma de point, etc.
| Décrit la structure de données sous forme de tableau comme les schémas SQL. Les schémas sont utilisés dans d'autres éléments de balisage pour déterminer le type de données à l'entrée et à la sortie du modèle, par exemple, lors de la description d'un mappage (UI).
|
Importer des schémas de données
| schéma d'importation
| Importez des schémas prêts à l'emploi. Il vous permet d'utiliser des structures de données définies par Apple.
|
Modèle d'outil
| modeleur
| Associe l'outil à un fichier .clp, dans lequel la logique de l'outil est définie, et annonce le schéma de données attendu à l'entrée et à la sortie du modèle.
|
Description de l'outil
| instrument
| Décrit le modèle de données et détermine comment les événements seront affichés dans l'interface utilisateur. Le modèle de données est décrit à l'aide des attributs create-table, create-parameter, etc. Les diagrammes d'outils sont définis par des attributs de graphique et le tableau des pièces est défini par une liste, un récit, etc.
|
Si nous voulons compléter la logique du nouvel outil, alors créez un fichier .clp avec le code CLIPS. Entités linguistiques de base:
- «Fact» est un certain événement enregistré dans le système à l'aide de la commande assert;
- «Règle» est un bloc if avec une syntaxe spécifique qui contient une condition sous laquelle un ensemble d'actions est effectué.
Les règles et la séquence qui seront activées sont déterminées par le CLIPS lui-même en fonction des faits entrants, des priorités des règles et du mécanisme de résolution des conflits.
Le langage prend en charge la création de types de données basés sur des primitives, l'utilisation de fonctions et d'opérations arithmétiques et logiques. Et aussi une programmation orientée objet (OOP) à part entière avec la définition de classes, l'envoi de messages, l'héritage multiple.
Considérez la syntaxe de base d'un langage qui vous permet de créer une logique pour des outils personnalisés.
1. Pour créer un
fact
, utilisez la construction
assert
:
CLIPS> (assert (duck))
Ainsi, nous obtenons l'entrée
duck
dans la table de faits, qui peut être consultée à l'aide de la commande
facts
:
CLIPS> (facts)
Pour supprimer le fait, utilisez la commande
retract
:
(retract duck)
2. Pour créer une
rule
, utilisez la construction
defrule
:
CLIPS> (defrule duck) — duck (animal-is duck)</i> — animal-is duck => (assert (sound-is quack))) — sound-is quack
3. Pour créer et utiliser des variables, la syntaxe suivante est utilisée (avant le nom de la variable, il y a un signe obligatoire "?"):
?<variable-name>
4. Vous pouvez créer de nouveaux types de données en utilisant:
CLIPS> (deftemplate prospect (slot name (type STRING) (default ?DERIVE)) (slot assets (type SYMBOL) (default rich)) (slot age (type NUMBER) (default 80)))
Ainsi, nous avons défini une structure avec le nom du prospect et trois attributs nom, actifs et âge du type correspondant et une valeur par défaut.
5. Les opérations arithmétiques et logiques ont une syntaxe de préfixe. Autrement dit, pour ajouter 2 et 3, vous devez utiliser la construction suivante:
CLIPS> (+ 2 3)
Ou pour comparer deux variables x et y:
CLIPS> (> ?x ?y)
Exemple pratique
Dans notre projet, nous utilisons la bibliothèque
OCMock pour créer des objets stub. Cependant, il existe des situations où un mok vit plus longtemps que le test pour lequel il a été créé et affecte l'isolement des autres tests. En conséquence, cela conduit au «clignotement» (instabilité) des tests unitaires. Afin de suivre la durée de vie des tests et des simulations, nous allons créer notre propre outil. Ce qui suit est un algorithme d'actions.
Étape 1. Création d'un balisage pour les événements de signalisation
Pour détecter les mox problématiques, deux catégories d'événements d'intervalle sont nécessaires: l'heure de création et de destruction du moxa, l'heure de début et l'heure de fin du test. Pour obtenir ces événements, accédez à la bibliothèque
OCMock
et
OCMock
les avec
signpost
dans les méthodes
init
et
stopMocking
de la classe
stopMocking
.


Ensuite, allez au projet à l'étude, faites le balisage dans les tests unitaires, les
tearDown
setUp
et
tearDown
:

Étape 2. Créez un nouvel outil à partir du modèle de package d'instruments

Tout d'abord, nous déterminons le type de données de l'entrée. Pour ce faire,
.instrpkg
importons le schéma de
signpost
dans le
signpost
. Maintenant, les événements créés par
signpost
tomberont dans l'outil:

Ensuite, nous déterminons le type de données dans la sortie. Dans cet exemple, nous afficherons des événements simultanés. Chaque événement aura une heure et une description. Pour ce faire, déclarez le schéma:

Étape 3. Nous décrivons la logique de l'outil
Nous créons un fichier séparé avec l'extension
.clp
, dans lequel nous définissons les règles en utilisant le langage CLIPS. Pour indiquer au nouvel outil dans quel fichier la logique est définie, ajoutez le bloc de
modeler
:

Dans ce bloc, à l'aide de l'attribut
production-system
, spécifiez le chemin d'accès relatif au fichier avec la logique. Dans les attributs
output
et
required-input
définissons les schémas de données pour l'entrée et la sortie, respectivement.

Étape 4. Nous décrivons les spécificités de la présentation de l'outil (UI)
Dans le fichier
.instrpkg
,
.instrpkg
reste à décrire l'outil lui-même, c'est-à-dire à afficher les résultats. Créez une table pour les données dans l'attribut
create-table
utilisant le
schema-ref
detected-mocks-narrative
précédemment déclaré dans l'attribut
schema-ref
. Et définissez le type de sortie d'information - narrative (descriptive):

Étape 5. Nous écrivons le code logique
Passons au fichier
.clp
, dans lequel la logique du système expert est définie. La logique sera la suivante: si l'heure de début du test chevauche l'intervalle de vie du moka, alors nous pensons que ce mok «provient» d'un autre test - qui viole l'isolement du test unitaire actuel. Afin de créer éventuellement un événement avec des informations d'intérêt, vous devez effectuer les étapes suivantes:
1. Définissez les structures mock et unitTest avec des champs - l'heure de l'événement, l'identifiant de l'événement, le nom du test et la classe du mok.

2. Nous définissons les règles qui créeront des faits avec les types
mock
et
unitTest
fonction des événements entrants du
signpost
:

Ces règles peuvent être lues comme suit: si à l'entrée, nous obtenons un fait de type os-signpost avec le
subsystem
-
subsystem
, la
category
, le
name
et le
event-type
souhaités, puis créez un nouveau fait avec le type défini ci-dessus (unitTest ou mock) et remplissez-le de valeurs. Il est important de se souvenir ici - CLIPS est un langage sensible à la casse et les valeurs de sous-système, catégorie, nom et type d'événement doivent correspondre à ce qui a été utilisé dans le code du projet à l'étude.

Les variables des événements d'orientation sont transmises comme suit:

3. Nous définissons les règles qui libèrent les événements terminés (ils sont redondants, car ils n'affectent pas le résultat).

Étape 6. Définissez la règle qui générera les résultats.
Vous pouvez lire la règle comme ceci.
Si1) il y a unitTest et mock;
2) dans ce cas, le début du test se produit plus tard que le moka existant;
3) il existe un tableau pour stocker les résultats avec le schéma narratif simulé détecté;
alors4) créer un nouvel enregistrement;
5) remplissez avec le temps;
6) ... et une description.

Par conséquent, nous voyons l'image suivante lors de l'utilisation du nouvel outil:

Le code source de l'instrument personnalisé et un exemple de projet d'utilisation de l'instrument peuvent être consultés
sur GitHub .
Débogage d'outils
Le débogueur est utilisé pour déboguer des outils personnalisés.

Il permet
1. Voir le code compilé basé sur la description dans instrpkg.
2. Consultez les informations détaillées sur ce qui arrive à l'outil lors de l'exécution.

3. Affichez une liste complète et une description des schémas de données système qui peuvent être utilisés comme entrée dans de nouveaux outils.

4. Exécutez des commandes arbitraires dans la console. Par exemple, affichez une liste de règles avec la commande list-defrules ou des faits avec la commande facts

Configuration sur le serveur CI
Vous pouvez exécuter des outils à partir de la ligne de commande - pour profiler l'application pendant l'exécution de tests unitaires ou d'interface utilisateur sur le serveur CI. Cela permettra, par exemple, de détecter une fuite de mémoire le plus tôt possible. Pour profiler les tests dans le pipeline, utilisez les commandes suivantes:
1. Exécution d'outils avec des attributs:
xcrun instruments -t <template_name> -l <average_duration_ms> -w <device_udid>
- où
template_name
est le chemin d'accès au modèle avec des outils ou le nom du modèle. Vous pouvez obtenir la commande xcrun instruments -s
; average_duration_ms
- le temps d'enregistrement en millisecondes, doit être supérieur ou égal au temps d'exécution du test;device_udid
- identifiant du simulateur. Vous pouvez obtenir la commande xcrun instruments -s. Doit correspondre à l'identifiant du simulateur sur lequel les tests seront exécutés.
2. Exécution de tests sur le même simulateur avec la commande:
xcodebuild -workspace <path_to_workspace>-scheme <scheme_with_tests> -destination <device> test-without-building
- où
path_to_workspace
est le chemin d'accès à l'espace de travail Xcode; scheme_with_tests
- schéma avec tests;device
- identifiant du simulateur.
En conséquence, un rapport avec l'extension .trace sera créé dans le répertoire de travail, qui peut être ouvert par l'application Instruments ou en cliquant avec le bouton droit sur le fichier et en sélectionnant Afficher le contenu du package.
Conclusions
Nous avons examiné un exemple de mise à niveau de poteau indicateur vers un outil à part entière et expliqué comment l'appliquer automatiquement sur les «exécutions» du serveur CI et l'utiliser pour résoudre le problème des tests de «clignotement» (instable).
En plongeant dans les possibilités des instruments personnalisés, vous comprendrez mieux dans quels autres cas vous pouvez utiliser les instruments. Par exemple, ils nous aident également à comprendre les problèmes du multithreading - où et quand utiliser l'accès aux données thread-safe.
La création d'un nouvel outil était assez simple. Mais l'essentiel est qu'après avoir passé plusieurs jours à étudier la mécanique et la documentation pour le créer aujourd'hui, vous pourrez éviter plusieurs nuits blanches dans les tentatives de correction de bugs.
Les sources
L'article a été écrit avec @regno , Anton Vlasov, développeur iOS.