Test sırasında DateTime.Now üzerine yazmanın iyi bir yolu nedir?


116

Gelecekteki şeyleri doğru bir şekilde hesaplamak için bugünün tarihine dayanan bazı (C #) kodum var. Testte bugünün tarihini kullanırsam testte hesaplamayı tekrar etmem gerekir ki bu doğru gelmiyor. Sonucun bilinen bir değer olup olmadığını test edebilmem için tarihi test içinde bilinen bir değere ayarlamanın en iyi yolu nedir?

Yanıtlar:


157

Tercihim, zamanı kullanan sınıflara sahip olmaktır, örneğin

interface IClock
{
    DateTime Now { get; } 
}

Somut bir uygulama ile

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

Daha sonra isterseniz, test etmek için istediğiniz diğer herhangi bir saati sağlayabilirsiniz.

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

Saatin kendisine dayanan sınıfa sağlanmasında bazı ek yükler olabilir, ancak bu, herhangi bir sayıda bağımlılık enjeksiyon çözümüyle (bir Kontrolün Tersine Çevirme konteyneri, düz eski yapıcı / ayarlayıcı enjeksiyonu veya hatta bir Statik Ağ Geçidi Modeli kullanılarak) idare edilebilir. ).

İstenen süreleri sağlayan bir nesneyi veya yöntemi sunmanın diğer mekanizmaları da işe yarıyor, ancak bence önemli olan sistem saatini sıfırlamaktan kaçınmak, çünkü bu sadece diğer seviyelerde ağrıya neden olacak.

Ayrıca, DateTime.Nowbunu kullanmak ve hesaplamalarınıza dahil etmek sadece doğru hissettirmez - belirli zamanları test etme yeteneğinizi elinizden alır, örneğin, yalnızca gece yarısı sınırının yakınında veya Salı günleri meydana gelen bir hata keşfederseniz. Geçerli zamanı kullanmak, bu senaryoları test etmenize izin vermez. Ya da en azından ne zaman istersen.


2
Bunu aslında xUnit.net uzantılarından birinde resmileştirdik. DateTime yerine statik olarak kullandığınız bir Clock sınıfımız var ve belirli tarihler de dahil olmak üzere saati "dondurabilir" ve "çözebilirsiniz". Bkz. İs.gd/3xds ve is.gd/3xdu
Brad Wilson

2
Ayrıca, sistem saati yönteminizi ikame etmek istediğinizde - bu, örneğin, çok dağınık zaman dilimlerinde şubeleri olan bir kuruluşta küresel bir saat kullanıldığında meydana gelecektir - bu yöntemin, "Şimdi" nin anlamı.
Mike Burton

1
Bu yöntem, IClock örneğine ulaşmak için bir Bağımlılık Enjeksiyon Çerçevesi kullanmanın yanı sıra benim için gerçekten iyi çalışıyor.
Wilka

8
Mükemmel cevap. Hemen hemen tüm durumlarda UtcNowkullanılması ve daha sonra kodun endişesine göre uygun şekilde ayarlanması gerektiğini eklemek istedim , örneğin iş mantığı, kullanıcı arayüzü, vb. Saat dilimleri arasında DateTime manipülasyonu bir mayın tarlasıdır, ancak ileriye dönük en iyi adım her zaman başlamaktır. UTC zamanı.
Adam Ralph

@BradWilson Bu bağlantılar artık kesildi. Onları WayBack'te de çekemedim.

55

Ayende Rahien , oldukça basit olan statik bir yöntem kullanıyor ...

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

1
Saplama / sahte noktayı genel bir küresel değişken (sınıf statik değişkeni) yapmak tehlikeli görünüyor. Bunun kapsamını sadece test edilen sisteme dahil etmek - örneğin, onu test edilen sınıfın özel bir statik üyesi yapmak daha iyi olmaz mıydı?
Aaron

