xUnit vs NUnit vs MSTest — który framework testowy wybrać w C#?
Wszystkie trzy frameworki robią to samo: uruchamiają metody testowe i sprawdzają, czy wynik jest zgodny z oczekiwaniem. Różnią się atrybutami, sposobem parametryzacji i tym, jak bardzo są “opiniotwórcze”. Poniżej ten sam test w każdym z nich — różnice widać od razu.
xUnit — domyślny wybór w nowych projektach .NET
using Xunit;
public class KalkulatorTests
{
[Fact]
public void Dodaj_DwieLiczby_ZwracaSume()
{
var wynik = Kalkulator.Dodaj(2, 3);
Assert.Equal(5, wynik);
}
[Theory]
[InlineData(2, 3, 5)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
public void Dodaj_RozneLiczby_ZwracaPoprawnaSume(int a, int b, int oczekiwany)
{
Assert.Equal(oczekiwany, Kalkulator.Dodaj(a, b));
}
}xUnit nie ma atrybutu [SetUp] — zamiast tego setup wspólny dla testów w klasie umieszcza się w konstruktorze, a teardown w IDisposable.Dispose():
public class BazaDanychTests : IDisposable
{
private readonly TestDbContext _kontekst;
public BazaDanychTests() // odpowiednik [SetUp] — wykonuje się przed KAŻDYM testem
{
_kontekst = TworzKontekstTestowy();
}
public void Dispose() // odpowiednik [TearDown]
{
_kontekst.Dispose();
}
}To świadoma decyzja projektowa xUnit: każdy test dostaje świeżą instancję klasy testowej, więc nie ma współdzielonego stanu między testami przez pola klasy — trudniej przypadkiem napisać test zależny od kolejności wykonania innych testów.
NUnit — starszy, bardziej rozbudowany
using NUnit.Framework;
[TestFixture]
public class KalkulatorTests
{
[SetUp]
public void PrzedKazdymTestem()
{
// wykonuje się przed każdym testem w klasie
}
[Test]
public void Dodaj_DwieLiczby_ZwracaSume()
{
var wynik = Kalkulator.Dodaj(2, 3);
Assert.That(wynik, Is.EqualTo(5));
}
[TestCase(2, 3, 5)]
[TestCase(-1, 1, 0)]
[TestCase(0, 0, 0)]
public void Dodaj_RozneLiczby_ZwracaPoprawnaSume(int a, int b, int oczekiwany)
{
Assert.That(Kalkulator.Dodaj(a, b), Is.EqualTo(oczekiwany));
}
[TearDown]
public void PoKazdymTescie()
{
// sprzątanie po każdym teście
}
}NUnit ma jawne [SetUp]/[TearDown] oraz dodatkowo [OneTimeSetUp]/[OneTimeTearDown] (wykonują się raz na całą klasę, nie przed/po każdym teście) — więcej kontroli, ale też więcej atrybutów do zapamiętania. Składnia asercji Assert.That(wartość, Is.EqualTo(...)) jest bardziej czytelna w złożonych warunkach (Is.GreaterThan, Contains.Item, Throws.TypeOf<T>()).
MSTest — natywny wybór Microsoftu
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class KalkulatorTests
{
[TestInitialize]
public void PrzedKazdymTestem()
{
// odpowiednik [SetUp]
}
[TestMethod]
public void Dodaj_DwieLiczby_ZwracaSume()
{
var wynik = Kalkulator.Dodaj(2, 3);
Assert.AreEqual(5, wynik);
}
[DataTestMethod]
[DataRow(2, 3, 5)]
[DataRow(-1, 1, 0)]
[DataRow(0, 0, 0)]
public void Dodaj_RozneLiczby_ZwracaPoprawnaSume(int a, int b, int oczekiwany)
{
Assert.AreEqual(oczekiwany, Kalkulator.Dodaj(a, b));
}
}MSTest jest utrzymywany bezpośrednio przez Microsoft i domyślnie zintegrowany z Visual Studio (żadnych dodatkowych pakietów do “po prostu kliknij Run Tests”). Składnia jest zbliżona do NUnit ([TestClass]/[TestMethod] zamiast [TestFixture]/[Test]), ale historycznie miał mniej możliwości niż NUnit — różnica ta w ostatnich wersjach się zaciera.
Kluczowe różnice w jednej tabeli
| xUnit | NUnit | MSTest | |
|---|---|---|---|
| Test jednostkowy | [Fact] | [Test] | [TestMethod] |
| Test parametryzowany | [Theory] + [InlineData] | [TestCase] | [DataTestMethod] + [DataRow] |
| Setup przed testem | konstruktor klasy | [SetUp] | [TestInitialize] |
| Teardown po teście | IDisposable.Dispose() | [TearDown] | [TestCleanup] |
| Setup raz na klasę | IClassFixture<T> | [OneTimeSetUp] | [ClassInitialize] |
| Domyślna asercja | Assert.Equal(oczekiwany, wynik) | Assert.That(wynik, Is.EqualTo(oczekiwany)) | Assert.AreEqual(oczekiwany, wynik) |
| Stan współdzielony między testami | nie (nowa instancja per test) | tak (chyba że jawnie wyczyścisz) | tak (chyba że jawnie wyczyścisz) |
Który wybrać?
xUnit — domyślny wybór dla nowych projektów .NET Core/.NET 5+. Używany w dokumentacji Microsoft, w szablonach dotnet new, i w większości nowych projektów open-source. Konwencja “nowa instancja na test” wymusza pisanie testów niezależnych od siebie, co długoterminowo daje mniej ukrytych bugów w samych testach.
NUnit — dobry wybór, jeśli pracujesz w starszym projekcie, który już go używa, albo potrzebujesz bogatszej biblioteki asercji (Assert.That z fluent-style warunkami) bez dodawania osobnego pakietu jak FluentAssertions.
MSTest — sensowny, jeśli firma/zespół ma zasadę “tylko oficjalne narzędzia Microsoft”, albo integrujesz testy z istniejącym pipeline’em opartym już na MSTest. W nowym, niezależnym projekcie rzadko jest pierwszym wyborem.
W praktyce różnice między frameworkami są mniejsze niż mogłyby się wydawać — logika testowa (Arrange-Act-Assert) i to, co testujesz, ma większe znaczenie niż wybrany framework. Zobacz TDD w praktyce w C# i Unit Testy w C# z xUnit i FluentAssertions, gdzie xUnit jest używany w realnym projekcie.
Podsumowanie
xUnit, NUnit i MSTest różnią się głównie nazwami atrybutów i podejściem do współdzielenia stanu między testami — xUnit tworzy nową instancję klasy testowej na każdy test (mniej przypadkowych zależności), NUnit i MSTest współdzielą stan przez pola klasy z jawnym [SetUp]/[TearDown]. Dla nowego projektu .NET domyślnym wyborem jest xUnit; NUnit i MSTest mają sens głównie w kontekście istniejącego kodu, który już ich używa.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
1 comment