127 votes

Comment savoir ce qu'il faut tester quand on écrit des tests unitaires ?

En utilisant C#, j'ai besoin d'une classe appelée User qui a un nom d'utilisateur, un mot de passe, un drapeau actif, un prénom, un nom de famille, un nom complet, etc.

Il devrait y avoir des méthodes pour authentifier y sauver un utilisateur. Dois-je simplement écrire un test pour les méthodes ? Et dois-je me soucier de tester les propriétés puisqu'il s'agit des getter et setters de .Net ?

130voto

Rob Cooper Points 15945

Beaucoup d'excellentes réponses à cette question se trouvent également sur ma question : " Début du TDD - Des défis ? Solutions ? Recommandations ? "

Je vous recommande également de jeter un coup d'oeil à mon article de blog (qui était en partie inspirée par ma question), j'ai reçu de bons retours à ce sujet. A savoir :

Je ne sais pas par où commencer ?

  • Recommencez à zéro. Ne pensez à l'écriture de tests que lorsque vous écrivez de nouvelles applications. nouveau code. Il peut s'agir d'un remaniement d'un ancien code, ou d'une toute nouvelle fonctionnalité.
  • Commencez simplement. Ne vous lancez pas dans une course effrénée en essayant de vous faire la main sur un cadre de test tout en étant TDD-esque. Debug.Assert fonctionne bien. Utilisez-le comme point de départ. Il ne votre projet ou ne crée pas de dépendances.
  • Commencez par être positif. Vous essayez d'améliorer votre métier, soyez positif. bien. J'ai vu beaucoup de développeurs qui sont heureux de stagner et ne pas essayer de nouvelles choses pour s'améliorer pour s'améliorer. Vous faites ce qu'il faut chose, souvenez-vous-en et cela vous aidera vous empêcher d'abandonner.
  • Commencez par être prêt à relever le défi. Il est assez difficile de se lancer dans tests. Attendez-vous à un défi, mais mais n'oubliez pas que les défis peuvent être relevés.

Ne testez que ce à quoi vous vous attendez

J'ai eu de vrais problèmes quand j'ai commencé commencé parce que j'étais constamment assis à essayer de résoudre tous les problèmes possibles qui pourraient survenir et puis d'essayer de le tester et de le résoudre. C'est le moyen le plus rapide d'avoir mal à la tête. Les tests devraient être un véritable YAGNI réel. Si vous savez qu'il y a un problème, écrivez un test pour celui-ci. Sinon, ne vous donnez pas la peine.

Ne testez qu'une seule chose

Chaque scénario de test ne devrait jamais tester que une seule chose. Si vous vous retrouvez de mettre "et" dans le nom du scénario de test, vous faites quelque chose de mal.

J'espère que cela signifie que nous pouvons passer à autre chose que "getters and setters" :)

63voto

Tim Howland Points 5705

Testez votre code, pas le langage.

Un test unitaire comme :

Integer i = new Integer(7);
assert (i.instanceOf(integer));

n'est utile que si vous écrivez un compilateur et qu'il y a une chance non nulle pour que votre instanceof ne fonctionne pas.

Ne testez pas les choses que vous pouvez compter sur le langage pour faire respecter. Dans votre cas, je me concentrerais sur vos méthodes d'authentification et de sauvegarde - et j'écrirais des tests pour m'assurer qu'elles peuvent gérer les valeurs nulles dans l'un ou l'autre de ces champs.

38voto

eroijen Points 101

Cela m'a permis de me lancer dans les tests unitaires et cela m'a rendu très heureux.

Nous venons de commencer à faire des tests unitaires. Depuis longtemps, je savais qu'il serait bon de commencer à le faire, mais je n'avais aucune idée de la façon de commencer et, surtout, de ce qu'il fallait tester.

