5. Почему?
•У нас нет времени
•У нас нет бюджета
•У нас специфический продукт
•Как?
5
6. Система по выделению мобильных
приложений из Интернет-трафика
• Разрабатываем по этапам
• Код покрываем тестами
• К чему это нас приведет
• Java, Spring Boot, Hibernate, PostgreSQL
• Код на GitHub
• SimonHarmonicMinor/spring-boot-testing-levels
6
8. Task #1
Требуется реализовать сервис валидации правил по типам.
RuleType: key, value
Rule: key, value
Rule[key] = RuleType[key] И
(Rule[value] = RuleType[value]
ИЛИ Rule[value] has RuleType[value])
8
с
с
10. Реализация
10
@Service
class RuleValidatorServiceImpl implements RuleValidatorService {
private final ObjectMapper objectMapper;
@Override
public boolean isRuleValid(Rule rule, RuleType ruleType) {
final var keysMatch = Objects.equals(rule.getKey(), ruleType.getKey());
try {
final Map<?, ?> map = objectMapper.readerFor(Map.class).readValue(rule.getValue());
return keysMatch && map.containsValue(ruleType.getValue());
} catch (Exception e) {
return keysMatch && Objects.equals(rule.getValue(), ruleType.getValue());
}
}
}
с
11. Тест
11
class RuleValidatorServiceImplTest {
private final ObjectMapper objectMapper = new ObjectMapper();
private final RuleValidatorService service = new
RuleValidatorServiceImpl(objectMapper);
@ParameterizedTest
@CsvSource({
"key,value,key,value",
"key,value,key,{"key": "value"}"
})
void shouldReturnTrueIfKeysAndValuesMatch(
String typeKey, String typeValue,
String ruleKey, String ruleValue
) {
final var ruleType =
RuleTypeTestBuilder.builder().setKey(typeKey).setValue(typeValue).build();
final var rule =
RuleTestBuilder.builder().setKey(ruleKey).setValue(ruleValue).build();
final var result = service.isRuleValid(rule, ruleType);
assertTrue(result, "Rule should be valid");
}
с
с
с
с
13. Task #2
Все правила, которые не прошли валидацию, должны
фиксироваться в системе аудита.
13
Сервиса аудита нет, но есть интерфейс!
14. Декорируем
14
@Service
@Primary
public class AuditingRuleValidatorService implements RuleValidatorService {
@ActualRuleValidatorServiceQualifier
private final RuleValidatorService origin;
private final AuditService auditService;
@Override
public boolean isRuleValid(Rule rule, RuleType ruleType) {
final var res = origin.isRuleValid(rule, ruleType);
if (!res) {
auditService.auditWrongRule(rule);
}
return res;
}
}
с
18. Task #3
Требуется написать сервис по созданию новых правил. Если
параметры не валидны, операция прерывается.
18
19. 19
@Transactional
public Rule createRule(RuleInfo ruleInfo) {
final var ruleType =
ruleTypeRepository.findById(ruleInfo.getRuleTypeId()).orElseThrow();
final var app = appRepository.findById(ruleInfo.getAppId()).orElseThrow();
final var rule =
new Rule()
.setRuleType(ruleType)
.setApp(app)
.setKey(ruleInfo.getKey())
.setValue(ruleInfo.getValue())
.setName(ruleInfo.getName());
if (!ruleValidatorService.isRuleValid(rule, ruleType)) {
throw new IllegalArgumentException("Not valid rule arguments");
}
return ruleRepository.saveAndFlush(rule);
}
}
20. Протестируем успех
20
@Test
void shouldCreateRuleSuccessfully() {
final var ruleInfo = new RuleInfo("name", "key", "value", 1L, 2L);
final var ruleType = RuleTypeTestBuilder.builder().setId(1L).build();
when(ruleTypeRepository.findById(1L)).thenReturn(Optional.of(ruleType));
final var app = AppTestBuilder.builder().setId(2L).build();
when(appRepository.findById(2L)).thenReturn(Optional.of(app));
when(ruleValidatorService.isRuleValid(any(), any())).thenReturn(true);
when(ruleRepository.saveAndFlush(any())).thenAnswer(invocation -> {
final Rule currRule = invocation.getArgument(0);
return currRule.setId(1L);
});
final var result = ruleService.createRule(ruleInfo);
assertNotNull(result.getId());
assertEquals("name", ruleInfo.getName());
assertEquals("key", ruleInfo.getKey());
assertEquals("value", ruleInfo.getValue());
}
22. Task #3.1
Требуется написать сервис по созданию новых правил. Если
параметры не валидны, операция прерывается.
Нужно иметь возможность создавать несколько правил в одной
транзакции.
22
24. 24
@Test
void shouldCreateMultipleRulesSuccessfully() {
final var rulesInfo = Stream.of("ip", "host", "user_agent")
.map(name -> new RuleInfo(name, "key", "value", 1L, 2L))
.collect(Collectors.toList());
final var ruleType = RuleTypeTestBuilder.builder().setId(1L).build();
when(ruleTypeRepository.findById(1L)).thenReturn(Optional.of(ruleTy
pe));
final var app = AppTestBuilder.builder().setId(1L).build();
when(appRepository.findById(2L)).thenReturn(Optional.of(app));
when(ruleValidatorService.isRuleValid(any(),
any())).thenReturn(true);
final var idGenerator = new AtomicLong();
when(ruleRepository.saveAndFlush(any())).thenAnswer(invocation ->
{
final Rule currRule = invocation.getArgument(0);
return currRule.setId(idGenerator.incrementAndGet());
});
final var list = ruleService.createRules(rulesInfo);
for (Rule rule : list) {
assertNotNull(rule.getId());
assertTrue(List.of("ip", "host",
26. 26
@Transactional
public Rule createRule(RuleInfo ruleInfo) {
final var ruleType =
ruleTypeRepository.findById(ruleInfo.getRuleTypeId()).orElseThrow();
final var app = appRepository.findById(ruleInfo.getAppId()).orElseThrow();
final var rule =
new Rule()
.setRuleType(ruleType)
.setApp(app)
.setKey(ruleInfo.getKey())
.setValue(ruleInfo.getValue())
.setName(ruleInfo.getName());
if (!ruleValidatorService.isRuleValid(rule, ruleType)) {
throw new IllegalArgumentException("Not valid rule arguments");
}
return ruleRepository.save(rule);
}
}
43. 43
@Test
void shouldCreateMultipleRulesSuccessfully() {
when(ruleValidatorService.isRuleValid(any(), any())).thenReturn(true);
final var ruleType =
ruleTypeRepository.saveAndFlush(RuleTypeTestBuilder.builder().build());
final var app = appRepository.saveAndFlush(AppTestBuilder.builder().build());
final var rulesInfo = Stream.of("ip", "host", "user_agent")
.map(name -> new RuleInfo(name, "key", "value", ruleType.getId(), app.getId()))
.collect(Collectors.toList());
final var list = ruleService.createRules(rulesInfo);
for (Rule rule : list) {
assertNotNull(rule.getId());
assertTrue(List.of("ip", "host", "user_agent").contains(rule.getName()));
assertEquals("key", rule.getKey());
assertEquals("value", rule.getValue());
}
}
84. 84
Creating container for image: postgres:9.6.8
Starting container with ID:
c1691eb7d62b1be2df336fd5988d59a35155cf0071d3844d0cb1e0ca02ad7858
Container postgres:9.6.8 started in PT6.7356176S
101. Выводы
• Тестирование помогает разрабатывать поэтапно
• Проверяем, что не сломали старые фичи
• Рефакторинг без страха
• Выбирайте подходящий уровень тестирования
• Тесты на Spring Boot – это совсем не страшно
101