SlideShare a Scribd company logo
1 of 102
Тестирование Spring Boot
приложений: Data и Service
Layer
1
Семен Киреков
О себе
•Киреков Семен
•Java Dev и Team Lead в «МТС Диджитал»
Центр Big Data
2
Тесты важны
3
4
Почему?
•У нас нет времени
•У нас нет бюджета
•У нас специфический продукт
•Как?
5
Система по выделению мобильных
приложений из Интернет-трафика
• Разрабатываем по этапам
• Код покрываем тестами
• К чему это нас приведет
• Java, Spring Boot, Hibernate, PostgreSQL
• Код на GitHub
• SimonHarmonicMinor/spring-boot-testing-levels
6
Доменная модель
7
Task #1
Требуется реализовать сервис валидации правил по типам.
RuleType: key, value
Rule: key, value
Rule[key] = RuleType[key] И
(Rule[value] = RuleType[value]
ИЛИ Rule[value] has RuleType[value])
8
с
с
Объявим интерфейс
9
public interface RuleValidatorService {
boolean isRuleValid(Rule rule, RuleType
ruleType);
}
Реализация
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
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");
}
с
с
с
с
12
Task #2
Все правила, которые не прошли валидацию, должны
фиксироваться в системе аудита.
13
Сервиса аудита нет, но есть интерфейс!
Декорируем
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;
}
}
с
15
Тестируем
16
@ExtendWith(MockitoExtension.class)
class AuditingRuleValidatorServiceTest {
@Mock
private RuleValidatorService origin;
@Mock
private AuditService auditService;
private RuleValidatorService ruleValidatorService;
@BeforeEach
void init() {
ruleValidatorService = new AuditingRuleValidatorService(origin, auditService);
}
@ParameterizedTest
@CsvSource({"true,0", "false,1"})
void shouldPerformAuditTheRightWay(boolean validness, int auditTimesCalled) {
final var rule = RuleTestBuilder.builder().build();
final var ruleType = RuleTypeTestBuilder.builder().build();
when(origin.isRuleValid(rule, ruleType)).thenReturn(validness);
final var result = ruleValidatorService.isRuleValid(rule, ruleType);
assertEquals(validness, result, "Unexpected rule validness");
verify(auditService, times(auditTimesCalled)).auditWrongRule(rule);
}
}
17
Task #3
Требуется написать сервис по созданию новых правил. Если
параметры не валидны, операция прерывается.
18
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
@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());
}
21
Task #3.1
Требуется написать сервис по созданию новых правил. Если
параметры не валидны, операция прерывается.
Нужно иметь возможность создавать несколько правил в одной
транзакции.
22
23
@Override
@Transactional
public List<Rule> createRules(List<RuleInfo> ruleInfos)
{
return ruleInfos.stream()
.map(this::createRule)
.collect(Collectors.toList());
}
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",
Какие проблемы?
• Тесты нестабильны
25
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);
}
}
27
Какие проблемы?
• Тесты нестабильны
• Репозиторий – это больше, чем интерфейc
28
Ну так это же unit тесты
29
30
@Query("select distinct r.name from Rule
r")
Set<String> findUniqueNames();
Ну и ладно, все равно
замокаем
31
32
Ой, да тут логика
элементарная
33
А если фильтрация с
Criteria API /
Spring Data Specification /
QueryDSL ?
34
35
RuleService
PredicateService
Tests with
mocks
?
Ну ладно
36
37
Вывод
Мокать DAL не имеет смысла
38
По моему мнению
Что делать-то?
Нужна база
39
40
testRuntimeOnly 'com.h2database:h2'
spring.jpa.hibernate.ddl-auto=create-
drop
41
@SpringBootTest
@AutoConfigureTestDatabase
class RuleServiceImplH2Test {
@Autowired
private RuleTypeRepository ruleTypeRepository;
@Autowired
private AppRepository appRepository;
@Autowired
private RuleRepository ruleRepository;
@MockBean
private RuleValidatorService ruleValidatorService;
@Autowired
private RuleService ruleService;
42
@BeforeEach
void beforeEach() {
ruleRepository.deleteAll();
appRepository.deleteAll();
ruleTypeRepository.deleteAll();
}
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());
}
}
44
Протестируем Rollback
45
@Test
void shouldRollbackIfAnyRuleIsNotValid() {
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());
when(ruleValidatorService.isRuleValid(any(), any())).thenAnswer(invocation -> {
Rule rule = invocation.getArgument(0);
return !Objects.equals(rule.getName(), "user_agent");
});
assertThrows(IllegalArgumentException.class, () -> ruleService.createRules(rulesInfo));
assertEquals(0, ruleRepository.count());
}
46
@SpringBootTest дорогой
47
48
@DataJpaTest тоже поднимает Spring
Context
Но не весь
49
@DataJpaTest
class RuleServiceImplH2DataJpaTest {
@Autowired
private RuleTypeRepository ruleTypeRepository;
@Autowired
private AppRepository appRepository;
@Autowired
private RuleRepository ruleRepository;
@MockBean
private RuleValidatorService ruleValidatorService;
@Autowired
private RuleService ruleService;
50
51
@DataJpaTest
class RuleServiceImplH2DataJpaTest {
@Autowired
private RuleTypeRepository ruleTypeRepository;
@Autowired
private AppRepository appRepository;
@Autowired
private RuleRepository ruleRepository;
@MockBean
private RuleValidatorService ruleValidatorService;
@Autowired
private RuleService ruleService;
52
@TestConfiguration
static class Config {
@Bean
public RuleService ruleService(RuleValidatorService
ruleValidatorService,
AppRepository appRepository,
RuleTypeRepository
ruleTypeRepository,
RuleRepository ruleRepository) {
return new RuleServiceImpl(
ruleValidatorService,
appRepository,
ruleTypeRepository,
ruleRepository
);
}
}
53
@Test
void shouldCreateMultipleRulesSuccessfully() {
… }
@Test
void shouldRollbackIfAnyRuleIsNotValid() {
… }
54
assertEquals(0,
ruleRepository.count());
55
Propagation
56
57
58
Что делать?
59
@Override
@Transactional(propagation =
Propagation.REQUIRES_NEW)
public List<Rule> createRules(List<RuleInfo> ruleInfos) {
return ruleInfos.stream()
.map(this::createRule)
.collect(Collectors.toList());
}
60
assertThrows(IllegalArgumentException.class, () ->
ruleService.createRules(rulesInfo));
61
62
63
Транзакционные тесты – это опасно
64
@Override
@Transactional
public List<Rule> createRules(List<RuleInfo> ruleInfos) {
return ruleInfos.stream()
.map(this::createRule)
.collect(Collectors.toList());
}
@DataJpaTest
@Transactional(propagation =
Propagation.NOT_SUPPORTED)
class RuleServiceImplH2DataJpaTest {
65
@BeforeEach
void beforeEach() {
ruleRepository.deleteAll();
appRepository.deleteAll();
ruleTypeRepository.deleteAll();
}
66
67
А почему Transactional по умолчанию?
68
Репозитории!
69
public interface RuleRepository extends JpaRepository<Rule,
Long> {
@Query("select distinct r.name from Rule r")
Set<String> findUniqueNames();
}
70
@DataJpaTest
class RuleRepositoryTestFindUniqueNames {
@Autowired
private RuleRepository ruleRepository;
@Autowired
private RuleTypeRepository ruleTypeRepository;
@Autowired
private AppRepository appRepository;
71
@Test
void shouldReturnUniqueNames() {
createRule("name1");
createRule("name2");
createRule("name2");
final var names = ruleRepository.findUniqueNames();
assertEquals(Set.of("name1", "name2"), names);
}
private void createRule(String name) {
ruleRepository.saveAndFlush(
RuleTestBuilder.builder().setName(name)
.setRuleType(defaultRuleType).setApp(defaultApp)
.build()
);
}
72
73
Проблемы H2
• Не все фичи поддерживаются
• Нет возможности использовать Vendor-specific SQL
• H2 не используют в проде (как правило)
74
Build
?
75
Developer
Test DB
Local Environment CI/CD
76
docker-compose.yml
Port 5432
is reserved
77
$port:543
2
script.py
Если билд упадет, что делать с
контейнером?
78
79
To the rescue!
•Postgres
•MariaDB
•MongoDB
•Oracle-XE
•Nginx
•Kafka
•RabbitMQ
•… 80
+ Generic
Containers
81
testImplementation 'org.testcontainers:junit-
jupiter'
testImplementation
'org.testcontainers:postgresql'
82
spring:
datasource:
url:
jdbc:tc:postgresql:9.6.8:///test_database
username: user
password: password
jpa:
hibernate:
ddl-auto: create-drop
application-test-containers.yml
83
@DataJpaTest
@Transactional(propagation =
Propagation.NOT_SUPPORTED)
@Testcontainers
@AutoConfigureTestDatabase(replace = Replace.NONE)
@ActiveProfiles("test-containers")
class RuleServiceImplTestContainersDataJpaTest {
void shouldCreateMultipleRulesSuccessfully() { … }
void shouldRollbackIfAnyRuleIsNotValid() { … }
}
84
Creating container for image: postgres:9.6.8
Starting container with ID:
c1691eb7d62b1be2df336fd5988d59a35155cf0071d3844d0cb1e0ca02ad7858
Container postgres:9.6.8 started in PT6.7356176S
Плюсы DDL-Auto
• Создание только нужных таблиц
85
86
1000 tables
20 tables
Плюсы DDL-Auto
• Создание только нужных таблиц
• Отделение миграций от проекта
87
88
Application
Repo
Migrations Repo
Минусы DDL-auto
•Неверные конечные типы данных
•В миграция могут быть дефолтные данные
•Нельзя использовать views и stored procedures
89
Flyway
90
implementation "org.flywaydb:flyway-core"
91
application-test-containers-
flyway.yml
spring:
datasource:
url: jdbc:tc:postgresql:9.6.8:///test_database
username: user
password: password
flyway:
locations: classpath:db/migration
enabled: true
jpa:
hibernate:
ddl-auto: none
92
V1__create_app_table.sq
l
create table app
(
id bigserial primary key,
name varchar(200) not null,
device varchar(50) not null,
unique (name, device)
);
V2__create_rule_type_table.s
ql
V3__create_rule_table.sql
V4__create_aggregate_table.
sql
93
spring:
jpa:
hibernate:
ddl-auto: create-
drop
flyway:
enabled: false
application.y
ml
94
@DataJpaTest
@Transactional(propagation =
Propagation.NOT_SUPPORTED)
@Testcontainers
@AutoConfigureTestDatabase(replace = Replace.NONE)
@ActiveProfiles("test-containers-flyway")
class RuleServiceImplTestContainersFlywayDataJpaTest {
…
}
./gradlew test
95
org.flywaydb.core.api.FlywayException:
Found non-empty schema(s) "public" but no schema history
table.
96
test {
useJUnitPlatform {
excludeTags 'integration-test-
flyway'
}
}
task integrationTestFlyway(type: Test) {
useJUnitPlatform {
includeTags 'integration-test-
flyway'
}
}
97
@DataJpaTest
@Transactional(propagation =
Propagation.NOT_SUPPORTED)
@Testcontainers
@AutoConfigureTestDatabase(replace = Replace.NONE)
@ActiveProfiles("test-containers-flyway")
@Tag("integration-test-flyway")
class RuleServiceImplTestContainersFlywayDataJpaTest {
…
}
./gradlew test integrationTestFlyway
98
Database: jdbc:postgresql://localhost:55258/test (PostgreSQL 9.6)
Successfully validated 4 migrations (execution time 00:00.053s) Creating
Schema History table "public"."flyway_schema_history"
Testcontainers
+ Flyway
Testcontainers
H2 Tests
Mocks
99
Unit
Tests
Непонятки
• Это все слои?
• Нужны ли они всегда?
100
Нет
Нет
Выводы
• Тестирование помогает разрабатывать поэтапно
• Проверяем, что не сломали старые фичи
• Рефакторинг без страха
• Выбирайте подходящий уровень тестирования
• Тесты на Spring Boot – это совсем не страшно
101
Спасибо за внимание!
102
Слайды: https://cutt.ly/3QJnSAO
Telegram: @kirekov
Репозиторий:
https://github.com/SimonHarmonicMinor/spring-boot-testing-levels

More Related Content

What's hot

Модульное тестирование iOS-приложений.
Модульное тестирование iOS-приложений.Модульное тестирование iOS-приложений.
Модульное тестирование iOS-приложений.MageCloud
 
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до ЯБыстрое введение в TDD от А до Я
Быстрое введение в TDD от А до ЯAndrey Bibichev
 
В помощь разработчику: мини-анализатор кода
В помощь разработчику: мини-анализатор кодаВ помощь разработчику: мини-анализатор кода
В помощь разработчику: мини-анализатор кодаAndrey Karpov
 
Влад Ковташ — Yap Database
Влад Ковташ — Yap DatabaseВлад Ковташ — Yap Database
Влад Ковташ — Yap DatabaseCocoaHeads
 
Референсная архитектура приложения на ASP.NET MVC
Референсная архитектура приложения на ASP.NET MVCРеференсная архитектура приложения на ASP.NET MVC
Референсная архитектура приложения на ASP.NET MVCAndrew Mayorov
 
Автотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполнения
Автотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполненияАвтотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполнения
Автотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполненияSQALab
 
Выжить с помощью ООП. Максим Гопей
Выжить с помощью ООП. Максим ГопейВыжить с помощью ООП. Максим Гопей
Выжить с помощью ООП. Максим ГопейEatDog
 
Разработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительностьРазработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительностьВадим Воробьев
 
Очень вкусный фрукт Guava
Очень вкусный фрукт GuavaОчень вкусный фрукт Guava
Очень вкусный фрукт GuavaEgor Chernyshev
 
Lint в помощь / Григорий Джанелидзе (Одноклассники)
Lint в помощь / Григорий Джанелидзе (Одноклассники)Lint в помощь / Григорий Джанелидзе (Одноклассники)
Lint в помощь / Григорий Джанелидзе (Одноклассники)Ontico
 
TestRail. Некоторые возможности интеграции.
TestRail. Некоторые возможности интеграции.TestRail. Некоторые возможности интеграции.
TestRail. Некоторые возможности интеграции.PyNSK
 

What's hot (14)

Selenium vs AJAX
Selenium vs AJAXSelenium vs AJAX
Selenium vs AJAX
 
Модульное тестирование iOS-приложений.
Модульное тестирование iOS-приложений.Модульное тестирование iOS-приложений.
Модульное тестирование iOS-приложений.
 
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до ЯБыстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
 
Testing & TDD
Testing & TDDTesting & TDD
Testing & TDD
 
Survive with OOP
Survive with OOPSurvive with OOP
Survive with OOP
 
В помощь разработчику: мини-анализатор кода
В помощь разработчику: мини-анализатор кодаВ помощь разработчику: мини-анализатор кода
В помощь разработчику: мини-анализатор кода
 
Влад Ковташ — Yap Database
Влад Ковташ — Yap DatabaseВлад Ковташ — Yap Database
Влад Ковташ — Yap Database
 
Референсная архитектура приложения на ASP.NET MVC
Референсная архитектура приложения на ASP.NET MVCРеференсная архитектура приложения на ASP.NET MVC
Референсная архитектура приложения на ASP.NET MVC
 
Автотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполнения
Автотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполненияАвтотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполнения
Автотестирование АБС. Конвейер разработки, конвейер данных, конвейер выполнения
 
Выжить с помощью ООП. Максим Гопей
Выжить с помощью ООП. Максим ГопейВыжить с помощью ООП. Максим Гопей
Выжить с помощью ООП. Максим Гопей
 
Разработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительностьРазработка крупного Standalone проекта на юнити: улучшаем производительность
Разработка крупного Standalone проекта на юнити: улучшаем производительность
 
Очень вкусный фрукт Guava
Очень вкусный фрукт GuavaОчень вкусный фрукт Guava
Очень вкусный фрукт Guava
 
Lint в помощь / Григорий Джанелидзе (Одноклассники)
Lint в помощь / Григорий Джанелидзе (Одноклассники)Lint в помощь / Григорий Джанелидзе (Одноклассники)
Lint в помощь / Григорий Джанелидзе (Одноклассники)
 
TestRail. Некоторые возможности интеграции.
TestRail. Некоторые возможности интеграции.TestRail. Некоторые возможности интеграции.
TestRail. Некоторые возможности интеграции.
 

Similar to Тестирование spring boot приложений

Типичные ошибки начинающих писать тесты на WebDriver
Типичные ошибки начинающих писать тесты на WebDriverТипичные ошибки начинающих писать тесты на WebDriver
Типичные ошибки начинающих писать тесты на WebDriverIgor Khrol
 
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...Andrey Rebrov
 
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полнойОмские ИТ-субботники
 
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Ivan Tsyganov
 
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИСРеактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС2ГИС Технологии
 
Реактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложенияРеактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложенияMatvey Malkov
 
Как выглядит современный фронтенд
Как выглядит современный фронтендКак выглядит современный фронтенд
Как выглядит современный фронтендTimophy Chaptykov
 
Automation Functional Testing in Agile Projects
Automation Functional Testing in Agile ProjectsAutomation Functional Testing in Agile Projects
Automation Functional Testing in Agile ProjectsAndrey Rebrov
 
Unit testing iOS Applications
Unit testing iOS ApplicationsUnit testing iOS Applications
Unit testing iOS ApplicationsAndrey Volobuev
 
ук 03.002.01 2011
ук 03.002.01 2011ук 03.002.01 2011
ук 03.002.01 2011etyumentcev
 
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVCАрхитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVCDevDay
 
Статический анализ кода в DDD
Статический анализ кода в DDDСтатический анализ кода в DDD
Статический анализ кода в DDDAleksei Alekseev
 
Rambler.iOS #3: Test-Driven Development в iOS
Rambler.iOS #3: Test-Driven Development в iOSRambler.iOS #3: Test-Driven Development в iOS
Rambler.iOS #3: Test-Driven Development в iOSRAMBLER&Co
 
ASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NET
ASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NETASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NET
ASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NETDev2Dev
 
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким ХалиловWebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким ХалиловGeeksLab Odessa
 
Retro vs Volley
Retro vs VolleyRetro vs Volley
Retro vs VolleyArtjoker
 
Что могут статические анализаторы, чего не могут программисты и тестировщики
Что могут статические анализаторы, чего не могут программисты и тестировщикиЧто могут статические анализаторы, чего не могут программисты и тестировщики
Что могут статические анализаторы, чего не могут программисты и тестировщикиAndrey Karpov
 

Similar to Тестирование spring boot приложений (20)

Unit тестирование
Unit тестированиеUnit тестирование
Unit тестирование
 
Типичные ошибки начинающих писать тесты на WebDriver
Типичные ошибки начинающих писать тесты на WebDriverТипичные ошибки начинающих писать тесты на WebDriver
Типичные ошибки начинающих писать тесты на WebDriver
 
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
Бодрящий микс из Selenium и TestNG- регрессионное тестирование руками разрабо...
 
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
 
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
 
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИСРеактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
 
Реактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложенияРеактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложения
 
Как выглядит современный фронтенд
Как выглядит современный фронтендКак выглядит современный фронтенд
Как выглядит современный фронтенд
 
Automation Functional Testing in Agile Projects
Automation Functional Testing in Agile ProjectsAutomation Functional Testing in Agile Projects
Automation Functional Testing in Agile Projects
 
Unit testing iOS Applications
Unit testing iOS ApplicationsUnit testing iOS Applications
Unit testing iOS Applications
 
Seamy side of autotests
Seamy side of autotestsSeamy side of autotests
Seamy side of autotests
 
ук 03.002.01 2011
ук 03.002.01 2011ук 03.002.01 2011
ук 03.002.01 2011
 
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVCАрхитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
 
Статический анализ кода в DDD
Статический анализ кода в DDDСтатический анализ кода в DDD
Статический анализ кода в DDD
 
Rambler.iOS #3: Test-Driven Development в iOS
Rambler.iOS #3: Test-Driven Development в iOSRambler.iOS #3: Test-Driven Development в iOS
Rambler.iOS #3: Test-Driven Development в iOS
 
course js day 2
course js day 2course js day 2
course js day 2
 
ASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NET
ASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NETASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NET
ASP.NET MVC за пределами Hello World. Дятлов Александр D2D Just.NET
 
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким ХалиловWebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
WebCamp: Developer Day: Parse'им бэкенд - Аким Халилов
 
Retro vs Volley
Retro vs VolleyRetro vs Volley
Retro vs Volley
 
Что могут статические анализаторы, чего не могут программисты и тестировщики
Что могут статические анализаторы, чего не могут программисты и тестировщикиЧто могут статические анализаторы, чего не могут программисты и тестировщики
Что могут статические анализаторы, чего не могут программисты и тестировщики
 

Тестирование spring boot приложений