Aracılığıyla paylaş


.NET için birim testi en iyi yöntemleri

Birim testi yazmanın birçok avantajı vardır. Regresyona yardımcı olur, belge sağlar ve iyi bir tasarım sağlar. Ancak birim testlerinin okunuşu ve kırılganlıkları zor olduğunda kod tabanınıza zarar verebilir. Bu makalede , .NET Core ve .NET Standard projelerinizi desteklemek üzere birim testleri tasarlamaya yönelik bazı en iyi yöntemler açıklanmaktadır. Testlerinizi dayanıklı ve anlaşılması kolay tutmak için teknikler öğrenirsiniz.

John Reese'a özel teşekkürlerleRoy Osherove'a

Birim testinin avantajları

Aşağıdaki bölümlerde .NET Core ve .NET Standard projeleriniz için birim testleri yazmanın çeşitli nedenleri açıklanmaktadır.

İşlevsel testleri gerçekleştirmek için daha az zaman

İşlevsel testler pahalıdır. Bunlar genellikle uygulamayı açmayı ve beklenen davranışı doğrulamak için sizin (veya başka birinin) izlemesi gereken bir dizi adımı gerçekleştirmeyi içerir. Bu adımlar her zaman test eden tarafından bilinmeyebilir. Testi gerçekleştirmek için bölgede daha bilgili birine ulaşmaları gerekiyor. Testin kendisi önemsiz değişiklikler için saniyeler veya daha büyük değişiklikler için dakikalar sürebilir. Son olarak, bu işlemin sistemde yaptığınız her değişiklik için tekrarlanması gerekir. Öte yandan birim testleri milisaniye alır, bir düğmeye basılarak çalıştırılabilir ve sistem hakkında büyük bir bilgi gerektirmez. Test çalıştırıcısı, testin başarılı mı yoksa başarısız mı olduğunu belirler, kişi değil.

Regresyona karşı koruma

Regresyon hataları, uygulamada bir değişiklik yapıldığında ortaya çıkan hatalardır. Test edenlerin yalnızca yeni özelliklerini test etmekle kalmaz, aynı zamanda mevcut özelliklerin hala beklendiği gibi çalıştığını doğrulamak için önceden var olan özellikleri test etmeleri de yaygındır. Birim testi sayesinde, her derlemeden sonra ve hatta bir kod satırını değiştirdikten sonra tüm test paketinizi yeniden çalıştırabilirsiniz. Bu yaklaşım, yeni kodunuzun mevcut işlevselliği bozmadığı güvenini artırmaya yardımcı olur.

Çalıştırılabilir dokümantasyon

Belirli bir yöntemin ne yaptığı veya belirli bir girişe göre nasıl davrandığı her zaman açık olmayabilir. Kendinize şu soruyu sorabilirsiniz: Boş dize veya null geçirirsem bu yöntem nasıl davranır? İyi adlandırılmış birim testlerinden oluşan bir paketiniz olduğunda, her test belirli bir giriş için beklenen çıkışı açıkça açıklamalıdır. Buna ek olarak, test gerçekten çalıştığını doğrulayabilmelidir.

Daha az bağlı kod

Kod sıkı bir şekilde birleştirildiğinde birim testi yapmak zor olabilir. Yazdığınız kod için birim testleri oluşturmadan, bağlama daha az görünür olabilir. Kodunuz için testler yazmak, kodunuzu doğal olarak birbirinden ayrıştırıyor çünkü aksi takdirde test etmek daha zor.

İyi birim testlerinin özellikleri

İyi bir birim testi tanımlayan birkaç önemli özellik vardır:

  • Hızlı: Olgun projelerin binlerce birim testine sahip olması yaygın bir durumdur. Birim testlerinin çalıştırılması çok az zaman almalıdır. Milisaniye.
  • Yalıtılmış: Birim testleri tek başınadır, yalıtılmış olarak çalıştırılabilir ve dosya sistemi veya veritabanı gibi dış faktörlere bağımlılıkları yoktur.
  • Yinelenebilir: Bir birim testinin çalıştırılması, sonuçlarıyla tutarlı olmalıdır. Çalıştırmalar arasında hiçbir şeyi değiştirmezseniz test her zaman aynı sonucu döndürür.
  • Kendi Kendine Denetim: Test, herhangi bir insan etkileşimi olmadan başarılı olup olmadığını otomatik olarak algılamalıdır.
  • Zamanında: Birim testinin yazılması test edilen kodla karşılaştırıldığında orantısız bir şekilde uzun sürmemelidir. Kodu test etme işleminin kodu yazmaya kıyasla çok uzun sürdüğünü fark ederseniz, daha test edilebilir bir tasarım düşünün.

