Czy stworzenie interfejsu do przechowywania stałych w testach to dobry pomysł?

0

Cześć,
mam takie przykładowe testy:

  @Test
    void whenAuthenticationFails_SessionNotCreated() {
        String sessionToken = "2eds2etfghthheyyyjh536t3fasd235teg";
        LoginRequest request = loginHelper.loginInvalidPassword("User");
        assertThrows(BadCredentialsException.class,
                () -> userSessionCreator.createUserSession(request, sessionToken));
        assertThat(sessionChecker.userHasSession("User")).isFalse();
    }

    @Test
    void whenSessionCreated_SessionIsStored() {
        String sessionToken = "2eds2etfghthheyyyjh536t3fasd235teg";
        userSessionCreator.createUserSession(loginHelper.login("User"), sessionToken);
        assertThat(sessionChecker.userHasSession("User")).isTrue();
    }

    @Test
    void whenSessionCreated_UserIdIsCorrect() {
        String sessionToken = "2eds2etfghthheyyyjh536t3fasd235teg";
        userSessionCreator.createUserSession(loginHelper.login("User"), sessionToken);
        Session session = sessionFetcher.fetchSessionForToken("2eds2etfghthheyyyjh536t3fasd235teg");
        assertThat(session.getUserId()).isEqualTo(1);
    }

Czy dobrym pomysłem będzie stworzenie jakiegoś wspólnego interfejsu dla klas testowych, w którym będę przechowywał stałe?

Myślałem o czymś takim:

public interface TestConstants {
    String SESSION_TOKEN = "2eds2etfghthheyyyjh536t3fasd235teg";
    int USER_ID = 1;
    String USERNAME = "User";
}
  @Test
    void whenAuthenticationFails_SessionNotCreated() {
        LoginRequest request = loginHelper.loginInvalidPassword(TestConstants.USERNAME);
        assertThrows(BadCredentialsException.class,
                () -> userSessionCreator.createUserSession(request, TestConstants.SESSION_TOKEN));
        assertThat(sessionChecker.userHasSession(TestConstants.USERNAME)).isFalse();
    }

    @Test
    void whenSessionCreated_SessionIsStored() {
        userSessionCreator.createUserSession(loginHelper.login(TestConstants.USERNAME), TestConstants.SESSION_TOKEN);
        assertThat(sessionChecker.userHasSession(TestConstants.USERNAME)).isTrue();
    }

    @Test
    void whenSessionCreated_UserIdIsCorrect() {
        userSessionCreator.createUserSession(loginHelper.login(TestConstants.USERNAME), TestConstants.SESSION_TOKEN);
        Session session = sessionFetcher.fetchSessionForToken(TestConstants.SESSION_TOKEN);
        assertThat(session.getUserId()).isEqualTo(TestConstants.USER_ID);
    }

Co myślicie?

Dzięki za pomoc

1

Jak dla mnie to zaciemnia istotę testu i nie ma żadnego znaczenia bo tak naprawdę ten session id może byc losowy a tylko musi być identyczny w przypadku zapisu i pobierania.
Jedynie co to moim zdaniem można użyć tej samej zmiennej wewnątrz testu:

    @Test
    void whenSessionCreated_UserIdIsCorrect() {
        String sessionToken = "2eds2etfghthheyyyjh536t3fasd235teg";
        userSessionCreator.createUserSession(loginHelper.login("User"), sessionToken);
        Session session = sessionFetcher.fetchSessionForToken(sessionToken); // zamiast "2eds2etfghthheyyyjh536t3fasd235teg"
        assertThat(session.getUserId()).isEqualTo(1);
    }

żeby podkreślić że to ma być ta sama wartość. Wydzielanie tego do stałych we wspólnym pliku sugeruje że te wartości mają znaczenie i muszą takie być a tak naprawdę mogą być dowolne no i trzeba sięgać głębiej żeby się dowiedzieć co to za wartości

1

Moim zdaniem to jest mega słaby pomysł, i to z kilku powodów.

  1. Pierwszy i najważniejszy - ukryte powiązania między testami.
  2. Drugi - widzisz taki interfejs, widzisz stałą w nim - i nie wiesz w jaki sposób zmiana tych danych wpłynie na pozostałe testy.
  3. Trzeci - ja, jako czytający kod, nie mam zielonego pojęcia co chcesz osiągnąć tym zabiegiem.

