Spring: Test

Spring: Test


Правила именование

Четких правил именования нет, но рекомендуется классы заканчивать постфиксом *Test, а методы, проводящие тестирование именовать согласно тому что они проверяют.

1
2
3
4
5
6
7
8
9
10
public class MyServiceTest {
@BeforeEach
public void before() { ... }
@AfterEach
public void after() { ... }
@Test
public void createUser() {...}
@Test
public void findAllUsers() { ... }
}

JUnit4 vs JUnit5

  • @Before -> @BeforeEach
  • @After -> @AfterEach
  • @BeforeClass -> @BeforeAll
  • @AfterClass -> @AfterAll
  • @Test(expected=Exception.class) -> @Test { assertThrows(..., Exception.class) }
  • @Ignore -> @Disabled

Симуляция

Для тестирования принято разделять объекты-симуляции на 2 вида: mock и stub.

Оба подхода не реализует работу компонента, а создает копию классов, который создает иллюзию работы компонента, реализуя вход и выход в компонент тестовых данных. Например, в случае с работой с БД, можно создать коллекцию, которая будет хранить данные в памяти.
Негативной стороной stub считается долгая реализация симуляции работы компонента. Малейшее изменение оригинального класса так же может повлечь массивных изменений stub компонентов.

Mock

Аналог stub, но предполагает подключение библиотек, которые уже имеют некоторое количество реализаций соответствующих сервисов (например, иммитацию работы репозиториев БД).

Сущесвует ряд библиотек, реализующих mock подход к тестированию. Наиболее популярными являются EasyMock и Mockito.

Пример для EasyMock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyServiceEasyMockTest {
private MyObjectRepos mockRepo;
private MyService service;

@BeforeEach
public void setup() {
mockRepo = EasyMock.createMock(MyObjectRepo.class);
...
Service.setRepo(mockRepo);
}

@Test
public void createUser() {
MyObject mockObj = new MyObject();
EasyMock.expect(mockRepo.findAll(...).andReturn(Lists.of(mockObj)));
EasyMock.replay(mockRepo);

List<MyObject>objects = service.find(...);
assertAll(() -> assertNotNull(objects), () -> assertEquals(mockObj, objects.get(0)), ...);
}
}

Пример для Mockito:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ExtendWith(MockitoExtension.class)
public class UsersTest {
@Mock
private UserRepo repo;
@InjectMocks
private UserService userService;

@Test
public void findBy() {
User mockUser = new User();
Mockito.when(repo.findById(ArgumentMatchers.any(Long.class)).thenReturn(mockUser);
assertEquals(mockUser, userService.findById(1L));
}
}

Метод when создает псевдо-подстановку согласно условию. Тем самым сервис userService будет возвращать тот объект, который представлен в thenReturn.

ContextConfiguration

Для теста можно указать специфический загрузчик конфигураций. Это удобно если для 1-го конкретного теста надо изменить поведение компонента:

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class MyTest {
@Configuration
public static class MyCustomConfig {
public MyService alternativeService() {...}
}

@Autowired
MyService service;
...
}

Доступ к БД

Наиболее примитивным является доступ через JdbcTemplate. Придется много кода писать самостоятельно, но плюсом является то что запросы можно оптимизировать, а так же то что не используется никаких прокси, все запросы идут напрямую в БД.

1
2
3
4
5
6
7
@Configuration
public class JdbcConfig {
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}

Для разбора результатов выполнения запросов (в частности, SELECT) к БД, придется создать RowMapper, который будет выполнять построчную обработку результатов выборки в БД (например, конвертацию результатов запросов в POJO).

1
2
3
4
5
6
7
8
9
@Repository
public class MyRepo {
private RowMapper<MyObject> mapper = new MyObjectMapper();

public Set<MyObject> listAll() {
var sql = "select ... from ...";
return jdbcTemplate.query(sql, mapper);
}
}

Тесты и SQL

Аннотации из группы @Sql помогают оптимизировать тестирование под конкретные случаи:

1
2
3
4
@Test
@Sql(executionPhase=..., scripts={"classpath:db/my/scripts/01.sql", "classpath:db/script2.sql", "..."})
@Sql(executionPhase=AFTER_TEST_METHOD, statements={"delete from ...", "..."})
public void findAndCleanUp() {...}

Аннотации из данной группы можно применять и к классу, тогда они будут применены ко всем @Test методам.

Аннотация @SqlConfig добавляет правила исполнения скриптов (данная аннотация не часто используется, ввиду низкой потребности) - устанавливает изоляцию транзакций, кодировку чтения скриптов, разделители при чтении скриптов и т.д.

Аннотация @SqlGroup объединяет несколько @Sql аннотаций в группу и позволяет выполнять всю группу целиком, в единой транзакции.

В сложном тестировании необходимо подключать транзакции для работы с тестированием БД. Это осуществляется, как и обычно, посредством @Transactional либо ручным управлением транзакциями.

Аннотация @BeforeTransaction и @AfterTransaction помогают управлять началом и завершением транзакции, соответственно.

Для теста, по ум., транзакция всегда откатывается (выполняется rollback) - тем самым перед каждым тестом данные в БД лежат в эталонном виде. Если это поведение необходимо изменить (т.е. изменения транзакции подтверждались) - необходимо добавить аннотацию @Rollback(false). Аналогично работает и @Commit(true), который подтверждает транзакцию по окончании тестового метода (по ум. аннотация = @Commit(false)).

Профили тестирования

Для бина или конфигурации (или любого другого компонента) можно определить Spring профиль при котором он будет создан (и доступен).

1
2
3
4
5
6
7
@Configuration
@Profile
public class MyConfig {
@Bean
@Profile("test")
Public MyCustomBean myBean() { ... }
}

Без указания @Profile, бины создаются всегда и без ограничений.

Для тестов можно указать активный профиль на каждый отдельный класс (т.е. для каждого класса тестирования профили могут различаться):

1
2
3
@SpringJUnitConfig(...)
@ActiveProfiles(value={"test", "h2", "..."}, inheritProfiles=false)
public class MyTest extends BasicTest {...}

Параметр inheritProfiles помечает можно ли наследовать профили от родительских тест-классов (по ум. все профили наследуются, т.е. inheritProfiles=true).

SpringBoot и тесты

Добавление @SpringBootTest дает возможность запускать тестирование SpringBoot приложений. При этом, JUnit уже будет подключен как фреймворк для тестирования.

1
2
@SpringBootTest
public class MyBootTest {...}

Тестирование с JUnit5

Для 5 версии JUnit необходимо у тестов изменить @RunWith на @ExtendWith, присвоив соответствующий параметр в виде нового класса:

1
2
@ExtendWith(SpringExtention.class)
public class MyServiceJUnit5Test {...}

Можно сократить аннотацию совместив ее вместе с перечнем конфигураций до:

1
@SpringJUnitConfig(classes={MyJdbcConfig.class, MyRestConfig.class, ...})

Тестирование с JUnit4

Для тестирования Spring создает Singleton бин из класса ApplicationContext, который является активным на всем протяжении всего жизненного цикла тестов.

Для запуска тестов в контексте Spring необходимо:

  1. Добавить аннотацию @RunWith(SpringJUnit4ClassRunner.class) или @RunWith(SpringRunner.class);
  2. Добавить @ContextConfiguration с указанием дополнительных конфигураций для контекста (опционально): @ContextConfiguration(locations={"classpath:myConf.yml", "..."}). Если параметр не указан, то будет использована локация по ум.: {testClassName}-context.xml;
    3.Используя @Autowired или @Inject, добавить требуемые бины.
 Comments
Comment plugin failed to load
Loading comment plugin