Kod kapsamı ve kod kalitesi

Yüksek kod kapsamı yüzdesi genellikle daha yüksek bir kod kalitesiyle ilişkilendirilir. Ancak ölçümün kendisi kodun kalitesini belirleyemez. Aşırı iddialı bir kod kapsamı yüzdesi hedefi belirlemek ters etki yaratabilir. Binlerce koşullu dal içeren karmaşık bir proje düşünün ve 95% kod kapsamı hedefi ayarladığınızı düşünün. Proje şu anda 90% kod kapsamına sahip. Kalan 5% boyunca tüm uç durumları hesaba katmak için gereken süre çok zaman alıcı olabilir ve değer önerisi hızla azalır.

Yüksek kod kapsamı yüzdesi başarı göstergesi değildir ve yüksek kod kalitesi anlamına gelmez. Yalnızca birim testlerinin kapsadığı kod miktarını temsil eder. Daha fazla bilgi için bkz. birim testi kod kapsamı.

Birim testi terminolojisi

Birim testi bağlamında çeşitli terimler sıklıkla kullanılır: sahte , taklit ve kütük. Ne yazık ki bu terimler yanlış uygulanabilir, bu nedenle doğru kullanımı anlamak önemlidir.

  • Fake: Sahte, taslak veya sahte nesne tanımlamak için kullanılabilecek genel bir terimdir. Nesnenin saplama mı yoksa sahte mi olduğu, nesnenin kullanıldığı bağlama bağlıdır. Başka bir deyişle, bir sahte nesne bir taslak veya bir taklit olabilir.

  • Mock: Sahte nesne, sistemdeki birim testinin geçip geçmeyeceğine veya başarısız olmasına karar veren sahte bir nesnedir. Taklit bir nesne sahte olarak başlar ve bir Assert işlemine girene kadar sahte kalır.

  • saplama : Saptama, sistemdeki mevcut bir bağımlılık (veya ortak çalışan) için denetlenebilir bir değiştirmedir. Saplama kullanarak, doğrudan bağımlılıkla ilgilenmeden kodunuzu test edebilirsiniz. Varsayılan olarak, bir taslak başlangıçta sahte bir işlev gibi çalışır.

Aşağıdaki kodu inceleyin:

var mockOrder = new MockOrder();
var purchase = new Purchase(mockOrder);

purchase.ValidateOrders();

Assert.True(purchase.CanBeShipped);

Bu kod, sahte olarak adlandırılan bir saplama gösterir. Ancak bu senaryoda taslak gerçekten bir taslaktır. Kodun amacı, Purchase (test altındaki sistem) nesnesinin örneğini oluşturmak için bir araç olarak sırayı geçirmektir. MockOrder sınıf adı yanıltıcıdır çünkü nesne bir mock değil, bir stub'dır.

Aşağıdaki kod daha doğru bir tasarım gösterir:

var stubOrder = new FakeOrder();
var purchase = new Purchase(stubOrder);

purchase.ValidateOrders();

Assert.True(purchase.CanBeShipped);

sınıfı FakeOrderolarak yeniden adlandırıldığında, sınıf daha genel olur. Sınıf, test çalışması gereksinimlerine göre sahte veya saplama olarak kullanılabilir. İlk örnekte, FakeOrder sınıfı saplama olarak kullanılır ve Assert işlemi sırasında kullanılmaz. Kod, oluşturucunun gereksinimlerini karşılamak için FakeOrder sınıfını Purchase sınıfına geçirir.

sınıfını sahte olarak kullanmak için kodu güncelleştirebilirsiniz:

var mockOrder = new FakeOrder();
var purchase = new Purchase(mockOrder);

purchase.ValidateOrders();

Assert.True(mockOrder.Validated);