Najlepiej by było @Ornstein jakbyś powiedział jaki masz problem który próbujesz rozwiązać, i wtedy się zaproponuje najlepsze rozwiązanie.

Jeśli Twoim problemem jest to że podobne wartości się pojawiają w wielu miejscach w teście, to należy to wydzielić do metody, np tak:

@Test
void whenAuthenticationFails_SessionNotCreated() {
    LoginRequest request = loginHelper.loginInvalidPassword("User");
    assertThrows(BadCredentialsException.class, () -> createUserSession(request));
    assertThat(sessionChecker.userHasSession("User")).isFalse();
}

@Test
void whenSessionCreated_SessionIsStored() {
    createUserSession(loginHelper.login("User"));
    assertThat(sessionChecker.userHasSession("User")).isTrue();
}

@Test
void whenSessionCreated_UserIdIsCorrect() {
    createUserSession(loginHelper.login("User"));
    assertThat(sessionUserId()).isEqualTo(1);
}

private void createUserSession(LoginRequest request) {
  userSessionCreator.createUserSession(request, "2eds2etfghthheyyyjh536t3fasd235teg");
}

private int sessionUserId() {
  Session session = sessionFetcher.fetchSessionForToken("2eds2etfghthheyyyjh536t3fasd235teg");
  session.getUserId()
}

Co do testu whenSessionCreated_UserIdIsCorrect(), jeśli chcesz koniecznie sprawdzić czy jakiś konkretny session id jest użyty, to test mógłby wyglądać tak:

@Test
void whenSessionCreated_UserIdIsCorrect() {
    createUserSession(loginHelper.login("User"), "abc123-session-id");
    assertThat(sessionUserId("abc123-session-id")).isEqualTo(1);
}

Co do opinii przedmówcy, że dobrze jest wydzielić session ID do zmiennej żeby zaznaczyć że coś ma być tą samą wartością - tutaj bym się zastanowił. Bo jeśli test ma uwidaczniać to, że dana wejściowa i wyjściowa ma być taka sama - to wręcz na miejscu jest, żeby zduplikowana wartość się pojawiła dwukronie w teście - żeby uwidocznić to że ona jest dwa razy - na wejściu i na wyjściu. Wydzielenie do zmiennej semantycznie nic nie wnosi - oba testy są tożsame.

Niżej @dalbajob wypowiada się że lepiej wydzielić zmienną, a innego powodu - nie dlatego żeby usunąć duplikacje, tylko po to żeby nadać nazwę - czyli zrobić zmienną wyjaśniającą. I to nawet ma sens. Jeśli celem nie jest DRY tylko dodatkowy kontekst, to moim zdaniem można.

W przykłądzie który podałem wyżej to samo session id jest w dwóch metodach pomocniczych, createUserSession() i sessionUserId() - to jest okej - metody są blisko siebie, więc nie ma problemu. Jeśli ktoś chciałby "zbliżyć" te dwa session id tak żeby nie były w dwóch osobnych metodach, to te dwie metody należałoby wynieść do osobnego obiektu.

PS: @Ornstein A swoją drogą, te testy są dosyć dziwne. One faktycznie coś testują? Skąd to 1 się bierze w teście? Albo ten login "User"?

5

Stałe w teście są dobre. Widząc taki kod:

def test_calculator():
 assert add(5, 4) == 9

wiem jaka ma być logika nawet nie widząc ciała metody testowanej. A tutaj:

def test_calculator():
  assert add(AbstractTestDataFactory.getInstance().getParam1(), AbstractTestDataFactory.getInstance().getParam2()) == AbstractTestDataFactory.getInstance().getResult()

xd

Warto myśleć o czytelności. Jak jakaś stała jest z d**y i nie wpływa na test to warto wyrzucić kod tworzenia jakiegoś obiektu do osobnej metody, żeby ukryć to co niepotrzebne. W samym teście powinno być widoczne całe potrzebne mięsko do przeprowadzenia i rozumowania na temat danego testu. Test to inny kod niż produkcja, bo tu testujesz konkretne fizyczne przypadki a nie przerzucasz abstrakcjami. Zupełnie inne wymagania sprawiają, że kod musi po prostu wyglądać inaczej. Polecam ten artykuł https://mtlynch.io/good-developers-bad-tests/ .

1

Jeżeli wartość tej zmiennej nie wpływa na działanie testu, to moim zdaniem TAK wydzielenie jej do wspólnego miejsca to jest dobry pomysł.

