Bagikan melalui


Praktik terbaik untuk pengujian unit di .NET

Ada banyak manfaat dari menulis tes unit. Mereka membantu regresi, memberikan dokumentasi, dan memfasilitasi desain yang baik. Tetapi ketika pengujian unit sulit dibaca dan rapuh, mereka dapat menimbulkan malapetaka di basis kode Anda. Artikel ini menjelaskan beberapa praktik terbaik untuk merancang pengujian unit untuk mendukung proyek .NET Core dan .NET Standard Anda. Anda mempelajari teknik untuk menjaga tes Anda tetap tangguh dan mudah dipahami.

Dengan John Reese dengan ucapan terima kasih khusus kepada Roy Osherove

Manfaat pengujian unit

Bagian berikut menjelaskan beberapa alasan untuk menulis pengujian unit untuk proyek .NET Core dan .NET Standard Anda.

Lebih sedikit waktu melakukan pengujian fungsi

Tes fungsi ini mahal. Mereka biasanya melibatkan pembukaan aplikasi dan melakukan serangkaian langkah yang harus Anda (atau orang lain) ikuti untuk memvalidasi perilaku yang diharapkan. Langkah-langkah ini mungkin tidak selalu diketahui oleh penguji. Mereka harus menjangkau seseorang yang lebih berpengetahuan luas di daerah untuk melakukan tes. Pengujian itu sendiri dapat memakan waktu beberapa detik untuk perubahan sepele, atau menit untuk perubahan yang lebih besar. Terakhir, proses ini harus diulang untuk setiap perubahan yang Anda buat dalam sistem. Pengujian unit, di sisi lain, mengambil milidetik, dapat dijalankan dengan menekan tombol, dan tidak selalu memerlukan pengetahuan tentang sistem pada umumnya. Pelari pengujian menentukan apakah pengujian lolos atau gagal, bukan individu.

Perlindungan terhadap regresi

Cacat regresi adalah kesalahan yang diperkenalkan ketika perubahan dilakukan pada aplikasi. Penguji tidak hanya menguji fitur baru mereka tetapi juga menguji fitur yang ada sebelumnya untuk memverifikasi bahwa fitur yang ada masih berfungsi seperti yang diharapkan. Dengan pengujian unit, Anda dapat menjalankan ulang seluruh rangkaian pengujian setelah setiap build atau bahkan setelah mengubah baris kode. Pendekatan ini membantu meningkatkan keyakinan bahwa kode baru Anda tidak merusak fungsionalitas yang ada.

Dokumentasi yang dapat dieksekusi

Mungkin tidak selalu jelas apa yang dilakukan metode tertentu atau bagaimana perilakunya diberikan input tertentu. Anda mungkin bertanya pada diri sendiri: Bagaimana metode ini berperilaku jika saya meneruskannya string kosong atau null? Saat Anda memiliki serangkaian pengujian unit yang diberi nama dengan baik, setiap pengujian harus menjelaskan secara jelas keluaran yang diharapkan untuk setiap input. Selain itu, pengujian harus dapat memverifikasi bahwa pengujian tersebut benar-benar berfungsi.

Kode yang kurang digabungkan

Ketika kode yang saling terkait erat, mungkin sulit untuk melakukan pengujian unit. Tanpa membuat pengujian unit untuk kode yang Anda tulis, kopling mungkin kurang jelas. Menulis pengujian untuk kode Anda secara alami memisahkan kode Anda karena lebih sulit untuk menguji sebaliknya.

Karakteristik pengujian unit yang baik

Ada beberapa karakteristik penting yang menentukan pengujian unit yang baik:

  • Fast: Tidak jarang proyek yang matang memiliki ribuan pengujian unit. Pengujian unit sebaiknya memerlukan sedikit waktu untuk dijalankan. Milidetik.
  • Terisolasi : Pengujian unit bersifat mandiri, dapat berjalan dalam isolasi, dan tidak memiliki dependensi pada faktor luar apa pun, seperti sistem file atau database.
  • Berulang: Menjalankan pengujian unit harus memberikan hasil yang konsisten. Pengujian selalu mengembalikan hasil yang sama jika Anda tidak mengubah apa pun di antara eksekusi.
  • Pemeriksaan Mandiri : Pengujian harus secara otomatis mendeteksi apakah lulus atau gagal tanpa interaksi manusia.
  • Timely: Pengujian unit tidak boleh memakan waktu yang tidak proporsional untuk ditulis dibandingkan dengan kode yang sedang diuji. Jika Anda menemukan bahwa pengujian kode membutuhkan banyak waktu dibandingkan dengan menulis kode, pertimbangkan desain yang lebih dapat diuji.