Bu tasarımda kod, sahte üzerinde bir özelliği denetler (bunu doğrular) ve bu nedenle mockOrder sınıfı bir mock'tur.

Önemli

Terminolojiyi doğru uygulamak önemlidir. Saplamalarınıza "sahte" derseniz, diğer geliştiriciler amacınız hakkında yanlış varsayımlarda bulunur.

Mocklar ve stublar hakkında hatırlanması gereken en önemli nokta, Assert işlemi dışında mockların stublar gibi olmasıdır. Assert işlemleri bir sahte nesneye karşı çalıştırırsınız, ancak saplamaya karşı çalıştırmazsınız.

En iyi yöntemler

Birim testleri yazarken izleyebileceğiniz birkaç önemli en iyi yöntem vardır. Aşağıdaki bölümlerde, kodunuz için en iyi yöntemlerin nasıl uygulanacağını gösteren örnekler verilmiştir.

Altyapı bağımlılıklarından kaçınma

Birim testleri yazarken altyapıya bağımlılıklar eklememeye çalışın. Bağımlılıklar, testleri yavaş ve kırılgan hale getirir ve tümleştirme testleri için ayrılmalıdır. Açık Bağımlılıklar İlkesi izleyerek ve .NET bağımlılık eklemekullanarak uygulamanızda bu bağımlılıklardan kaçınabilirsiniz. Birim testlerinizi tümleştirme testlerinden ayrı bir projede de tutabilirsiniz. Bu yaklaşım, birim testi projenizin altyapı paketlerine yönelik başvuruları veya bağımlılıkları olmamasını sağlar.

Test adlandırma standartlarına uyun

Testinizin adı üç bölümden oluşmalıdır:

  • Test edilen yöntemin adı
  • Yöntemin test edildiği senaryo
  • Senaryo çağrıldığında beklenen davranış

Adlandırma standartları, test amacını ve uygulamasını ifade etmeye yardımcı olduğundan önemlidir. Testler, kodunuzun çalıştığından emin olmaktan daha fazlasıdır. Belgeleri de sağlıyorlar. Birim testleri paketine bakarak kodunuzun davranışını çıkarabilmeniz ve kodun kendisine bakmanız gerekmemelidir. Ayrıca, testler başarısız olduğunda, beklentilerinizi tam olarak hangi senaryoların karşılamadığı görebilirsiniz.

Özgün kod

[Fact]
public void Test_Single()
{
    var stringCalculator = new StringCalculator();

    var actual = stringCalculator.Add("0");

    Assert.Equal(0, actual);
}

En iyi uygulamaları uygula

[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();

    var actual = stringCalculator.Add("0");

    Assert.Equal(0, actual);
}

Testlerinizi düzenleyin

"Düzenleme, Eylem, Onay" deseni, birim testleri yazmak için yaygın bir yaklaşımdır. Adından da anlaşılacağı gibi, desen üç ana görevden oluşur:

  • Nesnelerinizi düzenleyin, bunları gerektiği gibi oluşturun ve yapılandırın
  • Nesne üzerinde
  • Bir şeyin beklendiği gibi olduğunu doğrula

Deseni uyguladığınızda, test edilenleri Yerleştir ve Onayla görevlerinden açıkça ayırabilirsiniz. Desen, onayların Act görevindeki kodla kesişmesi fırsatını azaltmaya da yardımcı olur.

Okunabilirlik, birim testi yazarken en önemli yönlerden biridir. Test içindeki her desen eylemini ayırmak kodunuzu çağırmak için gereken bağımlılıkları, kodunuzun nasıl çağrıldığını ve onaylamaya çalıştığınız şeyi açıkça vurgular. Bazı adımları birleştirmek ve testinizin boyutunu küçültmek mümkün olsa da, genel hedef testi mümkün olduğunca okunabilir hale getirmektir.

Özgün kod

[Fact]
public void Add_EmptyString_ReturnsZero()
{
    // Arrange
    var stringCalculator = new StringCalculator();

    // Assert
    Assert.Equal(0, stringCalculator.Add(""));
}

En iyi uygulamaları uygula

[Fact]
public void Add_EmptyString_ReturnsZero()
{
    // Arrange
    var stringCalculator = new StringCalculator();

    // Act
    var actual = stringCalculator.Add("");

    // Assert
    Assert.Equal(0, actual);
}