Dlaczego? Bo widząc TestConstants.SESSION_TOKEN rozumiem, że tutaj ma być jakiś token, ale nie ważne jaki. Dla mnie to jest jasna informacja. To jest jak najbardziej w porządku i według mnie zwiększa czytelność.

Natomiast jeśli ma znaczenie (np jest to jakaś wartość w requeście która wpływa na logikę), to NIE, lepiej żeby było w teście widać co to za wartość

1
dalbajob napisał(a):

Dlaczego? Bo widząc TestConstants.SESSION_TOKEN rozumiem, że tutaj ma być jakiś token, ale nie ważne jaki. Dla mnie to jest jasna informacja. To jest jak najbardziej w porządku i według mnie zwiększa czytelność.

Jak ktoś zadaje sobie tyle trudu to kompletnie nie wpadłbym, że token jest bezużyteczny. Lepiej po prostu napisać new Token("bla") albo new Token("dupa")

0

Kompletnie nie rozumiem, czemu 99% programmerów z taką łatwością wynosi wspólne dane do jakichś constów (które są daleko od miejsca użycia, i tym samym dodaje ukryte powiązania), ale żeby w tym samym miejscu tak jak się powinno wydzielić klasę to nagle "uuuuuuu, overengineering" 🧐.

0
slsy napisał(a):
dalbajob napisał(a):

Dlaczego? Bo widząc TestConstants.SESSION_TOKEN rozumiem, że tutaj ma być jakiś token, ale nie ważne jaki. Dla mnie to jest jasna informacja. To jest jak najbardziej w porządku i według mnie zwiększa czytelność.

Jak ktoś zadaje sobie tyle trudu to kompletnie nie wpadłbym, że token jest bezużyteczny. Lepiej po prostu napisać new Token("bla") albo new Token("dupa")

Nie napisałem, że bezużyteczny, tylko "nie ważne jaki" (na potrzeby testu).

A dla mnie to wcale nie jest dużo trudu, a wręcz przeciwnie. Kiedy piszę nowy test nie zastanawiam się czy napisać new Token("dupa") czy new Token("bla") tylko sięgam po gotowca. A jeśli token musi np. być w konkretnym formacie, to wiem że token z testowych constów taki będzie i nie zastanawiam się czy muszę napisać 420 czy 2137 cyfr.

Idąc dalej, jeśli ten token i user są częścią setupu testu, i sam test nie potrzebuje testować funkcjonalności uwierzytelnienia czy cokolwiek to jest, to jeszcze wydzieliłbym to do oddzielnej metody.

Po prostu w teście chcę widzieć to, co ma znaczenie dla tego testu. Nie potrzebuję widzieć wszystkiego co się dzieje, w tym tworzenia wszystkich danych testowych.

Przy czym jedno czy dwa takie new Token("dupa") są jeszcze w porządku. problem robi się kiedy tego jest więcej

Riddle napisał(a):

Kompletnie nie rozumiem, czemu 99% programmerów z taką łatwością wynosi wspólne dane do jakichś constów (które są daleko od miejsca użycia, i tym samym dodaje ukryte powiązania), ale żeby w tym samym miejscu tak jak się powinno wydzielić klasę to nagle "uuuuuuu, overengineering" 🧐.

"Ukryte" powiązania do singletona z pakietu/modułu testów, przeżyję to jakoś ;p

0
Riddle napisał(a):

PS: @Ornstein A swoją drogą, te testy są dosyć dziwne. One faktycznie coś testują? Skąd to 1 się bierze w teście? Albo ten login "User"?

Przed testem tworzę w bazie danych użytkownika z id 1 i nazwą User. Stąd te wartości w teście.

A te testy testują tę klasę:

@Service
public class UserSessionCreator {

    private final SessionCreation sessionCreation;
    private final UserAuthentication userAuth;
    private final SessionDAO sessionDAO;
    private final UserDAO userDAO;

    public UserSessionCreator(SessionCreation sessionCreation,
                              UserAuthentication userAuth,
                              SessionDAO sessionDAO,
                              UserDAO userDAO) {
        this.sessionCreation = sessionCreation;
        this.userAuth = userAuth;
        this.sessionDAO = sessionDAO;
        this.userDAO = userDAO;
    }

