JustDummies

Juste des dummies, mais redoutablement efficaces.

Sections de la documentation

Erreurs et conflits

JustDummies préfère refuser bruyamment plutôt que renvoyer une valeur que personne ne saurait expliquer. Cette page décrit ce qu’elle refuse, ce que signifient les exceptions, et comment lire un message qui nomme les deux côtés d’une contradiction.

La hiérarchie d’exceptions

La hiérarchie d’exceptions de la bibliothèqueDummyException est abstraite et dérive d’Exception. Trois types concrets en dérivent. AnyGenerationException, quand un tirage n’a pas pu aboutir. ConflictingAnyConstraintException, quand les contraintes n’admettent aucune valeur. UnsupportedRegexException, quand le motif sort du sous-ensemble régulier.ExceptionDummyExceptionabstraite la racine de labibliothèqueAnyGenerationExceptionun tirage n’a pas pu aboutirConflictingAnyConstraintExceptionles contraintes n’admettentaucune valeurUnsupportedRegexExceptionle motif sort dusous-ensemble régulier

DummyException est abstraite : l’attraper attrape donc tout ce que cette bibliothèque lève, et rien d’autre :

try {
    int impossible = Any.Int32().Between(1, 10).MultipleOf(50).Generate();
} catch (DummyException exception) {
    Console.Error.WriteLine(exception.Message);
}

Les erreurs d’argument ordinaires ne font pas partie de cette hiérarchie. Passer null là où un générateur est attendu, ou une longueur négative, lève les habituelles ArgumentNullException / ArgumentException — ce sont des bogues du code appelant, pas des affirmations sur un jeu de contraintes.

ConflictingAnyConstraintException : aucune valeur possible

C’est celle que vous rencontrerez le plus, et c’est une fonctionnalité, non un défaut. Parce que les valeurs sont construites pour satisfaire toute la spécification au lieu d’être tirées puis filtrées, une spécification qui ne satisfait rien est détectée au lieu d’être parcourue en boucle :

// Aucun entier n'est à la fois supérieur à 100 et inférieur à 10.
int impossible = Any.Int32().GreaterThan(100).LessThan(10).Generate();

Le message nomme les deux côtés du conflit. C’est une garantie du produit, non un hasard de formulation : un message se contentant de dire « aucune valeur n’est possible » vous laisserait relire une chaîne de douze appels pour trouver lesquels se contredisent.

Les conflits prennent quelques formes reconnaissables :

FormeExemple
bornes qui se croisent.GreaterThan(100).LessThan(10)
un treillis sans point dans l’intervalle.Between(1, 10).MultipleOf(50)
des exclusions qui vident le domaineAny.Boolean().Except(true, false)
une longueur trop courte pour les fragments.StartingWith("ORDER-").WithLength(3)
un effectif qu’aucun vivier ne peut remplir100 valeurs distinctes depuis un vivier de trois

Attrapés à la compilation

Beaucoup de ces chaînes sont décidables à partir de constantes que le compilateur voit déjà, et les analyzers embarqués dans le paquet les signalent avant que le test ne s’exécute. C’est la différence entre un build rouge et un test rouge à trois heures du matin :

RègleDétecte
JD014un argument constant que la garde du générateur refuse
JD015une chaîne de caractères qui lève : fragments trop longs, ou value set vidé par une contrainte
JD016des effectifs de collection incompatibles entre eux
JD017une contrainte d’énumération sortant des membres déclarés
JD023une chaîne entière réduite à rien
JD024une contrainte qui ne restreint rien du tout

Les vérifications à l’exécution restent en place dans tous les cas : elles couvrent tout argument qu’un analyzer ne peut pas voir — calculé, lu dans un champ, ou reçu en paramètre.

AnyGenerationException : un tirage qui n’a pas abouti

Quelques contraintes ne peuvent pas être honorées par construction. Exclure des valeurs d’un intervalle continu, satisfaire une expression régulière et remplir une collection d’éléments distincts aboutissent au même endroit : tirer un candidat, le vérifier, recommencer s’il ne convient pas.

Sans borne, c’est une boucle qui peut ne jamais finir. JustDummies la borne — un nombre fixe de tentatives, puis un refus :

// Deux décimales entre 0 et 1 laissent 101 candidats ; en exclure 100 n'en laisse qu'un.
decimal[] excluded = Enumerable.Range(0, 100).Select(index => index / 100m).ToArray();

try {
    decimal awkward = Any.Decimal().Between(0m, 1m).WithScale(2).Except(excluded).Generate();
} catch (AnyGenerationException exception) {
    // exception.Seed porte la graine de l'exécution, si une graine était épinglée — l'échec se rejoue donc.
    Console.Error.WriteLine($"{exception.Message} (seed: {exception.Seed})");
}

AnyGenerationException porte une propriété Seed nullable. Quand le tirage a eu lieu dans une portée reproductible, la graine qui l’a produit figure sur l’exception : un échec de retirage borné est donc aussi rejouable que n’importe quel autre échec.

En rencontrer un signifie généralement que la spécification est plus serrée que prévu, et non que la bibliothèque a abandonné trop tôt. Élargissez l’intervalle, retirez une exclusion, ou demandez moins d’éléments distincts.

UnsupportedRegexException : hors du sous-ensemble régulier

Any.StringMatching construit une valeur à partir du motif au lieu de tester des candidats contre lui, et c’est pourquoi il peut garantir la correspondance. Construire exige que le motif soit régulier, et la bibliothèque le dit plutôt que de deviner :

try {
    // Une référence arrière n'est pas une construction régulière : aucun automate fini ne la porte.
    string impossible = Any.StringMatching(@"(\w+)\s\1").Generate();
} catch (UnsupportedRegexException exception) {
    Console.Error.WriteLine(exception.Message);
}

Les constructions acceptées — et celles refusées — sont listées dans Chaînes et motifs. La décision d’analyser un sous-ensemble régulier avec l’analyseur syntaxique de la bibliothèque, plutôt que de prendre une dépendance à un automate d’expressions régulières pour élargir la couverture, est ADR-0008 ouvre un nouvel onglet.

Symptôme, cause, remède

SymptômeCause probableRemède
ConflictingAnyConstraintException à la ligne d’arrangementdeux contraintes se contredisentlisez le message — il nomme les deux — et retirez celle qui n’est pas un invariant du domaine
AnyGenerationException après une pauseun retirage borné a épuisé ses tentativesélargissez le domaine, ou demandez moins de valeurs distinctes
UnsupportedRegexExceptionle motif utilise une construction non régulièreréécrivez-le dans le sous-ensemble régulier, ou construisez la chaîne avec les contraintes d’Any.String()
une valeur que votre fabrique refuseles contraintes sont plus lâches que la fabriqueresserrez les contraintes jusqu’à ce qu’elles impliquent le contrat de la fabrique
un test qui passe à la relanceles valeurs en échec ont disparuenveloppez le corps dans Any.Reproducibly pour que le prochain échec nomme sa graine
un avertissement de build JD0NNune erreur décidable à la compilationouvrez la page de règle liée depuis le diagnostic

Lire la source, ou la corriger là-bas ouvre un nouvel onglet· Repris depuis lib-v1.0.0-preview.6