Asgari geçiş testleri yazma

Birim testinin girişi, şu anda test etmekte olduğunuz davranışı doğrulamak için gereken en basit bilgiler olmalıdır. Minimalist yaklaşım, testlerin kod tabanındaki gelecekteki değişikliklere karşı daha dayanıklı hale gelmesine ve uygulama üzerindeki davranışı doğrulamaya odaklanmasına yardımcı olur.

Geçerli testi geçmek için gerekenden daha fazla bilgi içeren testlerin teste hata ekleme şansı daha yüksektir ve testin amacını daha az net hale getirebilirsiniz. Testler yazarken davranışa odaklanmak istiyorsunuz. Modellerde ek özellikler ayarlamak veya gerekli olmadığında sıfır olmayan değerler kullanmak, yalnızca onaylamaya çalıştığınız şeyi zayıflatır.

Özgün kod

[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();

    var actual = stringCalculator.Add("42");

    Assert.Equal(42, actual);
}

En iyi uygulamaları uygula

[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();

    var actual = stringCalculator.Add("0");

    Assert.Equal(0, actual);
}

Sihirli dizelerden kaçının

Magic dizeleri, herhangi bir kod ek açıklaması veya bağlamı olmadan doğrudan birim testlerinizde sabit kodlanmış dize değerleridir. Bu değerler kodunuzun daha az okunabilir olmasını ve bakımının zor olmasını sağlar. Sihirli dizeler, testlerinizin okuyucusunun kafa karışıklığına neden olabilir. Bir dize olağan dışı görünüyorsa, parametre veya dönüş değeri için belirli bir değerin neden seçildiğini merak edebilir. Bu tür dize değeri, teste odaklanmak yerine uygulama ayrıntılarına daha yakından bakmalarına neden olabilir.

Tavsiye

Birim test kodunuzda mümkün olduğunca çok amacı ifade etme hedefinizi oluşturun. Sihirli dizeleri kullanmak yerine sabitlere sabit kodlanmış değerler atayın.

Özgün kod

[Fact]
public void Add_BigNumber_ThrowsException()
{
    var stringCalculator = new StringCalculator();

    Action actual = () => stringCalculator.Add("1001");

    Assert.Throws<OverflowException>(actual);
}

En iyi uygulamaları uygula

[Fact]
void Add_MaximumSumResult_ThrowsOverflowException()
{
    var stringCalculator = new StringCalculator();
    const string MAXIMUM_RESULT = "1001";

    Action actual = () => stringCalculator.Add(MAXIMUM_RESULT);

    Assert.Throws<OverflowException>(actual);
}

Birim testlerinde kodlama mantığını kullanmaktan kaçının

Birim testlerinizi yazarken, el ile dize birleştirme, if, while, forve switchgibi mantıksal koşullardan ve diğer koşullardan kaçının. Test paketinize mantık eklerseniz hata ekleme şansı önemli ölçüde artar. Hata bulmak istediğiniz son yer, test paketinizin içindedir. Testlerinizin çalıştığından emin olmanız gerekir, aksi takdirde bunlara güvenemezsiniz. Güvenmediğiniz testler herhangi bir değer sağlamaz. Test başarısız olduğunda, kodunuzla ilgili bir sorun olduğunu hisseder ve bunun göz ardı edilemeyeceğini fark edersiniz.

Tavsiye

Testinize mantık eklemek kaçınılmaz görünüyorsa, mantık gereksinimlerini sınırlamak için testi iki veya daha fazla farklı teste bölmeyi göz önünde bulundurun.

Özgün kod

[Fact]
public void Add_MultipleNumbers_ReturnsCorrectResults()
{
    var stringCalculator = new StringCalculator();
    var expected = 0;
    var testCases = new[]
    {
        "0,0,0",
        "0,1,2",
        "1,2,3"
    };

    foreach (var test in testCases)
    {
        Assert.Equal(expected, stringCalculator.Add(test));
        expected += 3;
    }
}

En iyi uygulamaları uygula