    @Transactional
    public void createUserSession(LoginRequest request, String sessionToken) {
        Authentication auth = userAuth.authenticateUser(request);
        User user = userDAO.findByUsername(auth.getName()).orElseThrow(
                () -> new UserNotFoundException("User not found with username :" + auth.getName()));

        Session session = sessionCreation.createSession(sessionToken, user.getId());
        sessionDAO.save(session);
    }
}
3
Ornstein napisał(a):
Riddle napisał(a):

PS: @Ornstein A swoją drogą, te testy są dosyć dziwne. One faktycznie coś testują? Skąd to 1 się bierze w teście? Albo ten login "User"?

Przed testem tworzę w bazie danych użytkownika z id 1 i nazwą User. Stąd te wartości w teście.

To średnio.

Powinieneś te wartości tworzyć w teście.

Ornstein napisał(a):

A te testy testują tę klasę:

Nie powinieneś myśleć o testach w kontekście "testy testują klasę", to jest słabe podejście.

Powinieneś o nich myśleć w kontekście zachowania, np. "ten test mówi, że po zalogowaniu użytkownika, jego sesja jest zapisana na stałe". Albo "próba zalogowania z niepoprawnym loginem skutkuje błędem".

0
dalbajob napisał(a):

Dlaczego? Bo widząc TestConstants.SESSION_TOKEN rozumiem, że tutaj ma być jakiś token, ale nie ważne jaki. Dla mnie to jest jasna informacja. To jest jak najbardziej w porządku i według mnie zwiększa czytelność.

Ja bym to odczytał zupełnie na odwrót. Widząc token = "blablabla" myślę że nie ma znaczenia jaki to token, widząc token = TestConstants.SESSION_TOKEN wynoszę wrażenie że to musi być ten konkretny token który może mieć jakieś specjalne znaczenie. Jeszcze lepiej moim zdaniem byłoby wyrazić intencje i napisać wprost coś w stylu token = generateRandomToken()

Gdy w innym teście będę potrzebował drugi, inny token to mam dopisać constant na potrzeby tego jednego testu do wspólnego miejsca czy jeden token wziąć z jednego miejsca a drugi wpisać z palca? Zaczyna się robić bałagan i z czasem plik z constantami na rzecz spójności rozrasta się o nowe consty użyte tylko raz. Jeśli projekt rozwija kilku devów to te same wartości mogłyby być użyte w różnych miejscach, ale ktoś nie przejrzał wszystkich dostępnych constów przy pisaniu nowego testu i postanowił dodać własny. Teraz kolejna osoba to widzi i myśli że może to chyba połączyć ale nie ma pewności bez przeanalizowania tych testów na co nie ma ochoty więc bajzel zostaje na zawsze. To nie teoretyczny przypadek tylko wyjęte z życia mojego poprzedniego projektu.
I nie sądzę że

LoginRequest request = loginHelper.loginInvalidPassword(TestConstants.USERNAME);

jest bardziej czytelne niż

LoginRequest request = loginHelper.loginInvalidPassword("User");
Riddle napisał(a):

Co do opinii przedmówcy, że dobrze jest wydzielić session ID do zmiennej żeby zaznaczyć że coś ma być tą samą wartością - tutaj bym się zastanowił. Bo jeśli test ma uwidaczniać to, że dana wejściowa i wyjściowa ma być taka sama - to wręcz na miejscu jest, żeby zduplikowana wartość się pojawiła dwukronie w teście - żeby uwidocznić to że ona jest dwa razy - na wejściu i na wyjściu.

Zgodziłbym się może gdyby chodziło o asercję, ale tu mamy po prostu użycie tego samego id dwa razy na etapie arrange i act.
W przypadku asercji nie powinno się używać zmiennych bo może się zdarzyć że dajemy do testu na przykład instancję jakiegoś rekordu / tablicę a on(a) zmutował(a) gdzieś po drodze i asercja wcale nie sprawdza już tego co chcieliśmy i mamy false positive.

0

Ja bym to odczytał zupełnie na odwrót. Widząc token = "blablabla" myślę że nie ma znaczenia jaki to token, widząc token = TestConstants.SESSION_TOKEN wynoszę wrażenie że to musi być ten konkretny token który może mieć jakieś specjalne znaczenie.

😐

Dziwne podejście. Przecież "blablabla" jest bardziej konkretne niż SESSION_TOKEN. Stała jest bardziej ogólna niż dosłowna wartość, więc semantycznie ma mniejsze znaczenie.

