Walczę z coraz bardziej irytującym problemem dotyczącym naszych testów jednostkowych, które wdrażamy w moim zespole. Próbujemy dodać testy jednostkowe do starszego kodu, który nie został dobrze zaprojektowany i chociaż nie mieliśmy żadnych trudności z faktycznym dodaniem testów, zaczynamy zmagać się z tym, jak przebiegają testy.
Jako przykład problemu załóżmy, że masz metodę, która wywołuje 5 innych metod w ramach jej wykonania. Testem dla tej metody może być potwierdzenie, że zachowanie występuje w wyniku wywołania jednej z tych 5 innych metod. Ponieważ test jednostkowy powinien zakończyć się niepowodzeniem tylko z jednego powodu i tylko z jednego powodu, chcesz wyeliminować potencjalne problemy wywołane tymi 4 innymi metodami i wyśmiewać je. Świetny! Test jednostkowy jest wykonywany, wyśmiewane metody są ignorowane (a ich zachowanie można potwierdzić w ramach innych testów jednostkowych), a weryfikacja działa.
Ale pojawia się nowy problem - test jednostkowy ma dogłębną wiedzę na temat tego, w jaki sposób potwierdziłeś, że zachowanie i wszelkie zmiany sygnatur w dowolnej z pozostałych 4 metod w przyszłości, lub wszelkie nowe metody, które należy dodać do „metody nadrzędnej”, będą skutkuje koniecznością zmiany testu jednostkowego, aby uniknąć możliwych awarii.
Oczywiście problem można nieco złagodzić, po prostu dzięki temu, że więcej metod pozwala osiągnąć mniej zachowań, ale miałem nadzieję, że może być dostępne bardziej eleganckie rozwiązanie.
Oto przykładowy test jednostkowy, który wychwytuje problem.
W skrócie „MergeTests” to klasa testów jednostkowych, która dziedziczy z klasy, którą testujemy i zastępuje zachowanie w razie potrzeby. Jest to „wzorzec”, który stosujemy w naszych testach, aby umożliwić nam zastąpienie wywołań zewnętrznych klas / zależności.
[TestMethod]
public void VerifyMergeStopsSpinner()
{
var mockViewModel = new Mock<MergeTests> { CallBase = true };
var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());
mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
mockViewModel.Setup(
m =>
m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
It.IsAny<bool>()));
mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
mockViewModel.Setup(m => m.SwitchToOverviewTab());
mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));
mockViewModel.Object.OnMerge(It.IsAny<MergeState>());
mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}
Jak reszta z was sobie z tym poradziła lub czy nie ma świetnego „prostego” sposobu radzenia sobie z tym?
Aktualizacja - doceniam opinie wszystkich. Niestety i nie jest to żadną niespodzianką, nie wydaje się, aby istniało świetne rozwiązanie, wzorzec lub praktyka, które można zastosować w testach jednostkowych, jeśli testowany kod jest słaby. Zaznaczyłem odpowiedź, która najlepiej uchwyciła tę prostą prawdę.