[Theory]
[InlineData("0,0,0", 0)]
[InlineData("0,1,2", 3)]
[InlineData("1,2,3", 6)]
public void Add_MultipleNumbers_ReturnsSumOfNumbers(string input, int expected)
{
    var stringCalculator = new StringCalculator();

    var actual = stringCalculator.Add(input);

    Assert.Equal(expected, actual);
}

Kurulum ve Yırtma yerine yardımcı yöntemleri kullanma

Testleriniz için benzer bir nesneye veya duruma ihtiyacınız varsa, Setup ve Teardown öznitelikleri yerine yardımcı bir yöntem kullanın. Yardımcı yöntemler, çeşitli nedenlerle bu öznitelikler yerine tercih edilir:

  • Tüm kod her testin içinden görünür olduğundan testleri okurken daha az karışıklık
  • Verilen test için aşırı veya yetersiz ayarlama yapma olasılığı daha az.
  • Testler arasında durum paylaşma olasılığı daha azdır ve bu da aralarında istenmeyen bağımlılıklar oluşturur

Birim testi çerçevelerinde Setup özniteliği, test paketinizdeki her birim testinden önce çağrılır. Bazı programcılar bu davranışı yararlı olarak görür, ancak genellikle şişmiş ve okuması zor testlere neden olur. Her test genellikle kurulum ve yürütme için farklı gereksinimlere sahiptir. Ne yazık ki, Setup özniteliği sizi her test için tam olarak aynı gereksinimleri kullanmaya zorlar.

Uyarı

SetUp ve TearDown öznitelikleri xUnit sürüm 2.x ve sonraki sürümlerde kaldırılır.

Özgün kod

En iyi uygulamaları uygula

private readonly StringCalculator stringCalculator;
public StringCalculatorTests()
{
    stringCalculator = new StringCalculator();
}
[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
    var stringCalculator = CreateDefaultStringCalculator();

    var actual = stringCalculator.Add("0,1");

    Assert.Equal(1, actual);
}
// More tests...
// More tests...
[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
    var result = stringCalculator.Add("0,1");

    Assert.Equal(1, result);
}
private StringCalculator CreateDefaultStringCalculator()
{
    return new StringCalculator();
}

Birden çok Eylem görevinden kaçının

Testlerinizi yazarken, test başına yalnızca bir Act görevi eklemeyi deneyin. Tek bir Act görevini uygulamaya yönelik yaygın yaklaşımlardan bazıları, her Bir Eylem için ayrı bir test oluşturmak veya parametreli testler kullanmaktır. Her test için tek bir Act görevi kullanmanın çeşitli avantajları vardır:

  • Test başarısız olursa hangi Act görevinin başarısız olduğunu kolayca anlayabilirsiniz.
  • Testin yalnızca tek bir olaya odaklandığından emin olabilirsiniz.
  • Testlerinizin neden başarısız olduğunu net bir şekilde görebilirsiniz.

Birden çok Eylem görevinin tek tek onaylanması gerekir ve tüm Assert görevlerinin yürütüldüğünü garantileyemezsiniz. Çoğu birim testi çerçevesinde, bir Assert görevi birim testinde başarısız olduktan sonra, sonraki tüm testler otomatik olarak başarısız olarak kabul edilir. Bazı çalışma işlevleri başarısız olarak yorumlandığı için işlem kafa karıştırıcı olabilir.

Özgün kod

[Fact]
public void Add_EmptyEntries_ShouldBeTreatedAsZero()
{
    // Act
    var actual1 = stringCalculator.Add("");
    var actual2 = stringCalculator.Add(",");

    // Assert
    Assert.Equal(0, actual1);
    Assert.Equal(0, actual2);
}

En iyi uygulamaları uygula

[Theory]
[InlineData("", 0)]
[InlineData(",", 0)]
public void Add_EmptyEntries_ShouldBeTreatedAsZero(string input, int expected)
{
    // Arrange
    var stringCalculator = new StringCalculator();

    // Act
    var actual = stringCalculator.Add(input);

    // Assert
    Assert.Equal(expected, actual);
}

Genel yöntemlerle özel yöntemleri doğrulama

