Lab 2.5 - Passer la barrière de qualité de code et la couverture de tests Apex¶
Niveau : 2 Contributeur avancé
Durée : ~30 min
Vous allez : être bloqué deux fois par des robots, corriger les deux correctement plutôt que de les contourner, et apprendre à lancer les contrôles avant de pousser.
La situation¶
US-027 - Check several installations against panel availability at once
As a planner, I want to check a list of installations against panel availability in one go, so that I can fill a week of planning without opening every record.
Une modification Apex dans InstallationScheduler. Deux choses vont vous arrêter, et aucune des deux
ne concerne une métadonnée fausse :
- PMD, l'analyseur de code Apex, lancé pour vous par MegaLinter, sur une requête à l'intérieur d'une boucle que vous êtes sur le point d'écrire en copiant un motif existant. Il avertit
- La couverture de code, parce que la nouvelle branche de logique n'a pas de test. Elle bloque
La première est un choix de ce projet : il pourrait faire refuser l'analyseur, et il ne le fait pas. La seconde est aussi un seuil de ce projet : 80 % de l'Apex de l'org exécuté par les tests, au-dessus des 75 % qu'exige Salesforce lui-même. Savoir lesquelles de vos barrières avertissent et lesquelles refusent est la moitié du travail sur une pipeline, ce lab vous en fait donc rencontrer une de chaque.
Admins, ce lab est aussi pour vous
Il y a de l'Apex dedans, et vous n'en écrirez pas une ligne : chaque bloc se copie depuis cette page et se colle dans un fichier. Ce que vous pratiquez est ce qu'un admin rencontre sur tout vrai projet, une Pull Request bloquée par un analyseur de code ou par la couverture de tests, et comment lire ce que disent les robots avant de demander de l'aide à un développeur.
Avant de commencer¶
- Lab 2.4 terminé et mergé
- Prêt à lire quinze lignes d'Apex. Vous n'avez pas à en écrire : chaque bloc de ce lab est là pour être copié, et ce que le lab enseigne vraiment, c'est comment lire ce que les robots en disent
Les étapes¶
1. Prendre la story¶
New User Story (2), sous Project Contribution Workflow (1) du panneau DevOps
Pipeline. Branche US-027-schedule-by-availability, cible integration, org helios-dev.
2. Ajouter la modification telle que les gens l'écrivent vraiment¶
Le planificateur veut vérifier plusieurs installations d'un coup. Ouvrez
force-app/main/default/classes/InstallationScheduler.cls, le fichier que la pipeline déploie déjà,
et collez-y la méthode ci-dessous, exactement comme l'a fait le développeur qui l'a écrite en
premier. C'est la façon évidente de faire, et c'est bien le problème :
/**
* The installations from the list that can be scheduled on the given day.
*
* @param installationIds the installations to check
* @param wanted the day the planner wants
* @return the ids that can take a crew that day
*/
public static List<Id> schedulableOn(List<Id> installationIds, Date wanted) {
List<Id> allowed = new List<Id>();
for (Id installationId : installationIds) {
for (Panel_Batch__c batch : [
SELECT Arrival_Date__c
FROM Panel_Batch__c
WHERE Installation__c = :installationId
ORDER BY Arrival_Date__c DESC
LIMIT 1
]) {
if (batch.Arrival_Date__c != null && wanted >= batch.Arrival_Date__c.addDays(PREPARATION_DAYS)) {
allowed.add(installationId);
}
}
}
return allowed;
}
Celle-ci est un fichier, pas une modification d'org : il n'y a donc rien à récupérer. Commitez
InstallationScheduler.cls depuis le panneau Source Control, puis Save / Publish, poussez,
et ouvrez la Pull Request.
3. MegaLinter vous avertit¶
(Moderate) pmd:OperationWithLimitsInLoop force-app/main/default/classes/InstallationScheduler.cls
Avoid operations in loops that may hit governor limits
C'est dans le commentaire MegaLinter de votre Pull Request, sous code-analyzer-apex, au milieu de quelques trouvailles sur du code qui était déjà là avant vous. Le contrôle de déploiement à côté est vert : les tests exécutent toujours plus de 80 % de l'Apex de l'org, environ 81 %.
Une requête SOQL à l'intérieur d'une boucle for. Salesforce autorise 100 requêtes par transaction :
cette méthode marche donc parfaitement pour un planificateur qui vérifie cinq installations et lève
System.LimitException: Too many SOQL queries: 101 la première fois que quelqu'un en vérifie cent
une. Elle passera tous les tests que vous écrirez et échouera un lundi chargé.
La correction est le seul motif Apex qui mérite d'être connu par cœur : interroger une fois, en dehors de la boucle, et indexer ce qu'on récupère.
public static List<Id> schedulableOn(List<Id> installationIds, Date wanted) {
Map<Id, Date> latestArrival = new Map<Id, Date>();
for (Panel_Batch__c batch : [
SELECT Installation__c, Arrival_Date__c
FROM Panel_Batch__c
WHERE Installation__c IN :installationIds
AND Arrival_Date__c != null
ORDER BY Arrival_Date__c ASC
]) {
latestArrival.put(batch.Installation__c, batch.Arrival_Date__c);
}
List<Id> allowed = new List<Id>();
for (Id installationId : installationIds) {
Date arrival = latestArrival.get(installationId);
if (arrival != null && wanted >= arrival.addDays(PREPARATION_DAYS)) {
allowed.add(installationId);
}
}
return allowed;
}
Une seule requête, quelle que soit la taille de la liste.
Celle-ci avertit, elle ne bloque pas
L'analyseur Apex n'est pas bloquant sur ce projet : votre Pull Request reste mergeable avec cette trouvaille dessus. Rien ne vous empêche de livrer la boucle, sinon la lecture du commentaire. C'est un choix délibéré que fait un projet, et c'est pourquoi l'étape suivante est celle qui, elle, refuse vraiment.
4. Les tests vous bloquent¶
Poussez la correction. MegaLinter ne signale plus la boucle. Cette fois, c'est le contrôle de déploiement qui échoue, et celui-là n'est pas un conseil :
Le commentaire sfdx-hardis le dit aussi, en rouge : code coverage is insufficient. Salesforce seul l'aurait laissée passer, à 77 % pour son plancher de 75 % : le projet en demande davantage, et le job de contrôle l'y tient.
La version avec la boucle était courte, et l'org la portait à 81 %. La correction est plus longue, et
chacune de ses nouvelles lignes est une ligne qu'aucun test n'exécute. Vous avez ajouté une méthode à
trois branches et aucun test. Ajoutez-les dans
force-app/main/default/classes/InstallationSchedulerTest.cls :
@isTest
static void schedulableOnRefusesBeforeThePanelsArrive() {
Installation__c inst = [SELECT Id FROM Installation__c LIMIT 1];
Test.startTest();
List<Id> tooEarly = InstallationScheduler.schedulableOn(new List<Id>{ inst.Id }, Date.today());
List<Id> lateEnough = InstallationScheduler.schedulableOn(new List<Id>{ inst.Id }, Date.today().addDays(60));
Test.stopTest();
System.assert(tooEarly.isEmpty(), 'The crew cannot be sent before the panels arrive');
System.assertEquals(1, lateEnough.size(), 'A date after the buffer is allowed');
}
@isTest
static void schedulableOnIgnoresInstallationsWithNoBatch() {
Installation__c lonely = new Installation__c(Status__c = 'Planned', Crew_Size__c = 2, External_Id__c = 'TEST-INST-003');
insert lonely;
Test.startTest();
List<Id> allowed = InstallationScheduler.schedulableOn(new List<Id>{ lonely.Id }, Date.today().addDays(30));
Test.stopTest();
System.assert(allowed.isEmpty(), 'With no panel batch, nothing can be scheduled');
}
Remarquez ce que font les assertions : elles vérifient le comportement demandé par la story, avec un message qui dit pourquoi. Un test qui se contente d'exécuter le code pour faire monter un pourcentage est pire que pas de test, parce qu'il fait mentir le chiffre.
Le Crew_Size__c = 2 sur l'enregistrement que crée le second test n'est pas de la décoration. Le
Lab 2.3 a rendu ce champ obligatoire : tout test qui insère une installation sans lui échoue
désormais, et un test en échec est un déploiement en échec. Chaque classe de test de l'org doit être
passée en revue pour cela, et c'est le vrai prix à payer pour rendre un champ obligatoire.
5. Lancer les contrôles avant de pousser, cette fois¶
Deux allers-retours par la CI pour trouver deux choses que vous auriez pu trouver sur votre machine. Faites-le dans l'autre sens à partir de maintenant.
Les tests Apex tournent dans une org, et jusqu'ici votre modification n'existe que dans les
fichiers du projet : l'Apex de helios-dev est encore l'ancienne version. Envoyez-la là-bas d'abord.
Dans l'Explorer, clic droit sur InstallationScheduler.cls, puis SFDX: Deploy This Source to
Org, et faites de même pour InstallationSchedulerTest.cls. C'est l'extension Salesforce livrée
avec le pack d'extensions, et elle envoie ce seul fichier vers votre org par défaut.
Puis, sur la Welcome page, cliquez sur Org Monitoring. Dans la section Apex Tests & Security
du panneau qui s'ouvre, cliquez sur la carte Apex Tests (1) et choisissez helios-dev.
Elle lance les tests Apex de l'org et vérifie le même seuil de couverture que la pipeline : vous avez donc le succès, l'échec et le pourcentage sans rien pousser. Laissez-lui quelques minutes : une scratch org met ses exécutions de tests en file d'attente, et la première de la journée peut prendre dix minutes.
La bannière du haut est normale
Org Monitoring Not Present (CI/CD Repo) veut dire que ce repository est une pipeline de livraison et non un repository de monitoring. Les cartes en dessous fonctionnent quand même sur l'org que vous choisissez. Le Lab 3.8 est là où le monitoring obtient son propre repository.
L'onglet Apex Tests est une autre chose
Une Pull Request dans le panneau DevOps Pipeline peut afficher un onglet
Apex Tests (n) (beta). Il ne lance rien. Il choisit quelles classes de test le déploiement de
cette Pull Request va lancer, et il n'apparaît que sur les projets qui posent
enableDeploymentApexTestClasses. Helios ne le fait pas : il lance RunLocalTests, tous les
tests de l'org, à chaque fois.
Les linters : c'est le seul contrôle que vous ne pouvez pas utilement lancer sur votre machine, parce qu'il lui faut une image de conteneur que la CI possède déjà. Poussez, et lisez ce qu'ils disent sur la Pull Request. Cela fait deux minutes d'attente plutôt que vingt de mise en place, et le Niveau 3 montre la vue du release manager sur le même rapport.
Sous le capot : les deux commandes derrière ces cartes
La carte Apex Tests lance la même commande que la pipeline :
sf hardis:org:test:apex
et les linters, sur les machines de GitHub, lancent MegaLinter avec la saveur Salesforce. Si vous
voulez un jour les avoir sur votre machine, c'est npx mega-linter-runner --flavor salesforce, et
il faut Docker.
Documentation de la commande : hardis:org:test:apex
6. Pousser et merger¶
Les deux au vert. Mergez, et vérifiez helios-integration.
Sous le capot : d'où viennent ces deux barrières
La barrière de couverture est dans config/.sfdx-hardis.yml :
testLevel: RunLocalTests
apexTestsMinCoverageOrgWide: 80
testCoverageNotBlocking: false
RunLocalTests lance tous les tests de l'org sauf ceux des packages gérés. Le seuil est vérifié à
l'échelle de l'org, pas par classe, c'est pourquoi une classe mal couverte peut être portée par
le reste de l'org un certain temps puis bloquer soudain la Pull Request de quelqu'un d'autre. 75 %
est le minimum Salesforce, en dessous duquel aucun déploiement ne passe ; ce projet demande 80, et la
plupart des vrais projets posent 80 ou 85.
testCoverageNotBlocking: true transforme la barrière en avertissement. Elle existe pour les projets
qui reprennent une org historique, et c'est une mesure temporaire, pas un réglage.
Les linters sont MegaLinter, configuré dans .mega-linter.yml. La saveur Salesforce lance PMD
via Salesforce Code Analyzer sur l'Apex, plus un scanner de flows, plus les linters génériques. Il
tourne sur tout le repository pour une Pull Request vers une branche majeure, c'est pourquoi une
règle peut se déclencher sur un fichier que vous n'avez pas écrit.
Le linter, c'est votre équipe qui refuse, ou ici qui avertit. Le plancher de couverture, c'est Salesforce qui refuse, et le réglage du projet choisit seulement s'il faut en demander davantage. Ni l'un ni l'autre ne vérifie que le code fait la bonne chose, et c'est bien le sujet : Salesforce déploie très volontiers un identifiant en dur couvert à 100 %.
Ce que vous devez voir¶
- Le contrôle MegaLinter ne signalant aucune trouvaille
- Le contrôle de déploiement vert, avec une couverture au-dessus de 80 % dans le commentaire
schedulableOndanshelios-integration, avec une requête en dehors de la boucle
En cas de problème
MegaLinter échoue sur des fichiers que vous n'avez jamais touchés.
Il analyse tout le repository pour une Pull Request vers une branche majeure. Si un problème
préexistant fait surface, corrigez-le : c'est vous qui l'avez trouvé. Si c'est vraiment hors sujet,
la porte de sortie est une exclusion documentée dans .mega-linter.yml, jamais une désactivation en
bloc.
La couverture est toujours sous le seuil après l'ajout des tests. La couverture est à l'échelle de l'org. Regardez le tableau par classe dans le commentaire de la Pull Request : une autre classe peut tirer la moyenne vers le bas.
La carte Apex Tests dit qu'il n'y a pas d'org.
Elle tourne sur l'org vers laquelle vous êtes pointé. Ouvrez Orgs Manager et vérifiez que
helios-dev est votre org courante, puis relancez la carte.
Les tests Apex passent en local et échouent en CI. Presque toujours une histoire de données. Votre org de dev a des enregistrements que l'org d'intégration n'a pas, ou l'inverse. Un test doit créer lui-même les enregistrements dont il a besoin, plutôt que de faire confiance à ce qui se trouve dans l'org.
Vérifiez votre travail¶
Welcome page > Training: Level 2 > Check my work, puis choisissez le Lab 2.5.
Pour aller plus loin¶
Suite : Lab 2.6 - Permission sets, profils, et pourquoi une autorisation disparaît

