Testy jednostkowe

0

Witajcie,
Uczę się uprawiać TDD. Moja klasa i testy:

public class DatabaseHelperImpl implements DatabaseHelper {

    private DataSource dataSource;

    public DatabaseHelperImpl(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public <T> List<T> select(String query, RowMapper<T> rowMapper, Object... params) {
        try (Connection connection = this.dataSource.getConnection()) {
            try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
                try (ResultSet resultSet = preparedStatement.executeQuery()) {
                    List<T> result = new ArrayList<>();
                    while (resultSet.next()) {
                        result.add(rowMapper.map(resultSet));
                    }
                    return result;
                }
            }
        } catch (SQLException ex) {
            throw new RuntimeException(ex);
        }
    }

}

@RunWith(MockitoJUnitRunner.class)
public class DatabaseHelperTest {

    @Mock
    private DataSource dataSource;
    @Mock
    private Connection connection;
    @Mock
    private PreparedStatement preparedStatement;
    private DatabaseHelper databaseHelper;

    @Before
    public void init() {
        this.databaseHelper = new DatabaseHelperImpl(dataSource);
    }

    @Test(expected = RuntimeException.class)
    public void shouldThrowExceptionOnConnectionCreationFailed() throws SQLException {
        when(this.dataSource.getConnection()).thenThrow(new SQLException("dummy exception"));

        this.databaseHelper.select("", resultSet -> new Object());
    }

    @Test(expected = RuntimeException.class)
    public void shouldThrowExceptionOnPreparedStatementCreationFailed() throws SQLException {
        when(this.dataSource.getConnection()).thenReturn(this.connection);
        when(this.connection.prepareStatement(any(String.class))).thenThrow(new SQLException("Dummy exception"));

        this.databaseHelper.select("", resultSet -> new Object());
    }

    @Test
    public void shouldPrepareStatementWithGivenQuery() throws SQLException {
        String query = "Sample query";
        when(this.dataSource.getConnection()).thenReturn(this.connection);
        when(this.connection.prepareStatement(any(String.class))).thenReturn(this.preparedStatement);
        when(this.preparedStatement.executeQuery()).thenReturn(Mockito.mock(ResultSet.class));

        this.databaseHelper.select(query, resultSet -> new Object());

        verify(this.connection, atLeastOnce()).prepareStatement(eq(query));
    }

    @Test
    public void shouldMapRowWithResultSet() throws SQLException {
        ResultSetSavingRowMapper resultSetSavingRowMapper = new ResultSetSavingRowMapper();
        ResultSet result = Mockito.mock(ResultSet.class);

        when(result.next()).thenAnswer(new Answer<Boolean>() {
            private boolean first = true;

            @Override
            public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable {
                boolean val = this.first;
                this.first = !this.first;
                return val;
            }
        });
        when(this.dataSource.getConnection()).thenReturn(this.connection);
        when(this.connection.prepareStatement(any(String.class))).thenReturn(this.preparedStatement);
        when(this.preparedStatement.executeQuery()).thenReturn(result);

        this.databaseHelper.select("", resultSetSavingRowMapper);

        assertEquals(result, resultSetSavingRowMapper.getResultSet());
    }

    public class ResultSetSavingRowMapper implements DatabaseHelper.RowMapper<Object> {

        @Setter
        @Getter
        private Object returnValue = new Object();
        @Getter
        private ResultSet resultSet;

        @Override
        public Object map(ResultSet resultSet) {
            this.resultSet = resultSet;
            return this.returnValue;
        }
    }

}

I tutaj pojawia się moje pytanie.
Testy powinny być aż tak dokładne? Jeżeli nie to które są zbędne?
Testy powinny zajmować więcej miejsca od kodu?
Co w nich poprawić?

0

Tak, testy jednostkowe działają „dokładnie”, czyli na małych kawałkach kodu.
U ciebie, są zagmatwane, ale to wynik zagmatwanego kodu. BTW, te trzy try można zastąpić konstrukcją try(Connection c =...; PreparedStatement p=c.preparedStatement(); ResultSet r = ...) Nie będzie tak razić :) a najlepiej to jednak rozmontować na trzy metody.

Tak, testy zazwyczaj zajmują więcej niż kod.

Chociażby zamiast thenAnswer użyj thenReturn, które przyjmuje wiele parametrów i będzie zwracać ostatni, jeżeli ilość wywołań jest większa niż ilość parametrów. Nie podobają mi się testy związane z weryfikowaniem czy dana metoda została wywołana. Znacznie lepiej jest stworzyć pewien przepływ i weryfikować jego wynik, co powinno być jednoznaczne z wywołaniem poszczególnych metod.

0

Podzieliłbym to przynajmniej na 2 metody:

  1. Taką która zwraca prepared statement (albo lepiej optionala na wypadek failu)
  2. Taką która odpala statement i pobiera wyniki.
    Od razu testy sie uproszczą.

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