Domanda

Ho del codice (C#) che si basa sulla data odierna per calcolare correttamente le cose in futuro.Se utilizzo la data odierna nel test, devo ripetere il calcolo nel test, il che non mi sembra corretto.Qual è il modo migliore per impostare la data su un valore noto all'interno del test in modo da poter verificare che il risultato sia un valore noto?

È stato utile?

Soluzione

La mia preferenza è che le classi che utilizzano il tempo si basino effettivamente su un'interfaccia, come

interface IClock
{
    DateTime Now { get; } 
}

Con un'implementazione concreta

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

Quindi, se lo desideri, puoi fornire qualsiasi altro tipo di orologio che desideri per il test, ad esempio

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

Potrebbe esserci un sovraccarico nel fornire l'orologio alla classe che si basa su di esso, ma ciò potrebbe essere gestito da un numero qualsiasi di soluzioni di iniezione delle dipendenze (usando un contenitore Inversion of Control, una semplice vecchia iniezione di costruttore/setter o anche un Modello gateway statico).

Anche altri meccanismi per fornire un oggetto o un metodo che fornisce i tempi desiderati funzionano, ma penso che la cosa fondamentale sia evitare di reimpostare l'orologio di sistema, poiché ciò introdurrà solo dolore ad altri livelli.

Inoltre, utilizzando DateTime.Now e includerlo nei tuoi calcoli non solo non ti sembra giusto: ti priva della capacità di testare orari particolari, ad esempio se scopri un bug che si verifica solo vicino al limite di mezzanotte o il martedì.L'utilizzo dell'ora corrente non ti consentirà di testare tali scenari.O almeno non quando vuoi.

Altri suggerimenti

Ayende Rahien usi un metodo statico piuttosto semplice...

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

Penso che creare una classe orologio separata per qualcosa di semplice come ottenere la data corrente sia un po' eccessivo.

Puoi passare la data odierna come parametro in modo da poter inserire una data diversa nel test.Ciò ha l'ulteriore vantaggio di rendere il codice più flessibile.

Usare Microsoft Fakes per creare uno spessore è un modo davvero semplice per farlo.Supponiamo che io abbia avuto la seguente lezione:

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}

In Visual Studio 2012 puoi aggiungere un assembly Fakes al tuo progetto di test facendo clic con il pulsante destro del mouse sull'assembly per il quale desideri creare Fakes/Shims e selezionando "Aggiungi assembly Fakes"

Adding Fakes Assembly

Infine, ecco come apparirebbe la classe di 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 timeString = myClass.WhatsTheTime();

            //Assert
            Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);

        }
    }
}
}

La chiave per il successo dei test unitari è disaccoppiamento.Devi separare il tuo codice interessante dalle sue dipendenze esterne, in modo che possa essere testato isolatamente.(Fortunatamente, il Test-Driven Development produce codice disaccoppiato.)

In questo caso, il tuo esterno è il DateTime corrente.

Il mio consiglio qui è di estrarre la logica che gestisce DateTime in un nuovo metodo o classe o qualunque cosa abbia senso nel tuo caso e passare DateTime.Ora il test unitario può superare un DateTime arbitrario per produrre risultati prevedibili.

Un altro che utilizza Microsoft Moles (Framework di isolamento per .NET).

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

Le moli consente di sostituire qualsiasi metodo .NET con un delegato.Le moli supportano metodi statici o non virtuali.Le mole si basano sul profiler di PEX.

Suggerirei di utilizzare il modello IDisposable:

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

Descritto in dettaglio qui:http://www.lesnikowski.com/blog/index.php/testing-datetime-now/

Risposta semplice:abbandona System.DateTime :) Invece, usa NodaTime e sta testando la libreria: NodaTime.Testing.

Ulteriori letture:

Potresti iniettare la classe (meglio:metodo/delegare) per cui usi DateTime.Now nella classe sottoposta al test.Avere DateTime.Now essere un valore predefinito e impostarlo solo in testing su un metodo fittizio che restituisce un valore costante.

MODIFICARE: Quello che ha detto Blair Conrad (ha del codice da guardare).Tranne che per questo tendo a preferire i delegati, poiché non ingombrano la gerarchia di classi con cose del genere IClock...

Ho affrontato questa situazione così spesso che ho creato una semplice nuget che espone Ora proprietà tramite l'interfaccia.

public interface IDateTimeTools
{
    DateTime Now { get; }
}

L'implementazione è ovviamente molto semplice

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

Quindi, dopo aver aggiunto nuget al mio progetto, posso usarlo negli unit test

enter image description here

È possibile installare il modulo direttamente dalla GUI Nuget Package Manager o utilizzando il comando:

Install-Package -Id DateTimePT -ProjectName Project

E il codice per Nuget è Qui.

L'esempio di utilizzo con Autofac può essere trovato Qui.

Hai preso in considerazione l'utilizzo della compilazione condizionale per controllare cosa accade durante il debug/distribuzione?

per esempio.

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

In caso contrario, vuoi esporre la proprietà in modo da poterla manipolare, tutto questo fa parte della sfida della scrittura testabile code, che è qualcosa su cui attualmente sto lottando: D

Modificare

Gran parte di me preferirebbe L'approccio di Blair.Ciò consente di "collegare a caldo" parti del codice per facilitare il test.Tutto segue il principio del design incapsulare ciò che varia il codice di test non è diverso dal codice di produzione, semplicemente nessuno lo vede mai esternamente.

Tuttavia, la creazione e l'interfaccia possono sembrare molto lavoro per questo esempio (motivo per cui ho optato per la compilazione condizionale).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top