Çoğu durumda, kodunuzda özel bir yöntemi test etmeniz gerekmez. Özel yöntemler bir uygulama ayrıntısıdır ve hiçbir zaman yalıtımlı olarak mevcut olmaz. Geliştirme sürecinin bir noktasında, özel yöntemi uygulamanın bir parçası olarak çağırmak için genel kullanıma yönelik bir yöntem tanıtırsınız. Birim testlerinizi yazarken önemsediğiniz şey, özel olanı çağıran genel yöntemin sonucudur.

Aşağıdaki kod senaryolarını göz önünde bulundurun:

public string ParseLogLine(string input)
{
    var sanitizedInput = TrimInput(input);
    return sanitizedInput;
}

private string TrimInput(string input)
{
    return input.Trim();
}

Test açısından ilk tepkiniz, beklendiği gibi çalıştığından emin olmak için TrimInput yöntemi için bir test yazmak olabilir. Ancak, ParseLogLine yöntemi sanitizedInput nesnesini beklemediğiniz bir şekilde işler. Bilinmeyen davranış, TrimInput yöntemine karşı testinizi işe yaramaz hale getirebilir.

Bu senaryoda daha iyi bir test, genel kullanıma yönelik ParseLogLine yöntemini doğrulamaktır:

public void ParseLogLine_StartsAndEndsWithSpace_ReturnsTrimmedResult()
{
    var parser = new Parser();

    var result = parser.ParseLogLine(" a ");

    Assert.Equals("a", result);
}

Özel bir yöntemle karşılaştığınızda, özel yöntemi çağıran ortak yöntemi bulun ve testlerinizi public yöntemine karşı yazın. Özel bir yöntemin beklenen sonucu döndürmesi, sonunda özel yöntemi çağıran sistemin sonucu doğru kullandığı anlamına gelmez.

Kütük statik referansları dikişlerle yönetme

Birim testinin bir ilkesi, test altındaki sistemin tam denetimine sahip olması gerektiğidir. Ancak bu ilke, üretim kodu statik başvurulara (örneğin, DateTime.Now) yönelik çağrılar içerdiğinde sorunlu olabilir.

Aşağıdaki kod senaryolarını inceleyin:

public int GetDiscountedPrice(int price)
{
    if (DateTime.Now.DayOfWeek == DayOfWeek.Tuesday)
    {
        return price / 2;
    }
    else
    {
        return price;
    }
}

Bu kod için birim testi yazabilir misiniz? priceüzerinde bir Assert görevi çalıştırmayı deneyebilirsiniz:

public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
    var priceCalculator = new PriceCalculator();

    var actual = priceCalculator.GetDiscountedPrice(2);

    Assert.Equals(2, actual)
}

public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
    var priceCalculator = new PriceCalculator();

    var actual = priceCalculator.GetDiscountedPrice(2);

    Assert.Equals(1, actual);
}

Ne yazık ki, testinizde bazı sorunlar olduğunu hemen fark ediyorsunuz:

  • Test paketi Salı günü çalıştırılırsa, ikinci test geçer, ancak ilk test başarısız olur.
  • Test paketi başka bir gün çalıştırılırsa, ilk test geçer, ancak ikinci test başarısız olur.

Bu sorunları çözmek için üretim kodunuza bir dikiş eklemeniz gerekir. Bir yaklaşım, bir arabirimde denetlemeniz gereken kodu sarmalayıp üretim kodunun bu arabirime bağımlı olmasını sağlamaktır:

public interface IDateTimeProvider
{
    DayOfWeek DayOfWeek();
}

public int GetDiscountedPrice(int price, IDateTimeProvider dateTimeProvider)
{
    if (dateTimeProvider.DayOfWeek() == DayOfWeek.Tuesday)
    {
        return price / 2;
    }
    else
    {
        return price;
    }
}

Ayrıca test paketinizin yeni bir sürümünü de yazmanız gerekir:

public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
    var priceCalculator = new PriceCalculator();
    var dateTimeProviderStub = new Mock<IDateTimeProvider>();
    dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Monday);

    var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);

    Assert.Equals(2, actual);
}

public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
    var priceCalculator = new PriceCalculator();
    var dateTimeProviderStub = new Mock<IDateTimeProvider>();
    dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Tuesday);

    var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);

    Assert.Equals(1, actual);
}

Artık test paketi DateTime.Now değeri üzerinde tam denetime sahiptir ve metoda çağrıldığında herhangi bir değerin yedeğini alabilir.