Cakupan kode dan kualitas kode

Persentase cakupan kode tinggi sering dikaitkan dengan kualitas kode yang lebih tinggi. Namun, pengukuran itu sendiri tidak dapat menentukan kualitas kode. Menetapkan tujuan persentase cakupan kode yang terlalu ambisius dapat menjadi kontraproduktif. Pertimbangkan proyek kompleks dengan ribuan cabang bersyarat, dan misalkan Anda menetapkan tujuan cakupan kode 95%. Saat ini, proyek mempertahankan cakupan kode 90%. Jumlah waktu yang diperlukan untuk memperhitungkan semua kasus pengecualian dalam 5% yang tersisa dapat menjadi usaha besar-besaran, dan nilai proposisi dengan cepat berkurang.

Persentase cakupan kode tinggi bukanlah indikator keberhasilan, dan tidak menyiratkan kualitas kode yang tinggi. Ini hanya mewakili banyaknya kode yang dicakup oleh pengujian unit. Untuk informasi selengkapnya, lihat cakupan kode pengujian unit .

Terminologi pengujian unit

Beberapa istilah sering digunakan dalam konteks pengujian unit: palsu , tiruan , dan . Sayangnya, istilah-istilah ini dapat disalahgunakan, jadi sangat penting untuk memahami penggunaan yang benar.

  • Fake: Palsu adalah istilah generik yang dapat digunakan untuk menggambarkan stub atau objek tiruan. Apakah sebuah objek merupakan stub atau mock tergantung pada konteks penggunaannya. Dengan kata lain, sebuah kebohongan bisa berupa stub atau tiruan.

  • Mock: Objek tiruan adalah objek palsu dalam sistem yang memutuskan apakah pengujian unit lulus atau gagal atau tidak. Sebuah rekaan dimulai sebagai palsu dan tetap palsu sampai masuk ke dalam operasi Assert.

  • Stub: Stub adalah pengganti yang dapat dikontrol untuk dependensi (atau kolaborator) yang ada dalam sistem. Dengan menggunakan stub, Anda dapat menguji kode tanpa berurusan dengan dependensi secara langsung. Secara default, stub dimulai sebagai palsu.

Pertimbangkan kode berikut:

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

purchase.ValidateOrders();

Assert.True(purchase.CanBeShipped);

Kode ini menunjukkan stub yang disebut sebagai tiruan. Tetapi dalam skenario ini, stub benar-benar sebuah rangkap. Tujuan kode adalah untuk meneruskan pesanan sebagai sarana untuk membuat instans objek Purchase (sistem di bawah pengujian). Nama kelas MockOrder menyesatkan karena urutannya adalah stub dan bukan tiruan.

Kode berikut menunjukkan desain yang lebih akurat:

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

purchase.ValidateOrders();

Assert.True(purchase.CanBeShipped);

Ketika kelas diganti namanya menjadi FakeOrder, kelasnya lebih umum. Kelas dapat digunakan sebagai tiruan atau stub, sesuai dengan persyaratan kasus pengujian. Dalam contoh pertama, kelas FakeOrder digunakan sebagai stub, dan tidak digunakan selama operasi Assert. Kode meneruskan kelas FakeOrder ke kelas Purchase hanya untuk memenuhi persyaratan konstruktor.

Untuk menggunakan kelas sebagai tiruan, Anda dapat memperbarui kode:

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

purchase.ValidateOrders();

Assert.True(mockOrder.Validated);

Dalam desain ini, kode memeriksa properti pada objek palsu (melakukan asersi terhadapnya), dan oleh karena itu, kelas mockOrder adalah objek tiruan.

