Czy testować jednostkowo próby zapisania w DB?

0

Zastanawiam się, czy jest sens testowania jednostkowo próby zapisania / zmiany w DB? Czy to byłaby dobra praktyka? Czy lepiej zrobić to integracyjnie? A poniżej przykład:

Posiadam metodę serwisową która resetuje hasło:

public async Task<bool> ResetPassword(AppUser user, string token, string password)
        {
            IdentityResult result = await _userManager.ResetPasswordAsync(user, token, password);

            if (!result.Succeeded)
            {
                return false;
            }

            if (!await UpdateDbWithUser(user))
            {
                return false;
            }

            return true;
        }

Oczywistą dla mnie jest, że powinienem przetestować scenariusz dla różnych if (!result.Succeeded). Jednak nie mam pewności czy powinienem przetestować if (!await UpdateDbWithNewUser(user)), jeśli:

private async Task<bool> UpdateDbWithUser(AppUser user)
        {
            try
            {
                await _userManager.UpdateAsync(user);
                return true;
            }
            catch
            {
                return false;
            }
        }

Tutaj korzystam z ORM który powinien sam nadpisać w bazie użytkownika. Z jednej strony po co testować ORM? Z drugiej może być coś nie tak z bazą, więc napisałem taki test:

[Fact]
        private async Task ResetPassword_OnUpdateAsyncThrowingException_ReturnsFalse()
        {
            _userManagerMock.Setup(mock => mock.UpdateAsync(It.IsAny<AppUser>())).ThrowsAsync(new Exception());

            bool result = await _service.ResetPassword(new AppUser(), "some_token", "some_password");

            Assert.False(result);
        }

Ale jak sobie pomyślę, że jak będę try-catchował każdą metodę która uderza do DB, to lista testów się podwoi (przesadzając trochę). Co z tym zrobić?

1

Zacząłbym od zastanowienia się czy na pewno chcesz testować rzeczy które nie zależą od ciebie w danym kontekście. Jeżeli wywali ci się baza to problem ze zmianą hasła będzie mało ważny.

Co do testów - jeśli od wyniku jakieś operacji zależy wynik działania albo wybór ścieżki to jak najbardziej warto to testować. Ja bym jednak wybrał wariant izolowany bo w tym kontekście mało by mnie obchodziło czy baza działa czy nie, czy w ogóle jest jakaś baza. Po to masz abstrakcję w postaci user managera i ORM żeby się tym nie przejmować.Wyobrażasz sobie pisanie poważnej biznesowej aplikacji w której oprócz zachowania testujesz także działanie całej infrastruktury?
Oczywiście, testy integracyjne są ważne ale dotyczy to głównie bardziej złożonych biznesowo aspektów współdziałania różnych komponentów.

2

Powinieneś przetestować czy działa reset hasła a nie czy działa if. Nie pisze w C# więc nie wiem jak działają tam ORM, ale masz różne mechanizmy które leżą po Twojej stronie i które warto zweryfikować, jak chociażby poprawne mapowanie encji, więc twierdzenie że testowałbys tylko ORM jest nieprawidłowe.

0

Właśnie sobie uświadomiłem, że przecież UpdateAsync(user) zwraca IdentityResult(), więc zamiast try-catchować mogę to przetestować inaczej (to jak chodzi o menedżera użytkowników).

Bądź co bądź, pytanie jest wciąż aktualne, w kontekście innych zapytań do DB, choć wtedy one się odbywają przez Repo / ORM.

2

@bakunet: czy zawsze korzystasz bezpośrednio z ORM, czy jednak korzystasz też z normalnego SQL?
Bo chyba nie każda operacja na repo to jest usuwanie encji x z id o wartości y. Jak dla mnie to jest element który trzeba przetestować. Kwestia sporna może być czy za każdym razem, czy tylko na CI/CD ale warstwa perzystencji powinna być testowana.

2

Generalnie warto testować tylko metodę zawierające logikę, funkcji, która tylko wykonuje tylko zapytanie do bazy, raczej nie warto. Powyższej metody raczej bym nie testował. Jeśli decydujemy się testować, pamiętamy, że w teście jednostkowym używamy mocka, a nie prawdziwej bazy (to samo dotyczy plików i socketów).

2

@elwis: no masz rację. Napiszesz zapytanie SQL i będzie na pewno działać. Nawet jak jest to select do jakiegoś raportu który ma złożone joiny, group by having i tym podobne to nie warto testować.

1

@Aleksander32: No, to wtedy masz logikę tylko napisaną w SQLu. Też język programowania, nie? Poza tym, wciąż mamy testy integracyjne, one też mogą takie rzeczy wyłapywać. Zupełnie pomijam kwestię SQL injection, bo nie wyobrażam sobie składać zapytań bez narzędzi przed tym zabezpieczających.

6

To jak już przepiszemy ten kod, żeby przestać żonglować ifami, boolami i nie mieć pokemon exception handlingu:

public async Task<bool> ResetPassword(AppUser user, string token, string password)
{
	IdentityResult result = await _userManager.ResetPasswordAsync(user, token, password);

	if (result.Succeeded)
	{
		await _userManager.UpdateAsync(user);
	}

	return result.Succeeded;
}

Bez pokemon exception handling test, który pokazałeś traci sens. Win-win.

Właściwie jedyne, co tu możemy sprawdzić, to to, czy jeśli _userManager zwróci true, to czy wywołujemy UpdateAsync.
Jeśli to sprawdzimy, to jedyne co w ten sposób osiągamy, to unikamy ryzyka, że jakiś junior albo co gorsza senior w projekcie to przypadkiem skasuje podczas refaktoryzacji. Jeśli nie ma tego ryzyka, to prawdopodobnie nie warto mieć takiego testu.
Zakładam, że ta ścieżka i tak zostanie pokryta integracyjnie (tj. prawdziwym testem na działającej aplikacji).

0
somekind napisał(a):

To jak już przepiszemy ten kod, żeby przestać żonglować ifami, boolami i nie mieć pokemon exception handlingu

Ostatecznie skończyłem z taką metodą:

public async Task<bool> ResetPassword(AppUser user, string token, string password)
        {
            IdentityResult reset = await _userManager.ResetPasswordAsync(user, token, password);

            if (!reset.Succeeded)
            {
                return false;
            }

            IdentityResult update = await _userManager.UpdateAsync(user);

            if (!update.Succeeded)
            {
                return false;
            }

            return true;
        }

Ale faktycznie, poszedłem o jeden if za daleko.

Dzięki wszystkim za odpowiedzi.

1 użytkowników online, w tym zalogowanych: 0, gości: 1