Classe de base pour tests unitaires NUnit avec affichage de DataTables dans la console

Dans les tests unitaires avec NUnit, la sortie console se limite souvent à du texte brut. Pour visualiser des données structurées comme des DataTables, une implémentation personnalisée est nécessaire. Cet article présente une classe de base qui permet d'afficher des DataTables de manière formatée dans la console lors des tests.

La clasce BaseTesteur est conçue comme classe de base non décorée avec [TestFixture], destinée à être héritée par d'autres classes de test NUnit. Elle encapsule trois éléments principaux :

  • AfficherTable(DataTable dt) : Affiche le contenu d'une DataTable avec un formatage tableau dans la console.
  • Dire(string str) : Une méthode simplifiée qui enveloppe Console.WriteLine() pour une sortie concise.
  • LongueurMaxCellule : Une propriété contrôlant la longueur maximale des chaînes dans les cellules, définie par défaut à 25 caractères.

La méthode AfficherTable vise à : afficher les données du tableau, inclure les noms des colonnes, adapter automatiquement la largeur des colonnes en fonction du contenu le plus long, et utiliser des caractères de tabulation pour dessiner les bordures. L'adaptation des largeurs pose un défi en raison des différences de largeur entre caractères chinois (environ 13 pixels) et anglais (7 pixels) dans la console NUnit. L'approche adoptée calcule une largeur approximative basée sur le ratio de caractères, bien que l'alignement parfait ne soit pas toujours atteint.

Voici une implémentation révisée de la classe de base avec des noms de variables et une structure modifiés :

/// <summary>
/// Classe de base pour l'affichage de données dans les tests NUnit.
/// Fournit des méthodes pour afficher du texte et des DataTables.
/// </summary>
public class BaseTesteur
{
    private const int LongueurDefautCellule = 25;
    private int _longueurMaxCellule = LongueurDefautCellule;

    public int LongueurMaxCellule
    {
        get { return _longueurMaxCellule; }
        set { _longueurMaxCellule = value; }
    }

    public void Dire(string message)
    {
        Console.WriteLine(message);
    }

    private List<int> _largeursColonnes = new List<int>();

    public void AfficherTable(DataTable table)
    {
        var analyseurTexte = new AnalyseurTexte();
        int nombreColonnes = table.Columns.Count;

        // Calculer les largeurs optimales pour chaque colonne
        for (int col = 0; col < nombreColonnes; col++)
        {
            analyseurTexte.Contenu = table.Columns[col].ColumnName;
            int largeurColonne = CalculerLargeurPixel(analyseurTexte);

            for (int ligne = 0; ligne < table.Rows.Count; ligne++)
            {
                string contenuCellule = table.Rows[ligne][col].ToString();
                if (table.Columns[col].DataType == typeof(string) && contenuCellule.Length > LongueurMaxCellule)
                {
                    contenuCellule = contenuCellule.Substring(0, LongueurMaxCellule);
                    table.Rows[ligne][col] = contenuCellule;
                }

                analyseurTexte.Contenu = contenuCellule;
                int largeurActuelle = CalculerLargeurPixel(analyseurTexte);
                largeurColonne = Math.Max(largeurColonne, largeurActuelle);
            }
            largeurColonne++; // Ajouter un espace de padding
            _largeursColonnes.Add(largeurColonne);
        }

        // Construire la représentation du tableau avec des caractères de dessin
        char[] coins = { '┌', '├', '└' };
        char[] lignesHorizontales = { '─', '─', '─' };
        char[] intersections = { '┬', '┼', '┴' };
        char[] barresVerticales = { '│', '│', '│' };
        char[] coinsDroits = { '┐', '┤', '┘' };

        var constructeur = new StringBuilder();
        Dire("--- Affichage des données du tableau ---");

        // Dessiner l'en-tête
        DessinerLigneHaut(constructeur, nombreColonnes, coins[0], intersections[0], coinsDroits[0], lignesHorizontales[0]);
        AjouterLigneCellules(constructeur, table.Columns.Cast<DataColumn>().Select(c => c.ColumnName), nombreColonnes, barresVerticales[0]);
        constructeur.Append('\n');

        // Dessiner les lignes de données
        foreach (DataRow ligne in table.Rows)
        {
            DessinerLigneMilieu(constructeur, nombreColonnes, coins[1], intersections[1], coinsDroits[1], lignesHorizontales[1]);
            var valeurs = Enumerable.Range(0, nombreColonnes).Select(i => ligne[i].ToString());
            AjouterLigneCellules(constructeur, valeurs, nombreColonnes, barresVerticales[0]);
            constructeur.Append("\n");
        }

        // Dessiner le bas du tableau
        DessinerLigneBas(constructeur, nombreColonnes, coins[2], intersections[2], coinsDroits[2], lignesHorizontales[2]);

        Console.Write(constructeur.ToString());
    }