Penting

Penting untuk menerapkan terminologi dengan benar. Jika Anda menyebut stub Anda sebagai "mock", pengembang lain akan membuat asumsi yang salah tentang niat Anda.

Hal yang utama perlu diingat tentang mock versus stub adalah bahwa mock sama seperti stub, kecuali untuk proses Assert. Anda menjalankan operasi Assert terhadap objek tiruan, tetapi tidak terhadap stub.

Praktik terbaik

Ada beberapa praktik terbaik penting yang harus diikuti saat menulis pengujian unit. Bagian berikut memberikan contoh yang menunjukkan cara menerapkan praktik terbaik ke kode Anda.

Hindari dependensi infrastruktur

Usahakan untuk tidak mengaitkan ketergantungan pada infrastruktur saat menulis pengujian unit. Dependensi membuat pengujian lambat dan rapuh dan harus dicadangkan untuk pengujian integrasi. Anda dapat menghindari dependensi ini dalam aplikasi Anda dengan mengikuti Prinsip Dependensi Eksplisit dan dengan menggunakan injeksi dependensi .NET . Anda juga dapat menyimpan pengujian unit dalam proyek terpisah dari pengujian integrasi Anda. Pendekatan ini memastikan proyek pengujian unit Anda tidak memiliki referensi atau dependensi pada paket infrastruktur.

Mengikuti standar penamaan pengujian

Nama pengujian Anda harus terdiri dari tiga bagian:

  • Nama metode yang sedang diuji
  • Skenario di mana metode sedang diuji
  • Perilaku yang diharapkan ketika skenario dipanggil

Standar penamaan penting karena membantu mengekspresikan tujuan pengujian dan aplikasi. Pengujian lebih dari sekadar memastikan kode Anda berfungsi. Mereka juga menyediakan dokumentasi. Hanya dengan melihat rangkaian pengujian unit, Anda harus dapat menyimpulkan perilaku kode Anda dan tidak perlu melihat kode itu sendiri. Selain itu, ketika pengujian gagal, Anda dapat melihat dengan tepat skenario mana yang tidak memenuhi harapan Anda.

Kode asli

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

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

    Assert.Equal(0, actual);
}

Terapkan praktik terbaik

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

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

    Assert.Equal(0, actual);
}

Atur pengujian Anda

Pola "Arrange, Act, Assert" adalah pendekatan umum untuk pola penulisan pengujian unit. Seperti namanya, pola terdiri dari tiga tugas utama:

  • Menyusun objek Anda, membuat, dan mengonfigurasinya seperlunya
  • Bertindak pada objek
  • Assert bahwa sesuatu seperti yang diharapkan

Saat Anda mengikuti pola, Anda dapat memisahkan dengan jelas elemen yang sedang diuji dari tugas Pengaturan dan Verifikasi. Pola ini juga membantu mengurangi kesempatan pernyataan bercampur dengan kode dalam tugas 'Act'.

Keterbacaan adalah salah satu aspek terpenting saat menulis pengujian unit. Memisahkan setiap tindakan pola dalam pengujian dengan jelas menyoroti dependensi yang diperlukan untuk memanggil kode Anda, bagaimana kode Anda dipanggil, dan apa yang coba Anda tegaskan. Meskipun mungkin untuk menggabungkan beberapa langkah dan mengurangi ukuran pengujian Anda, tujuan keseluruhannya adalah untuk membuat pengujian dapat dibaca sebagus mungkin.

Kode asli

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

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

Terapkan praktik terbaik

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

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

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

Menulis pengujian yang lulus minimum

Input untuk pengujian unit harus menjadi informasi paling sederhana yang diperlukan untuk memverifikasi perilaku yang saat ini Anda uji. Pendekatan minimalis membantu pengujian menjadi lebih tangguh terhadap perubahan di masa mendatang dalam basis kode dan fokus pada verifikasi perilaku atas implementasi.