To jest dla mnie taki zły symptom programowania a'la "chciałbym żeby coś było", tak jak ludzie czasem piszą metody storeSecureKey() i nazywają metodę "secure" bo myślą że przez to faktycznie będzie secure. Tak samo tutaj, ktoś wyciąga stringa do consta SESSION_TOKEN, bo myślą że w ten sposób ten string stanie się session token. A tak nie jest oczywiście.

Jeszcze lepiej moim zdaniem byłoby wyrazić intencje i napisać wprost coś w stylu token = generateRandomToken()

A to moim zdaniem już jest w ogóle bez sensu, bo jeśli to ma być random, to test staje się niedeterministyczny, a po drugie nawet gdyby to zwracało deterministycznie wyniki, to to choruje na chorobę czyli wyrażanie w kodzie tego co chcielibyśmy żeby było, co jest słabe. Trochę jak napisanie asercji assertTrue(isSecure). To że sobie użyjesz generateRandomToken() w teście, nie znaczy wcale że ten Twój kod będzie działał na randomowych tokenach.

Test ma pokazać konkretne zachowanie, dla konkretnej danej wejściowej.

Mając test:

user = loginUserWithSessionToken("abc123fed");
assertSessionId(user, "abc123fed");

Ten test jest pięęęęękny - mówi: mając usera z tokenem sesji "abc123fed", zapisany session id to "abc123fed". Czego więcej potrzebujesz? Co konkretnie zyskasz wydzielając tą liczbę do stałej albo do metody? Oprócz tego że potencjalnie ktoś jej użyje w innym miejscu, i stworzy ukryte powiązanie z tym testem, przez co zmiana consta potencjalnie zepsuje ten test?

Chyba że myślisz że jak wydzielisz to do zmiennej, np tak:

user = loginUserWithSessionToken(Constants.SESSION_TOKEN);
assertSessionId(user, Constants.SESSION_TOKEN);

to ten test będzie w stanie pokazać że chcemy zachowanie dla wszystkich stringów, np test mógłby mówić mając usera z jakimkolwiek tokenem sesji x, to zapisany token sesji jest właśnie ten x. Tylko że żeby mieć coś takiego, to musiałbyś ten test odpalić dla wszystkich stringów, żeby on to faktycznie mógł sprawdzić. Pisząc consta w teście sugerujesz czytającemu test, że test assertuje bardziej ogólne dane wejściowe, niż faktycznie testuje - bo faktycznie testuje tylko jedną; a skoro testuje jedną to powinien wyrazić że testuje tylko jedną - np pisząc stringa dosłownie w teście, albo ewentualnie wydzielając zmienną nazywającą.

Test to narzędzie falsyfikujące - działają tak że podaje się dane wejściowe, i sprawdza zamierzone zachowanie (Są języki w których można specyfikować działanie takie ogólne, w których można napisać dla dowolnej liczby pierwszej podzielnej przez dwa, wynik działania ma być `true` , np Eiffel, a taki styl programowania nazywa się "design by contract"; tylko że to jest dosyć spora nisza).

Także nie polecam. Lepiej zostawić jawny literal w teście.

1

Tak naprawdę tu mamy totalnie szczególny przypadek bo okazuje się że "User" i wartość "1" faktycznie szczególne i op postanowił te wartości wstawiać do bazy danych przed testami. W tym przypadku ma sens a wręcz myślę że obowiązkowe jest zapisanie tego jako consty i najlepiej jeszcze opatrzyć doc commentem żeby poinformować o tym przyszłych devów. Co do session_token żaden z kodów w pierwszym poście mnie nie przekonuje czy jest to losowa czy specjalna wartość. Można tylko zgadywać.

0
Riddle napisał(a):
Ornstein napisał(a):
Riddle napisał(a):

PS: @Ornstein A swoją drogą, te testy są dosyć dziwne. One faktycznie coś testują? Skąd to 1 się bierze w teście? Albo ten login "User"?

Przed testem tworzę w bazie danych użytkownika z id 1 i nazwą User. Stąd te wartości w teście.

To średnio.

Powinieneś te wartości tworzyć w teście.

W taki sposób?

@Component
public class TestUserFactory {

    @Autowired
    private UserDAO userDAO;
    @Autowired
    private RoleDAO roleDAO;
    @Autowired
    private PasswordEncoder encoder;