Puis nous avons dû réécrire une partie importante du code de notre programme de comptabilité. Cette partie était très complexe car elle impliquait un grand nombre de scénarios différents. La partie dont je parle est une méthode permettant de payer les factures de vente et/ou d'achat déjà saisies dans le système comptable.

Je ne savais pas comment commencer à le coder, car il y avait tellement d'options de paiement différentes. Une facture peut s'élever à 100 $, mais le client n'a transféré que 99 $. Vous avez peut-être envoyé des factures à un client, mais vous avez également effectué un achat auprès de ce client. Ainsi, vous lui avez vendu pour 300 $ mais vous avez acheté pour 100 $. Vous pouvez vous attendre à ce que votre client vous verse 200 $ pour régler le solde. Et que se passe-t-il si vous avez vendu pour 500 $ mais que le client ne vous verse que 250 $ ?

J'avais donc un problème très complexe à résoudre, avec de nombreuses possibilités qu'un scénario fonctionne parfaitement, mais qu'il soit faux pour un autre type de combinaison invocation/paiement.

C'est là que les tests unitaires sont venus à la rescousse.

J'ai commencé à écrire (dans le code de test) une méthode pour créer une liste de factures, à la fois pour les ventes et les achats. Puis j'ai écrit une deuxième méthode pour créer le paiement proprement dit. Normalement, un utilisateur devrait entrer ces informations via une interface utilisateur.

J'ai ensuite créé le premier TestMethod, pour tester un paiement très simple d'une seule facture sans aucune remise. Toutes les actions du système se produiraient lorsqu'un paiement bancaire serait enregistré dans la base de données. Comme vous pouvez le voir, j'ai créé une facture, créé un paiement (une transaction bancaire) et enregistré la transaction sur le disque. Dans mes assertions, je mets ce qui devrait être les nombres corrects aboutissant dans la transaction bancaire et dans la facture liée. Je vérifie le nombre de paiements, les montants des paiements, le montant de l'escompte et le solde de la facture après la transaction.

Après l'exécution du test, j'allais dans la base de données et vérifiais que ce que j'attendais s'y trouvait.

Après J'ai écrit le test, j'ai commencé à coder la méthode de paiement (qui fait partie de la classe BankHeader). Lors du codage, je ne me suis occupé que du code permettant de faire passer le premier test. Je n'ai pas encore pensé aux autres scénarios, plus complexes.

J'ai lancé le premier test, corrigé un petit bug jusqu'à ce que mon test passe.

J'ai ensuite commencé à écrire le deuxième test, en travaillant cette fois-ci avec une remise de paiement. Après avoir écrit le test, j'ai modifié le mode de paiement pour qu'il prenne en charge les remises.

Tout en testant l'exactitude avec une remise de paiement, j'ai également testé le paiement simple. Les deux tests devraient bien sûr passer.

Puis j'ai travaillé sur les scénarios les plus complexes.

1) Pensez à un nouveau scénario

2) Écrire un test pour ce scénario

3) Exécuter ce seul test pour voir s'il passe.

4) Si ce n'était pas le cas, je déboguais et modifiais le code jusqu'à ce qu'il passe.

5) Tout en modifiant le code, j'ai continué à exécuter tous les tests.

C'est ainsi que j'ai réussi à créer ma méthode de paiement très complexe. Sans tests unitaires, je ne savais pas comment commencer à coder, le problème semblait insurmontable. Grâce aux tests, j'ai pu commencer par une méthode simple et l'étendre étape par étape, avec l'assurance que les scénarios les plus simples fonctionneraient toujours.

Je suis sûr que l'utilisation des tests unitaires m'a permis d'économiser quelques jours (ou semaines) de codage et garantit plus ou moins l'exactitude de ma méthode.

Si, plus tard, je pense à un nouveau scénario, je peux simplement l'ajouter aux tests pour voir s'il fonctionne ou non. Si ce n'est pas le cas, je peux modifier le code tout en m'assurant que les autres scénarios fonctionnent toujours correctement. Cela permettra de gagner des jours et des jours dans la phase de maintenance et de correction des bogues.

