Debugowanie nieudanych testów
Proszę zapewnić powtarzalność błędów, usunąć zależności od czasu i losowości, ograniczyć problem do minimalnego przypadku oraz ulepszyć komunikaty asercji, aby szybciej diagnozować problemy.
Debugowanie nieudanych testów to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 3 z 3. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 3 lekcji w sumie.
Plan działania
Plan naprawiania nieudanych testów:
- Odtwórz problem powtarzalnie
- Usuń niestabilność (czas, losowość, wejście-wyjście)
- Zmniejsz przypadek do najmniejszego, który kończy się niepowodzeniem
- Dodaj jasne komunikaty asercji
Niestabilność z założenia
Korzystanie z UtcNow i domyślnego Random sprawia, że zachowanie różni się między uruchomieniami → niestabilne testy.
using System;
// Demo: occasionally fails because it depends on current second and default Random()
public static class Promo
{
// Gives bonus on even seconds; random adds small jitter
public static int BonusNow()
{
int baseBonus = (DateTime.UtcNow.Second % 2 == 0) ? 10 : 9;
Random r = new Random(); // time-based seed → unpredictable
return baseBonus + (r.Next(0, 2)); // 0 or 1 more
}
}
public class Program
{
public static void Main(string[] args)
{
// Simulated test: sometimes expects exactly 10, but result varies
int got = Promo.BonusNow();
if (got != 10) Console.WriteLine("FAIL (flaky): expected 10 but got " + got);
else Console.WriteLine("PASS (maybe): got " + got);
}
}
Kontrolowanie zależności
Wstrzyknij czas i Random. Przy stałym ziarnie i stałym znaczniku czasu błąd staje się powtarzalny.
using System;
public interface IClock { DateTime NowUtc(); }
public sealed class FakeClock : IClock
{
private readonly DateTime _fixedUtc;
public FakeClock(DateTime fixedUtc) { _fixedUtc = fixedUtc; }
public DateTime NowUtc() { return _fixedUtc; }
}
public sealed class PromoDeterministic
{
private readonly IClock _clock;
private readonly Random _rng;
public PromoDeterministic(IClock clock, Random rng) { _clock = clock; _rng = rng; }
public int BonusNow()
{
int baseBonus = (_clock.NowUtc().Second % 2 == 0) ? 10 : 9;
return baseBonus + _rng.Next(0, 2);
}
}
public static class Assert
{
public static void AreEqual(int expected, int actual, string msg)
{
if (expected != actual) throw new Exception("Expected " + expected + " but got " + actual + " :: " + msg);
}
}
public class Program
{
public static void Main(string[] args)
{
try
{
IClock clock = new FakeClock(new DateTime(2016, 1, 1, 0, 0, 20, DateTimeKind.Utc)); // even second
Random rng = new Random(123); // fixed seed
PromoDeterministic p = new PromoDeterministic(clock, rng);
int got = p.BonusNow(); // predictable now
Assert.AreEqual(10, got, "even second, first draw should be 0");
Console.WriteLine("PASS: deterministic promo");
}
catch (Exception ex)
{
Console.WriteLine("FAIL: " + ex.Message);
}
}
}
Minimalny przypadek odtwarzający błąd
Zmniejszaj dane wejściowe, aż niewielki przykład zakończy się niepowodzeniem (tutaj: pusta tablica). Z małymi przypadkami łatwiej pracować.
using System;
// Buggy function: average of ints, but divides by count without guarding empty input
public static class Stats
{
public static double Average(int[] xs)
{
int sum = 0;
for (int i = 0; i < xs.Length; i++) sum += xs[i];
return sum / xs.Length; // BUG: divide by zero if empty; also int division truncates
}
}
public static class Assert
{
public static void Throws<T>(Action a, string msg) where T : Exception
{
try { a(); }
catch (Exception ex)
{
if (ex is T) return;
throw new Exception("Expected " + typeof(T).Name + " but got " + ex.GetType().Name + " :: " + msg);
}
throw new Exception("Expected " + typeof(T).Name + " but no exception thrown :: " + msg);
}
}
public class Program
{
public static void Main(string[] args)
{
// Minimal failing case: empty array
Assert.Throws<DivideByZeroException>(delegate { Stats.Average(new int[0]); }, "empty input");
Console.WriteLine("Reproduced: empty input triggers the bug.");
}
}
Naprawa i jakość komunikatów
Zabezpieczaj się przed nieprawidłowymi danymi wejściowymi i używaj opisowych komunikatów asercji (uwzględniaj dane wejściowe, oczekiwany wynik i epsilon).
using System;
public static class Stats
{
public static double Average(int[] xs)
{
if (xs == null) throw new ArgumentNullException("xs");
if (xs.Length == 0) throw new ArgumentException("xs must not be empty", "xs");
double sum = 0.0; // use double to avoid integer truncation
for (int i = 0; i < xs.Length; i++) sum += xs[i];
return sum / xs.Length;
}
}
public static class Assert
{
public static void AreAlmostEqual(double expected, double actual, double eps, string msg)
{
if (Math.Abs(expected - actual) > eps)
throw new Exception("Expected " + expected + " but got " + actual + " (eps " + eps + ") :: " + msg);
}
}
public class Program
{
public static void Main(string[] args)
{
try
{
double a = Stats.Average(new int[] { 2, 4, 6 });
Assert.AreAlmostEqual(4.0, a, 1e-9, "average of 2,4,6");
Console.WriteLine("PASS: fixed average with clear message");
}
catch (Exception ex)
{
Console.WriteLine("FAIL: " + ex.Message);
}
}
}
Lista kontrolna debugowania
Lista kontrolna debugowania:
- Stabilizuj zależności (czas, losowość, wejście-wyjście)
- Odtwarzaj problem lokalnie i wielokrotnie
- Zmniejszaj dane do minimalnego przypadku kończącego się niepowodzeniem
- Dodawaj precyzyjne komunikaty asercji
- Preferuj funkcje czyste i kontrole ochronne
Pierwszy krok przy niestabilnych testach
Podsumowanie
Podsumowanie: Odtwarzaj problem powtarzalnie, kontrolując czas i losowość, zmniejszaj dane wejściowe oraz pisz pomocne komunikaty asercji, aby szybko wskazać błąd.
Ucz się C# dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 93
- Lekcje
- 346
Często zadawane pytania
Czy lekcja „Debugowanie nieudanych testów” jest bezpłatna?
Tak — pełny tekst „Debugowanie nieudanych testów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 3 lekcji w sumie.
Co nauczysz się w „Debugowanie nieudanych testów”?
Proszę zapewnić powtarzalność błędów, usunąć zależności od czasu i losowości, ograniczyć problem do minimalnego przypadku oraz ulepszyć komunikaty asercji, aby szybciej diagnozować problemy. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 3.
Ile czasu zajmuje lekcja „Debugowanie nieudanych testów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Konfiguracja xUnit/MSTest i asercje
- Fakes za pośrednictwem interfejsów i kreatory danych testowych
- Debugowanie nieudanych testów