1
Bu bir tarz meselesi. Bu, birim testi ile değiştirilebilir bir sistem zamanı elde etmek için yapabileceğiniz en az şeydir.
Anthony Mastrean

3
IMHO, global statik singleton yerine arayüz kullanılması tercih edilir. Şu senaryoyu düşünün: Test koşucusu verimli ve paralel olarak olabildiğince çok test çalıştırıyor, bir test verilen süreye X'e, diğeri Y'ye değişiyor. Şimdi bir çatışmamız var ve bu iki test başarısızlıkları değiştirecek. Arayüzler kullanırsak, her test arayüzle kendi ihtiyaçlarına göre alay eder, her test artık diğer testlerden izole edilir. HTH.
ShloEmi

17

Şu anki tarihi almak gibi basit bir şey için ayrı bir saat sınıfı oluşturmanın biraz fazla olduğunu düşünüyorum.

Teste farklı bir tarih girebilmeniz için bugünün tarihini parametre olarak geçebilirsiniz. Bu, kodunuzu daha esnek hale getirme ek avantajına sahiptir.


İkisi de karşı çıksa da hem sizin hem de Blair'in cevaplarını + 1'ledim. Her iki yaklaşımın da geçerli olduğunu düşünüyorum. Yaklaşımınız muhtemelen Unity gibi bir şey kullanmayan bir proje kullanırdım.
RichardOD

1
Evet, "şimdi" için bir parametre eklemek mümkündür. Ancak, bazı durumlarda bu, normalde ortaya çıkarmak istemediğiniz bir parametreyi göstermenizi gerektirir. Diyelim ki, bir tarih ile şimdi arasındaki günleri hesaplayan bir yönteminiz var, o zaman "şimdi" yi parametre olarak göstermek istemiyorsunuz çünkü bu, sonucunuzun değiştirilmesine izin veriyor. Kendi kodunuzsa, devam edin "şimdi" parametresini ekleyin, ancak bir takımda çalışıyorsanız, diğer geliştiricilerin kodunuzu ne için kullanacağını asla bilemezsiniz, bu nedenle "şimdi" kodunuzun kritik bir parçasıysa, yapmanız gereken manipülasyondan veya yanlış kullanımdan koruyun.
Stitch10925

17

Dolgu oluşturmak için Microsoft Fakes kullanmak, bunu yapmanın gerçekten kolay bir yoludur. Aşağıdaki sınıfa sahip olduğumu varsayalım:

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

}

Visual Studio 2012'de, Fakes / Shims oluşturmak istediğiniz montaja sağ tıklayıp "Fakes Montajı Ekle" yi seçerek test projenize bir Fakes montajı ekleyebilirsiniz.

Sahte Montaj Ekleme

Son olarak, test sınıfı şöyle görünecektir:

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

        }
    }
}
}

1
Bu tam olarak aradığım şeydi. Teşekkürler! BTW, VS 2013'te aynı şekilde çalışır.
Douglas Ludlow

Veya bugünlerde VS 2015 Enterprise sürümü. Böyle bir en iyi uygulama için utanç verici.
RJB

12

Başarılı birim test için anahtar olduğunu ayırımı . İlginç kodunuzu harici bağımlılıklarından ayırmanız gerekir, böylece izole olarak test edilebilir. (Neyse ki, Test-Driven Development, ayrıştırılmış kod üretir.)

Bu durumda, harici cihazınız geçerli DateTime'dır.

Buradaki tavsiyem, DateTime ile ilgilenen mantığı yeni bir yönteme veya sınıfa veya sizin durumunuzda mantıklı olan herhangi bir şeye çıkarmak ve DateTime'ı aktarmaktır. Şimdi, birim testiniz, tahmin edilebilir sonuçlar üretmek için rastgele bir DateTime'ı geçebilir.


10

Microsoft Moles kullanan bir diğeri ( .NET için İzolasyon çerçevesi ).

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