Pengujian yang mencakup lebih banyak informasi daripada yang diperlukan untuk lulus tes saat ini memiliki kemungkinan yang lebih tinggi untuk memperkenalkan kesalahan ke dalam pengujian dan dapat membuat niat pengujian kurang jelas. Saat menulis tes, Anda ingin fokus pada perilaku. Mengatur properti tambahan pada model atau menggunakan nilai bukan nol jika tidak diperlukan, hanya mengurangi dari apa yang coba Anda konfirmasi.

Kode asli

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

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

    Assert.Equal(42, actual);
}

Terapkan praktik terbaik

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

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

    Assert.Equal(0, actual);
}

Hindari string ajaib

String ajaib adalah nilai string yang dikodekan secara permanen langsung dalam pengujian unit Anda tanpa komentar atau konteks tambahan kode apa pun. Nilai-nilai ini membuat kode Anda kurang mudah dibaca dan lebih sulit dipertahankan. String ajaib dapat menyebabkan kebingungan bagi yang membaca tes Anda. Jika string terlihat tidak biasa, mereka mungkin bertanya-tanya mengapa nilai tertentu dipilih untuk parameter atau mengembalikan nilai. Jenis nilai string ini dapat menyebabkan mereka melihat lebih dekat detail implementasi, daripada fokus pada pengujian.

Petunjuk / Saran

Jadikan tujuan Anda untuk mengekspresikan niat sebanyak mungkin dalam kode uji unit Anda. Daripada menggunakan string ajaib, tetapkan nilai yang dikodekan secara permanen ke konstanta.

Kode asli

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

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

    Assert.Throws<OverflowException>(actual);
}

Terapkan praktik terbaik

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

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

    Assert.Throws<OverflowException>(actual);
}

Hindari logika pengkodian dalam pengujian unit

Saat Anda menulis pengujian unit, hindari perangkaian string secara manual serta kondisi logis seperti if, while, for, dan switch, serta kondisi lainnya. Jika Anda menyertakan logika dalam rangkaian pengujian, kemungkinan memperkenalkan bug meningkat secara dramatis. Tempat terakhir Anda ingin menemukan bug ada di dalam rangkaian pengujian Anda. Anda harus memiliki tingkat kepercayaan diri yang tinggi bahwa pengujian Anda berfungsi, jika tidak, Anda tidak dapat mempercayainya. Pengujian yang tidak Anda percayai, tidak memberikan nilai apa pun. Ketika pengujian gagal, Anda ingin memiliki perasaan bahwa ada sesuatu yang salah dengan kode Anda dan bahwa itu tidak dapat diabaikan.

Petunjuk / Saran

Jika menambahkan logika dalam pengujian Anda tampaknya tidak dapat ditolak, pertimbangkan untuk membagi pengujian menjadi dua atau beberapa pengujian yang berbeda untuk membatasi persyaratan logika.

Kode asli

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

Terapkan praktik terbaik

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

Menggunakan metode pembantu alih-alih Penyiapan dan Teardown

Jika Anda memerlukan objek atau status serupa untuk pengujian Anda, gunakan metode pembantu daripada atribut Setup dan Teardown, jika ada. Metode pembantu lebih disukai daripada atribut ini karena beberapa alasan:

  • Lebih sedikit kebingungan saat membaca pengujian karena semua kode terlihat dari dalam setiap pengujian
  • Lebih sedikit kemungkinan untuk menyiapkan terlalu banyak atau terlalu sedikit untuk tes yang diberikan.
  • Lebih sedikit kesempatan untuk berbagi status antar pengujian, yang menciptakan dependensi yang tidak diinginkan di antaranya

Dalam kerangka kerja pengujian unit, atribut Setup dipanggil sebelum setiap pengujian unit dalam rangkaian pengujian Anda. Beberapa programmer melihat perilaku ini sebagai berguna, tetapi sering menghasilkan tes yang kembung dan sulit dibaca. Setiap pengujian umumnya memiliki persyaratan yang berbeda untuk penyiapan dan eksekusi. Sayangnya, atribut Setup memaksa Anda untuk menggunakan persyaratan yang sama persis untuk setiap pengujian.

Nota

Atribut SetUp dan TearDown dihapus di xUnit versi 2.x dan yang lebih baru.

Kode asli

Terapkan praktik terbaik

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

Hindari banyak tugas Act

