unit-testing
Testy jednostkowe: najlepsze praktyki
Szukaj…
Wprowadzenie
Test jednostkowy jest najmniejszą testowalną częścią aplikacji, taką jak funkcje, klasy, procedury, interfejsy. Testowanie jednostkowe to metoda, według której poszczególne jednostki kodu źródłowego są testowane w celu ustalenia, czy nadają się do użycia. Testy jednostkowe są w zasadzie pisane i wykonywane przez twórców oprogramowania, aby upewnić się, że kod spełnia jego wymagania projektowe i zachowuje się zgodnie z oczekiwaniami.
Dobre nazewnictwo
Znaczenie dobrego nazewnictwa najlepiej ilustrują złe przykłady:
[Test]
Test1() {...} //Cryptic name - absolutely no information
[Test]
TestFoo() {...} //Name of the function - and where can I find the expected behaviour?
[Test]
TestTFSid567843() {...} //Huh? You want me to lookup the context in the database?
Dobre testy wymagają dobrych nazwisk. Dobry test nie testuje metod, scenariuszy testowych ani wymagań.
Dobre nazewnictwo zapewnia również informacje o kontekście i oczekiwanym zachowaniu. Idealnie, gdy test zakończy się niepowodzeniem na komputerze kompilacyjnym, powinieneś być w stanie zdecydować, co jest nie tak, bez patrzenia na kod testowy, a nawet trudniej, z koniecznością debugowania go.
Dobre nazewnictwo oszczędza czas na czytanie kodu i debugowanie:
[Test]
public void GetOption_WithUnkownOption_ReturnsEmptyString() {...}
[Test]
public void GetOption_WithUnknownEmptyOption_ReturnsEmptyString() {...}
Dla początkujących pomocne może być uruchomienie nazwy testu z gwarantowanym prefiksem. Zacznij od „Zadbaj o to”, aby zacząć myśleć o scenariuszu lub wymaganiu, które wymaga testu:
[Test]
public void EnsureThat_GetOption_WithUnkownOption_ReturnsEmptyString() {...}
[Test]
public void EnsureThat_GetOption_WithUnknownEmptyOption_ReturnsEmptyString() {...}
Nazewnictwo jest ważne także dla urządzeń testowych. Nazwij urządzenie testowe po testowanej klasie:
[TestFixture]
public class OptionsTests //tests for class Options
{
...
}
Ostateczny wniosek jest następujący:
Dobre nazewnictwo prowadzi do dobrych testów, które prowadzą do dobrego zaprojektowania kodu produkcyjnego.
Od prostych do złożonych
Tak samo, jak w przypadku pisania klas - zacznij od prostych przypadków, a następnie dodawaj wymagania (aka testy) i implementację (aka kod produkcyjny) dla każdego przypadku:
[Test]
public void EnsureThat_IsLeapYearIfDecimalMultipleOf4() {...}
[Test]
public void EnsureThat_IsNOTLeapYearIfDecimalMultipleOf100 {...}
[Test]
public void EnsureThat_IsLeapYearIfDecimalMultipleOf400 {...}
Nie zapomnij o kroku refaktoryzacji, gdy zakończysz z wymaganiami - najpierw refaktoryzuj kod, a następnie refaktoryzuj testy
Po zakończeniu powinieneś mieć pełną, aktualną i DOKŁADNĄ dokumentację swojej klasy.
Koncepcja MakeSut
Kod testowy ma takie same wymagania jakościowe, jak kod produkcyjny. MakeSut ()
- poprawia czytelność
- można łatwo refaktoryzować
- doskonale obsługuje wstrzykiwanie zależności.
Oto koncepcja:
[Test]
public void TestSomething()
{
var sut = MakeSut();
string result = sut.Do();
Assert.AreEqual("expected result", result);
}
Najprostsza funkcja MakeSut () zwraca właśnie testowaną klasę:
private ClassUnderTest MakeSUT()
{
return new ClassUnderTest();
}
Gdy potrzebne są zależności, można je wprowadzić tutaj:
private ScriptHandler MakeSut(ICompiler compiler = null, ILogger logger = null, string scriptName="", string[] args = null)
{
//default dependencies can be created here
logger = logger ?? MockRepository.GenerateStub<ILogger>();
...
}
Można powiedzieć, że MakeSut jest po prostu prostą alternatywą dla metod konfiguracji i porzucania dostarczanych przez frameworki Testrunner i może uważać te metody za lepsze miejsce do konfiguracji i porzucania testów.
Każdy może sam zdecydować, którego użyć. Dla mnie MakeSut () zapewnia lepszą czytelność i znacznie większą elastyczność. Wreszcie, koncepcja jest niezależna od jakiegokolwiek frameworka testrunner.