Moles, herhangi bir .NET yöntemini bir temsilciyle değiştirmeye izin verir. Moles, statik veya sanal olmayan yöntemleri destekler. Moles, Pex'in profiline güveniyor.


Bu çok güzel, ancak Visual Studio 2010 gerektiriyor! :-(
Pandincus

VS 2008'de de iyi çalışıyor. Yine de MSTest ile en iyi şekilde çalışır. NUnit'i kullanabilirsiniz, ancak daha sonra testlerinizi özel bir test çalıştırıcısı ile çalıştırmanız gerektiğini düşünüyorum.
Torbjørn

Mümkün olduğunda Moles (diğer adıyla Microsoft Fakes) kullanmaktan kaçınırdım. İdeal olarak, yalnızca bağımlılık ekleme yoluyla halihazırda test edilemeyen eski kod için kullanılmalıdır.
brianpeiris

1
@brianpeiris, Microsoft Fakes kullanmanın dezavantajları nelerdir?
Ray Cheng

Üçüncü taraf içeriği her zaman DI yapamazsınız, bu nedenle, kodunuz bir birim testi için başlatmak istemediğiniz bir 3. taraf API'sini çağırırsa, Fakes birim testi için mükemmel bir şekilde kabul edilebilir. Kendi kodunuz için Fakes / Moles kullanmaktan kaçınmayı kabul ediyorum, ancak diğer amaçlar için tamamen kabul edilebilir.
ChrisCW



2

Test edilen sınıfta kullandığınız sınıfı (daha iyi: yöntem / temsilci ) enjekte edebilirsiniz DateTime.Now. Mü DateTime.Nowvarsayılan bir değer ve sadece bir kukla yöntemiyle döndürdüğü sabit bir değer için test bunu ayarlanmalıdır.

DÜZENLEME: Blair Conrad'ın söylediği şey (bakması gereken bir kod var). Bunun dışında, sınıf hiyerarşinizi şunun gibi şeylerle karıştırmadıkları için delegeleri bunun için tercih ederim IClock.


1

Bu durumla o kadar sık ​​karşılaştım ki Now özelliğini arayüz aracılığıyla ortaya çıkaran basit bir nuget yarattım .

public interface IDateTimeTools
{
    DateTime Now { get; }
}

Uygulama elbette çok basittir

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

Bu yüzden projeme nuget ekledikten sonra birim testlerinde kullanabilirim

görüntü açıklamasını buraya girin

Modülü doğrudan GUI Nuget Paket Yöneticisinden veya şu komutu kullanarak kurabilirsiniz:

Install-Package -Id DateTimePT -ProjectName Project

Ve Nuget'in kodu burada .

Autofac ile kullanım örneği burada bulunabilir .


-11

Hata ayıklama / dağıtım sırasında ne olacağını kontrol etmek için koşullu derlemeyi kullanmayı düşündünüz mü?

Örneğin

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

Aksi halde, mülkü ifşa etmek istersiniz, böylece onu manipüle edebilirsiniz, bu, test edilebilir kod yazma zorluğunun bir parçasıdır , ki bu şu anda kendimle uğraştığım bir şey: D

Düzenle

Büyük bir parçam Blair'in yaklaşımını tercih ederdi . Bu, teste yardımcı olmak için kodun bölümlerini "çalışırken takmanıza" olanak tanır. Her şey, neyin değiştiğini özetleyen tasarım ilkesini takip eder test kodunu değiştiren şeyin üretim kodundan farklı olmadığını , sadece hiç kimse onu harici olarak görmez.

Yine de oluşturma ve arayüz oluşturma bu örnek için çok fazla çalışma gibi görünebilir (bu yüzden koşullu derlemeyi seçtim).


vay bu cevaba sert darbe aldınız. Yaptığım şey buydu, tek bir yerde, DateTime's Nowve Todaybenzeri yöntemlerin yerini alan yöntemler diye adlandırdığım yerde
Dave Cousineau
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.