    public User createUser(int userId, String username) {
        Role role = roleDAO.findByName(UserRole.ROLE_USER).orElseThrow(() -> new RoleNotFoundException("Role not found"));
        Set<Role> roles = Set.of(role);
        User user = new User(userId, username, username + "@mail.mail", encoder.encode("Password#3"), roles);

        int id = userDAO.save(user);
        userDAO.assignRoleToUser(id, role.getId());

        return user;
    }
}

Test:

  @Test
    void whenSessionCreated_UserIdIsCorrect() {
        userFactory.createUser(4, "Kuba");
        createUserSession(loginHelper.login("Kuba"));
        assertThat(sessionUserId()).isEqualTo(4);
    }

    private void createUserSession(LoginRequest request) {
        userSessionCreator.createUserSession(request, "2eds2etfghthheyyyjh536t3fasd235teg");
    }

    private int sessionUserId() {
        Session session = sessionFetcher.fetchSessionForToken("2eds2etfghthheyyyjh536t3fasd235teg");
        return session.getUserId();
    }
0

A dla tego testu:

 @Test
    void whenAuthenticationFails_SessionNotCreated() {
        createSession(loginHelper.loginInvalidPassword("Tom"));
        assertThat(sessionChecker.userHasSession("Tom")).isFalse();
    }

Metoda pomocnicza może wyglądać tak:

  private void createSession(LoginRequest request) {
        try {
            userSessionCreator.createUserSession(request, "2eds2etfghthheyyyjh536t3fasd235teg");
        } catch (BadCredentialsException e) {
        }
    }

Czy zostawić to co było:

 private void createSession(LoginRequest request) {
        assertThrows(BadCredentialsException.class,
                () -> userSessionCreator.createUserSession(request, "2eds2etfghthheyyyjh536t3fasd235teg"));
    }
1
Ornstein napisał(a):

A dla tego testu:

 @Test
    void whenAuthenticationFails_SessionNotCreated() {
        createSession(loginHelper.loginInvalidPassword("Tom"));
        assertThat(sessionChecker.userHasSession("Tom")).isFalse();
    }

Metoda pomocnicza może wyglądać tak:

  private void createSession(LoginRequest request) {
        try {
            userSessionCreator.createUserSession(request, "2eds2etfghthheyyyjh536t3fasd235teg");
        } catch (BadCredentialsException e) {
        }
    }

Czy zostawić to co było:

 private void createSession(LoginRequest request) {
        assertThrows(BadCredentialsException.class,
                () -> userSessionCreator.createUserSession(request, "2eds2etfghthheyyyjh536t3fasd235teg"));
    }

Szczegół implementacyjny. Nie ma znaczenia. Wybierz co Ci się bardziej podoba.

0

