Unit Test — xUnit, [Fact], [Theory], Assert metodlar
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