Saat Anda menulis pengujian, coba sertakan hanya satu tugas "Act" per pengujian. Beberapa pendekatan umum untuk menerapkan satu tugas Act termasuk membuat pengujian terpisah untuk setiap Act atau menggunakan pengujian berparameter. Ada beberapa manfaat dalam menggunakan satu tugas Act untuk setiap pengujian.

  • Anda dapat dengan mudah mengetahui tugas Act mana yang gagal jika ujiannya gagal.
  • Anda dapat memastikan pengujian hanya berfokus pada satu kasus.
  • Anda mendapatkan gambaran yang jelas mengapa pengujian Anda gagal.

Beberapa tugas Act harus ditegaskan secara individual, dan Anda tidak dapat menjamin bahwa semua tugas Assert akan dijalankan. Di sebagian besar kerangka kerja pengujian unit, setelah tugas Assert gagal dalam pengujian unit, semua pengujian berikutnya secara otomatis dianggap gagal. Proses ini dapat membingungkan karena beberapa fungsionalitas kerja mungkin ditafsirkan gagal.

Kode asli

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

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

Terapkan praktik terbaik

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

Memvalidasi metode privat dengan metode publik

Dalam kebanyakan kasus, Anda tidak perlu menguji metode privat dalam kode Anda. Metode privat adalah detail implementasi dan tidak pernah ada secara terpisah. Pada titik tertentu dalam proses pengembangan, Anda memperkenalkan metode yang dapat diakses publik untuk memanggil metode privat sebagai bagian dari implementasinya. Saat Anda menulis tes unit, yang Anda pedulikan adalah hasil akhir dari metode publik yang memanggil metode privat.

Pertimbangkan skenario kode berikut:

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

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

Dalam hal pengujian, reaksi pertama Anda mungkin menulis tes untuk metode TrimInput untuk memastikannya berfungsi seperti yang diharapkan. Namun, ada kemungkinan metode ParseLogLine memanipulasi objek sanitizedInput dengan cara yang tidak Anda harapkan. Perilaku yang tidak diketahui mungkin membuat pengujian Anda terhadap metode TrimInput menjadi tidak berguna.

Pengujian yang lebih baik dalam skenario ini adalah memverifikasi metode ParseLogLine yang ditujukan untuk publik.

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

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

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

Ketika Anda menemukan metode privat, temukan metode publik yang memanggil metode privat, dan tulis pengujian Anda terhadap metode publik. Hanya karena metode privat mengembalikan hasil yang diharapkan, bukan berarti sistem yang akhirnya memanggil metode privat menggunakan hasilnya dengan benar.

Mengatasi stub referensi statis dengan teknis pemisahan (seams)

Salah satu prinsip pengujian unit adalah bahwa ia harus memiliki kontrol penuh atas sistem yang sedang diuji. Namun, prinsip ini dapat bermasalah ketika kode produksi mencakup panggilan ke referensi statis (misalnya, DateTime.Now).

Periksa skenario kode berikut:

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

Dapatkah Anda menulis pengujian unit untuk kode ini? Anda mungkin mencoba menjalankan tugas Assert pada price:

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

Sayangnya, Anda dengan cepat menyadari ada beberapa masalah dengan tes Anda:

  • Jika rangkaian pengujian berjalan pada hari Selasa, pengujian kedua lolos, tetapi pengujian pertama gagal.
  • Jika rangkaian pengujian berjalan pada hari lain, pengujian pertama lolos, tetapi pengujian kedua gagal.

Untuk mengatasi masalah ini, Anda perlu memperkenalkan jahitan ke dalam kode produksi Anda. Salah satu pendekatannya adalah membungkus kode yang perlu Anda kontrol dalam antarmuka dan memiliki kode produksi tergantung pada antarmuka tersebut:

public interface IDateTimeProvider
{
    DayOfWeek DayOfWeek();
}

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

Anda juga perlu menulis versi baru dari rangkaian pengujian Anda:

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

Sekarang suite pengujian memiliki kontrol penuh atas nilai DateTime.Now, dan dapat menggantikan nilai apa pun ketika memanggil metode.

  • cakupan kode pengujian unit