Quelle est la bonne façon de remplacer DateTime.Now pendant les tests?

J’ai un code (C #) qui repose sur la date d’aujourd’hui pour calculer correctement les choses à l’avenir. Si j’utilise la date d’aujourd’hui dans le test, je dois répéter le calcul dans le test, ce qui ne semble pas correct. Quelle est la meilleure façon de définir la date sur une valeur connue dans le test afin de pouvoir vérifier que le résultat est une valeur connue?

Je préfère que les classes utilisant le temps reposent sur une interface, telle que

interface IClock { DateTime Now { get; } } 

Avec une mise en œuvre concrète

 class SystemClock: IClock { DateTime Now { get { return DateTime.Now; } } } 

Ensuite, si vous le souhaitez, vous pouvez fournir tout autre type d’horloge que vous souhaitez tester, par exemple:

 class StaticClock: IClock { DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } } } 

Il peut y avoir une surcharge dans la fourniture de l’horloge à la classe qui en dépend, mais cela pourrait être géré par un nombre illimité de solutions d’dependency injections (en utilisant un conteneur Inversion of Control, un vieux constructeur / injecteur ou même un modèle de passerelle statique) ).

D’autres mécanismes pour fournir un object ou une méthode qui fournit les temps souhaités fonctionnent également, mais je pense que l’essentiel est d’éviter de réinitialiser l’horloge du système, car cela ne fera qu’introduire de la douleur à d’autres niveaux.

En outre, utiliser DateTime.Now et l’inclure dans vos calculs ne vous semble pas juste – cela vous prive de la possibilité de tester des temps particuliers, par exemple si vous découvrez un bogue qui se produit à une limite de minuit ou le mardi. L’utilisation de l’heure actuelle ne vous permettra pas de tester ces scénarios. Ou du moins pas quand vous voulez.

Ayende Rahien utilise une méthode statique plutôt simple …

 public static class SystemTime { public static Func Now = () => DateTime.Now; } 

Je pense que créer une classe d’horloge séparée pour quelque chose de simple comme obtenir la date du jour est un peu exagéré.

Vous pouvez passer la date d’aujourd’hui en tant que paramètre afin que vous puissiez saisir une date différente dans le test. Cela a l’avantage supplémentaire de rendre votre code plus flexible.

Utiliser Microsoft Fakes pour créer une cale est un moyen très simple de le faire. Supposons que j’ai la classe suivante:

 public class MyClass { public ssortingng WhatsTheTime() { return DateTime.Now.ToSsortingng(); } } 

Dans Visual Studio 2012, vous pouvez append un assemblage Fakes à votre projet de test en cliquant avec le bouton droit sur l’assemblage pour lequel vous souhaitez créer des Fakes / Shims et en sélectionnant «Add Fakes Assembly».

Ajout d'un assemblage de faux

Enfin, voici à quoi ressemblerait la classe de test:

 using System; using ConsoleApplication11; using Microsoft.QualityTools.Testing.Fakes; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace DateTimeTest { [TestClass] public class UnitTest1 { [TestMethod] public void TestWhatsTheTime() { using(ShimsContext.Create()){ //Arrange System.Fakes.ShimDateTime.NowGet = () => { return new DateTime(2010, 1, 1); }; var myClass = new MyClass(); //Act var timeSsortingng = myClass.WhatsTheTime(); //Assert Assert.AreEqual("1/1/2010 12:00:00 AM",timeSsortingng); } } } } 

La clé du succès des tests unitaires est le découplage . Vous devez séparer votre code intéressant de ses dépendances externes afin de pouvoir le tester isolément. (Heureusement, le développement piloté par les tests produit un code découplé.)

Dans ce cas, votre externe est la DateTime actuelle.

Mon conseil ici est d’extraire la logique qui traite le DateTime vers une nouvelle méthode ou classe ou tout ce qui a du sens dans votre cas, et de passer le DateTime. Maintenant, votre test unitaire peut passer un DateTime arbitraire pour produire des résultats prévisibles.

Un autre utilisant Microsoft Moles ( framework d’isolation pour .NET ).

 MDateTime.NowGet = () => new DateTime(2000, 1, 1); 

Moles permet de remplacer toute méthode .NET par un délégué. Moles prend en charge les méthodes statiques ou non virtuelles. Moles s’appuie sur le profileur de Pex.

Je suggère d’utiliser le modèle IDisposable:

 [Test] public void CreateName_AddsCurrentTimeAtEnd() { using (Clock.NowIs(new DateTime(2010, 12, 31, 23, 59, 00))) { ssortingng name = new ReportNameService().CreateName(...); Assert.AreEqual("name 2010-12-31 23:59:00", name); } } 

En détail décrit ici: http://www.lesnikowski.com/blog/index.php/testing-datetime-now/

Réponse simple: ditch System.DateTime 🙂 Utilisez plutôt NodaTime et sa bibliothèque de test: NodaTime.Testing .

Lectures complémentaires:

  • Test unitaire avec Noda Time
  • Injection de dépendance: Injectez vos dépendances, période!

Vous pouvez injecter la classe (mieux: méthode / delegate ) que vous utilisez pour DateTime.Now dans la classe testée. Avoir DateTime.Now être une valeur par défaut et la définir uniquement lors du test sur une méthode factice qui renvoie une valeur constante.

EDIT: Ce que Blair Conrad a dit (il a du code à regarder). Sauf que j’ai tendance à préférer les delegates pour cela, car ils n’encombrent pas votre hiérarchie de classes avec des choses comme IClock

J’ai fait face à cette situation si souvent que j’ai créé un nuget simple qui expose la propriété Now via l’interface.

 public interface IDateTimeTools { DateTime Now { get; } } 

La mise en œuvre est bien sûr très simple

 public class DateTimeTools : IDateTimeTools { public DateTime Now => DateTime.Now; } 

Donc, après avoir ajouté nuget à mon projet, je peux l’utiliser dans les tests unitaires

entrer la description de l'image ici

Vous pouvez installer le module directement depuis le GUI Nuget Package Manager ou en utilisant la commande:

 Install-Package -Id DateTimePT -ProjectName Project 

Et le code pour le Nuget est ici .

L’exemple d’utilisation avec l’Autofac peut être trouvé ici .

Avez-vous envisagé d’utiliser une compilation conditionnelle pour contrôler ce qui se passe pendant le débogage / déploiement?

par exemple

 DateTime date; #if DEBUG date = new DateTime(2008, 09, 04); #else date = DateTime.Now; #endif 

A défaut, vous voulez exposer la propriété afin de pouvoir la manipuler, tout cela fait partie du défi de l’écriture de code testable , ce que je suis en train de lutter: D

modifier

Une grande partie de moi préférerait l’approche de Blair . Cela vous permet de twigr des parties du code pour faciliter les tests. Tout suit le principe de conception encapsuler ce qui varie le code de test n’est pas différent du code de production, c’est juste personne ne le voit jamais en externe.

La création et l’interface peuvent sembler beaucoup de travail pour cet exemple (c’est pourquoi j’ai opté pour la compilation conditionnelle).