    private int CalculerLargeurPixel(AnalyseurTexte analyseur)
    {
        // Calcul approximatif basé sur 13 pixels par caractère chinois et 7 pixels par caractère anglais
        return (analyseur.NombreCaracteresChinois * 13 + analyseur.NombreCaracteresAnglais * 7) / 13;
    }

    private void DessinerLigneHaut(StringBuilder sb, int colonnes, char coinGauche, char intersection, char coinDroit, char ligneHorizontale)
    {
        sb.Append(coinGauche);
        for (int i = 0; i < colonnes; i++)
        {
            if (i != 0) sb.Append(intersection);
            sb.Append(ligneHorizontale, _largeursColonnes[i]);
        }
        sb.Append(coinDroit);
        sb.Append('\n');
    }

    private void AjouterLigneCellules(StringBuilder sb, IEnumerable<string> valeurs, int colonnes, char barreVerticale)
    {
        int index = 0;
        foreach (string valeur in valeurs)
        {
            sb.Append(barreVerticale);
            sb.Append(AjusterLongueur(valeur, _largeursColonnes[index]));
            index++;
        }
        sb.Append(barreVerticale);
    }

    private string AjusterLongueur(string texte, int longueurCible)
    {
        var analyseur = new AnalyseurTexte(texte);
        int largeurPixelsActuelle = analyseur.NombreCaracteresChinois * 13 + analyseur.NombreCaracteresAnglais * 7;
        int largeurPixelsCible = longueurCible * 13;
        int difference = largeurPixelsCible - largeurPixelsActuelle;

        if (analyseur.NombreCaracteresAnglais > 0 && difference > 7)
        {
            int espacesAnglais = difference / 7;
            texte = texte + new string(' ', espacesAnglais);
        }
        else if (analyseur.NombreCaracteresChinois > 0 && difference > 13)
        {
            int espacesChinois = difference / 13;
            texte = texte + new string(' ', espacesChinois);
        }

        return texte;
    }

    // Méthodes auxiliaires pour dessiner les lignes restantes (DessinerLigneMilieu, DessinerLigneBas) implémentées de manière similaire
    private void DessinerLigneMilieu(StringBuilder sb, int colonnes, char coinGauche, char intersection, char coinDroit, char ligneHorizontale)
    {
        sb.Append(coinGauche);
        for (int i = 0; i < colonnes; i++)
        {
            if (i != 0) sb.Append(intersection);
            sb.Append(ligneHorizontale, _largeursColonnes[i]);
        }
        sb.Append(coinDroit);
        sb.Append('\n');
    }

    private void DessinerLigneBas(StringBuilder sb, int colonnes, char coinGauche, char intersection, char coinDroit, char ligneHorizontale)
    {
        sb.Append(coinGauche);
        for (int i = 0; i < colonnes; i++)
        {
            if (i != 0) sb.Append(intersection);
            sb.Append(ligneHorizontale, _largeursColonnes[i]);
        }
        sb.Append(coinDroit);
        sb.Append('\n');
    }
}
</int>

La classe AnalyseurTexte est utilisée pour analyser les chaînes de caractères et compter les caractères chinois et anglais. Voici une version modifiée avec une logique simplifiée :

/// <summary>
/// Analyse une chaîne pour compter les caractères par type (chinois, anglais).
/// </summary>
public class AnalyseurTexte
{
    private string _contenu;

    public string Contenu
    {
        get { return _contenu; }
        set
        {
            _contenu = value;
            Analyser();
        }
    }

    public int NombreCaracteresChinois { get; private set; }
    public int NombreCaracteresAnglais { get; private set; }

    public AnalyseurTexte()
    {
        _contenu = string.Empty;
    }

    public AnalyseurTexte(string texte)
    {
        _contenu = texte;
        Analyser();
    }

    private void Analyser()
    {
        NombreCaracteresChinois = 0;
        NombreCaracteresAnglais = 0;

        if (string.IsNullOrEmpty(_contenu)) return;

        for (int i = 0; i < _contenu.Length; i++)
        {
            // Détecter les caractères chinois par la taille en octets (méthode simplifiée)
            byte[] octets = Encoding.Default.GetBytes(_contenu.Substring(i, 1));
            if (octets.Length > 1)
            {
                NombreCaracteresChinois++;
            }
            else
            {
                NombreCaracteresAnglais++;
            }
        }
    }
}

Cette approche offre une solution fonctionnelle pour afficher des DataTables dans la console lors des tests unitaires, malgré les limitations d'alignement dues aux différences de largeur des caractères.

Étiquettes: NUnit C# DataTable tests unitaires affichage console

Publié le 20 juin à 16h52