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);
}