Miałbym jeszcze pytanie dotyczące metody pomocniczej, która buduje obiekty do testów. Załóżmy mam taki test:

  @Test
    void whenEmailExist_ThrowException() {
        userFactory.buildUser(1, "User", "[email protected]");
        assertThrows(CredentialValidationException.class,
                () -> strategy.validate(registrationHelper.registerByEmail("[email protected]"));
    }

Korzystam tutaj z metody pomocniczej z trzema parametrami buildUser(int userId, String username, String email). Czy to jest ok? Czy może powinienem napisać specjalną metodę buildUser(String email) z jednym parametrem. Test wyglądałby tak:

 @Test
    void whenEmailExist_ThrowException() {
        userFactory.buildUserByEmail("[email protected]");
        assertThrows(CredentialValidationException.class,
                () -> strategy.validate(registrationHelper.registerByEmail("[email protected]"));
    }

A metoda pomocnicza tak:

    public void buildUserByEmail(String email) {
        Role role = roleDAO.findByName(UserRole.ROLE_USER).orElseThrow(
                () -> new RoleNotFoundException("Role not found"));
        Set<Role> roles = Set.of(role);

        User user = new User(1, "User", email, encoder.encode("Password#3"), roles);

        int id = userDAO.save(user);
        userDAO.assignRoleToUser(id, role.getId());
    }
1

@Ornstein Zdecydowanie buildUserByEmail("[email protected]") w tym wypadku jest odpowiedniejsze.

2

To się nazywa test fixture.
Jest też mnóstwo libów które generują testowe obiekty w locie (w sensie napełniają sensownymi danymi): https://naver.github.io/fixture-monkey/v1-0-0/docs/introduction/overview/ (nie polecam, po prostu pierwszy link z google).
To podejście ma swoich zwolenników i przeciwników (ja wolę jak testy są bardziej deterministyczne aka nie napychane losowymi danymi).

Ja zwykle robiłem na małych projektach tak:

class UsersFixture {
 static User newAdmin() { ... }
 static User newNormalUser() {... }
}

I używałem w testach w Before admin = UsersFixture.newAdmin()

Na dużych projektach jest z tym więcej roboty, wtedy warto zainwestować w test data buildery np. randomowy blog https://www.arhohuttunen.com/test-data-builders/

Generalna zasada jest taka: ma być łatwo i wygodnie tworzyć dane testowe, a jak to osiągniesz twoja brocha. Tutaj podałem 2 lekkie wzorce. Są też cieższe "architektoniczne" podejścia, opisane np. w https://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627

Taki ciężki wzorzec to np. (znów randomowy z google) https://medium.com/testvagrant/screenplay-pattern-3490c7f0c23c

Generalnie temat ciężki więc polecam chwycić jednak za książkę...

0

Na razie zrobiłem coś takiego:

@Component
public class UserBuilder {

    @Autowired
    private RoleDAO roleDAO;
    @Autowired
    private PasswordEncoder encoder;

    private Integer id;
    private String username;
    private String email;

    public UserBuilder buildByUsername(String username) {
        return withUsername(username).withId(1).withEmail("[email protected]");
    }

    public UserBuilder buildByEmail(String email) {
        return withEmail(email).withId(1).withUsername("User");
    }

    public UserBuilder buildById(Integer id) {
        return withId(id).withUsername("User").withEmail("[email protected]");
    }

    public UserBuilder withUsername(String username) {
        this.username = username;
        return this;
    }

    public UserBuilder withEmail(String email) {
        this.email = email;
        return this;
    }

    public UserBuilder withId(Integer id) {
        this.id = id;
        return this;
    }

    public User build() {
        Role role = roleDAO.findByName(UserRole.ROLE_USER).orElseThrow(
                () -> new RoleNotFoundException("Role not found"));
        Set<Role> roles = Set.of(role);

        User user = new User();
        user.setId(id);
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword(encoder.encode("Password#3"));
        user.setRoles(roles);

        return user;
    }
}

Osobna klasa do zapisu:

@Component
public class UserSaver {

    @Autowired
    private UserDAO userDAO;

    @Transactional
    public void save(User user) {
        int userId = userDAO.save(user);
        user.getRoles().forEach(role -> userDAO.assignRoleToUser(userId, role.getId()));
    }
}

Przykładowe testy:

 @Test
    void whenUserExist_DeleteUser() {
        User user = userBuilder.buildById(1).build();
        userSaver.save(user);

        delete.deleteUserById(1);
        assertThat(userChecker.userExist(1)).isFalse();
    }

    @Test
    void whenUserExist_DeleteUserSessions() {
        User user = userBuilder.buildById(1).build();
        userSaver.save(user);

        delete.deleteUserById(1);
        assertThat(sessionChecker.userHasActiveSession(1)).isFalse();
    }

    @Test
    void whenUserExist_DeleteUserRoles() {
        User user = userBuilder.buildById(1).build();
        userSaver.save(user);

        delete.deleteUserById(1);
        assertThat(roleChecker.userHasRoles(1)).isFalse();
    }
0

Przyzwoite języki programowania mają taki koncept jak przestrzeń nazw / moduł / pakiet, który działa niezależnie od klas i interfejsów.

Wtedy sobie mogę napisać:


mod test_constants {
   const FOO: &str = "foo";
   const BAR: u64 = 7;
}

Ogólnie pomysł jest fajny, bo fajniej wtedy działa autocomplete. Wiesz że potrzebujesz stałej i IDE wylistuje Ci wtedy wszystkie dostępne stałe.

0

W C++ name mangling działa w ten sposób, że jest nazwa funkcji i jej parametry z typami + przestrzeń nazw, czyli można dość elastycznie robić nazwy, mogą się powtarzać bo parametry są inne, przeciążanie funkcji, dodatkowo mangling, którego brakuje w C, tam 1:1 funkcje są zapisane, a w C++ już kodują i przestrzeń nazw i parametry pozwalają na dużą swobodę.
Potem takie zmanglowane nazwy są w exporcie funkcji w dll czy exe, czy ogólnie.

Inne dynamiczne języki czy na wirtualnej maszynie są zwykle bardziej rozwinięte, bo tam eksperymentują ze wszystkim.

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