Unit Test — xUnit, [Fact], [Theory], Assert metodlar

~25 daq · .NET Backend · Test

Muammodan boshlaylik

Chegirma hisoblash funksiyasini o'zgartirdingiz — "VIP mijozlarga qo'shimcha 5%". Qo'lda sinadingiz: VIP mijoz — to'g'ri. Deploy. Ertasi kuni ma'lum bo'ladi: oddiy mijozlarga 1 000 000 dan katta buyurtmada chegirma umuman hisoblanmayapti. Siz bu holatni qayta sinab ko'rmagansiz — har bir o'zgarishda barcha holatlarni qo'lda tekshirib bo'lmaydi.


Unit test nima?

Unit test — kodning eng kichik qismini (metod, klass) alohida, avtomatik tekshiruvchi kod:

  • Tez — millisekundlar, minglab testlar bir necha soniyada
  • Mustaqil — baza, tarmoq, fayl tizimisiz
  • Takrorlanadigan — har safar bir xil natija

Har bir o'zgarishdan keyin bitta buyruq — va barcha eski holatlar ham tekshiriladi.


Hayotiy o'xshatma

Avtomobil zavodi. Har bir detal — tormoz kolodkasi, sham, akkumulyator — mashinaga o'rnatilishidan oldin alohida stendda sinaladi. Tormoz kolodkasini sinash uchun butun mashinani yig'ib, yo'lga chiqish shart emas. Mashina yo'lda to'xtamasa — qaysi detal aybdorligini topish ancha qiyin bo'lardi.


Loyiha yaratish

dotnet new xunit -n Dokon.Tests
dotnet sln add Dokon.Tests
dotnet add Dokon.Tests reference Dokon.Api
Dokon.sln
├── Dokon.Api/
│   └── Services/ChegirmaHisoblagich.cs
└── Dokon.Tests/
    └── ChegirmaHisoblagichTests.cs

Sinaladigan kod:

public class ChegirmaHisoblagich
{
    public decimal Hisobla(decimal summa, bool vip)
    {
        if (summa < 0) throw new ArgumentOutOfRangeException(nameof(summa));

        decimal foiz = summa >= 1_000_000 ? 0.10m : 0m;
        if (vip) foiz += 0.05m;

        return Math.Round(summa * foiz, 2);
    }
}

[Fact] — bitta holat

public class ChegirmaHisoblagichTests
{
    private readonly ChegirmaHisoblagich _h = new();

    [Fact]
    public void Hisobla_kichik_summa_oddiy_mijoz_chegirma_yoq()
    {
        // Arrange
        decimal summa = 500_000;

        // Act
        var natija = _h.Hisobla(summa, vip: false);

        // Assert
        Assert.Equal(0m, natija);
    }

    [Fact]
    public void Hisobla_manfiy_summa_xato_tashlaydi()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => _h.Hisobla(-1, vip: false));
    }
}

AAA naqshi:

  • Arrange — tayyorlash
  • Act — bitta amal
  • Assert — natijani tekshirish

Test nomi — nimani, qanday sharoitda va qanday natija kutilishini aytadi.


[Theory] — bir nechta ma'lumot bilan

[Theory]
[InlineData(500_000, false, 0)]
[InlineData(1_000_000, false, 100_000)]
[InlineData(500_000, true, 25_000)]
[InlineData(2_000_000, true, 300_000)]
[InlineData(0, true, 0)]
public void Hisobla_turli_holatlar(decimal summa, bool vip, decimal kutilgan)
{
    Assert.Equal(kutilgan, _h.Hisobla(summa, vip));
}

Bitta test metodi — 5 ta alohida test. Chegara qiymatlari (999_999, 1_000_000) — xatolar ko'pincha aynan shu yerda.

Murakkab ma'lumotlar uchun — [MemberData]:

public static TheoryData<decimal, bool, decimal> Holatlar => new()
{
    { 999_999, false, 0 },
    { 1_000_000, false, 100_000 }
};

[Theory]
[MemberData(nameof(Holatlar))]
public void Hisobla_chegara_qiymatlari(decimal s, bool v, decimal k) => Assert.Equal(k, _h.Hisobla(s, v));

Asosiy Assert lar

Assert.Equal(5, natija);
Assert.NotEqual(0, natija);
Assert.True(m.Faol);
Assert.False(royxat.Any());
Assert.Null(topilgan);
Assert.NotNull(dto);
Assert.Empty(royxat);
Assert.Single(royxat);
Assert.Contains("telefon", nom);
Assert.Contains(royxat, m => m.Narx > 1000);
Assert.InRange(narx, 100, 500);
Assert.IsType<MahsulotDto>(obj);

var ex = Assert.Throws<BiznesQoidaException>(() => service.Sot(id, 100));
Assert.Contains("Omborda", ex.Message);

await Assert.ThrowsAsync<TopilmadiException>(() => service.OlAsync(999, CancellationToken.None));

[!TIP] Ko'proq o'qiladigan sintaksis uchun Shouldly (natija.ShouldBe(5)) yoki AwesomeAssertions (natija.Should().Be(5)) kutubxonalari bor. FluentAssertions 8-versiyadan tijoriy litsenziyaga o'tgan — AwesomeAssertions uning bepul davomi.


Ishga tushirish

dotnet test
Passed!  - Failed:     0, Passed:    12, Skipped:     0, Total:    12, Duration: 48 ms
dotnet test --filter "FullyQualifiedName~Chegirma"     # faqat moslari
dotnet test --collect:"XPlat Code Coverage"            # qamrov

Visual Studio / Rider da — Test Explorer.


Setup va tozalash

xUnit da har bir test uchun yangi klass nusxasi yaratiladi — konstruktor = setup, IDisposable.Dispose = tozalash:

public class SavatTests : IDisposable
{
    private readonly Savat _savat;

    public SavatTests() => _savat = new Savat();          // har bir testdan oldin
    public void Dispose() { /* har bir testdan keyin */ }
}

Bir nechta test uchun umumiy qimmat resurs — IClassFixture<T> (78-dars).


Nimani unit test qilish

✅ Biznes mantiq, hisoblashlar, validatsiya qoidalari, holat o'tishlari, mapping ❌ Framework kodi (EF Core, ASP.NET o'zi), oddiy getter/setter, bazaga bog'liq so'rovlar (integration test — 78-dars)


Tez-tez uchraydigan xatolar

Bitta testda ko'p narsani tekshirish — qaysi qismi buzilgani noaniq.

Testlar bir-biriga bog'liq (statik holat orqali) — tartib o'zgarsa yiqiladi.

DateTime.Now ni to'g'ridan-to'g'ri ishlatish — natija vaqtga bog'liq; TimeProvider ni inject qiling va testda FakeTimeProvider.


Xulosa

Unit test — kichik qismni alohida, tez va takrorlanadigan tekshirish. xUnit: [Fact] — bitta holat, [Theory] + [InlineData]/[MemberData] — bir nechta ma'lumot. AAA naqshi; tushunarli nomlar; chegara qiymatlari.

Assert.Equal, Throws/ThrowsAsync, Contains, Empty... ; dotnet test. Har test — yangi klass nusxasi (konstruktor = setup). Biznes mantiqni unit test qiling; bog'liqliklar — mock (77-dars), baza — integration test (78-dars).


Keyingi dars

77-dars: Mocking — Moq, interface, dependency larni almashtirish