Oui, même un code testé peut toujours comporter des bogues si un utilisateur fait des choses auxquelles vous n'avez pas pensé ou que vous l'avez empêché de faire.

Vous trouverez ci-dessous quelques-uns des tests que j'ai créés pour tester mon mode de paiement.

public class TestPayments
{
    InvoiceDiaryHeader invoiceHeader = null;
    InvoiceDiaryDetail invoiceDetail = null;
    BankCashDiaryHeader bankHeader = null;
    BankCashDiaryDetail bankDetail = null;

    public InvoiceDiaryHeader CreateSales(string amountIncVat, bool sales, int invoiceNumber, string date)
    {
        ......
        ......
    }

    public BankCashDiaryHeader CreateMultiplePayments(IList<InvoiceDiaryHeader> invoices, int headerNumber, decimal amount, decimal discount)
    {
       ......
       ......
       ......
    }

    [TestMethod]
    public void TestSingleSalesPaymentNoDiscount()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 1, "01-09-2008"));
        bankHeader = CreateMultiplePayments(list, 1, 119.00M, 0);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(1, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(119M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(0M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
    }

    [TestMethod]
    public void TestSingleSalesPaymentDiscount()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 2, "01-09-2008"));
        bankHeader = CreateMultiplePayments(list, 2, 118.00M, 1M);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(1, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(118M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(1M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
    }

    [TestMethod]
    [ExpectedException(typeof(ApplicationException))]
    public void TestDuplicateInvoiceNumber()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("100", true, 2, "01-09-2008"));
        list.Add(CreateSales("200", true, 2, "01-09-2008"));

        bankHeader = CreateMultiplePayments(list, 3, 300, 0);
        bankHeader.Save();
        Assert.Fail("expected an ApplicationException");
    }

    [TestMethod]
    public void TestMultipleSalesPaymentWithPaymentDiscount()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 11, "01-09-2008"));
        list.Add(CreateSales("400", true, 12, "02-09-2008"));
        list.Add(CreateSales("600", true, 13, "03-09-2008"));
        list.Add(CreateSales("25,40", true, 14, "04-09-2008"));

        bankHeader = CreateMultiplePayments(list, 5, 1144.00M, 0.40M);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(4, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(118.60M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(400, bankHeader.BankCashDetails[0].Payments[1].PaymentAmount);
        Assert.AreEqual(600, bankHeader.BankCashDetails[0].Payments[2].PaymentAmount);
        Assert.AreEqual(25.40M, bankHeader.BankCashDetails[0].Payments[3].PaymentAmount);

        Assert.AreEqual(0.40M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[2].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].PaymentDiscount);

        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[2].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].InvoiceHeader.Balance);
    }

    [TestMethod]
    public void TestSettlement()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("300", true, 43, "01-09-2008")); //Sales
        list.Add(CreateSales("100", false, 6453, "02-09-2008")); //Purchase

        bankHeader = CreateMultiplePayments(list, 22, 200, 0);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(2, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(300, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(-100, bankHeader.BankCashDetails[0].Payments[1].PaymentAmount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].InvoiceHeader.Balance);
    }

13voto

Steve Cooper Points 6637

S'ils sont vraiment insignifiants, ne prenez pas la peine de les tester. Par exemple, s'ils sont implémentés comme ceci ;

public class User
{
    public string Username { get; set; }
    public string Password { get; set; }
}

Si, par contre, vous faites quelque chose d'intelligent (comme crypter et décrypter le mot de passe dans le getter/setter), faites un essai.

10voto

Slavo Points 5585

La règle est que vous devez tester chaque élément de logique que vous écrivez. Si vous avez implémenté une fonctionnalité spécifique dans les getters et setters, je pense qu'ils méritent d'être testés. S'ils ne font qu'attribuer des valeurs à certains champs privés, ne vous donnez pas la peine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X