Перемещая нашу дочь в ее новую квартиру, которая находится на 9-м этаже арендного комплекса, я заметил интересную ситуацию, ожидая прибытия лифта. Когда другие подходили, чтобы ждать лифта, они нажимали кнопку с универсальным символом «стрелка вверх» — даже если мы уже нажали кнопку, чтобы запросить обслуживание лифта.
Я начал задаваться вопросом о мыслительном процессе в игре здесь. Чувствовал ли человек, что лифт прибудет быстрее, если снова нажать кнопку вверх? Ожидался ли другой результат?
Вам также может понравиться:
7 советов по написанию лучших модульных тестов на Java
Верьте или нет, это напомнило мне о чем-то, что я называю «безумием юнит-теста».
Юнит Тест Безумие
С тех пор как я увидел ценность в написании своего первого модульного теста несколько десятилетий назад, я всегда выступал за включение охвата модульных тестов для проверки функциональности тестируемой системы (SUT). Тем не менее, как и почти все в информационных технологиях, есть возможность перестроиться до такой степени, что ценность таких улучшений приближается к нулю.
В этой статье я хочу сосредоточиться на сценарии, где добавлено покрытие модульных тестов, которое дублирует набор уже используемых тестов. Когда это происходит, набор тестов завершает выполнение одного и того же теста снова и снова — как будто ожидается другой результат.
Insanity.
Пример
В моей статье « Избегать перезагрузки контекста приложения в модульных тестах » в зоне микросервисов я привел следующий пример реализации класса обслуживания:
Джава
1
2
3
public class WidgetServiceImpl implements WidgetService {
4
private final AccountService accountService;
5
private final WidgetRepository widgetRepository;
6
7
/**
8
* {@inheritDoc}
9
*/
10
11
public List<Widget> getWidgetsByAccountId(Long accountId, String authId) throws AccountException {
12
Account account = accountService.getAccountById(accountId, authId);
13
return widgetRepository.getWidgetsByAccountId(accountId);
14
}
15
}
В этом очень простом примере, прежде чем List <Widget> может быть возвращен, AccountService
вызывается, чтобы обеспечить некоторый уровень авторизации / валидации, который authId
имеет надлежащее разрешение для просмотра виджетов, связанных с предоставленными accountId
.
Если надлежащее разрешение не будет достигнуто, AccountService
будет брошено AccountException
. Это исключение просто передается тому, что вызывает getWidgetsByAccountId()
метод.
Модульный тест покрытия
В приведенном выше примере, когда он AccountService
был представлен, разработчик включил бы модульные тесты, которые охватывают все аспекты getAccountById()
метода.
Некоторые примеры модульных тестов перечислены ниже:
- Действительный
authId
, который имеет надлежащий доступ к действительномуaccountId
, возвращающемуAccount
объект - Неверный
accountId
, бросаяAccountException
- Неверный
authId
, бросаяAccountException
AuthId
не имеет доступа к accountId, выбрасываяAccountException
Что касается getWidgetsByAccountId()
метода, безусловно, существует необходимость смоделировать AccountService
и создать when()
случай, когда значения accountId
и authId
являются действительными значениями и Account
объект возвращается.
Пример очень простого модульного теста может выглядеть следующим образом:
Джава
xxxxxxxxxx
1
public class WidgetServiceTest extends BaseServiceTest {
2
private final AccountService accountService = Mockito.mock(AccountService.class);
3
private final WidgetRepository widgetRespository = Mockito.mock(WidgetRepository.class);
4
WidgetServiceImpl widgetService = new WidgetService(accountService, widgetRepository);
5
6
7
8
void testGetWidgetsByAccountId() throws Exception {
9
long accountId = 1L;
10
long widgetId = 2L;
11
String authId = "notARealAuthId";
12
13
Account account = new Account();
14
account.setId(accountId);
15
16
List<Widget> widgets = new ArrayList<>();
17
Widget widget = new Widget();
18
widget.setId(widgetId);
19
widgets.add(widget);
20
21
when(accountService.getAccountById(accountId, authId)).thenReturn(account);
22
when(widgetRepository.getWidgetsByAccountId(accountId)).thenReturn(widgets);
23
24
List<Widget> testWidgets = widgetService.getWidgetsByAccountId(accountId, authId);
25
26
assertEquals(widgets, testWidgets);
27
}
28
}
В этом примере на самом деле нет необходимости тестировать сценарии использования, которые просто выдают AccountException
. Это связано с тем, что этот результат на самом деле не является частью тестируемой системы (SUT) и уже предполагается, что он рассматривается в AccountServiceTest
классе.
Заключение
При написании модульных тестов всегда важно сосредоточиться на тестировании только тестируемой системы (SUT). В тех случаях, когда на тестируемый метод может повлиять исключение, генерируемое внедренной службой, это исключение службы не нужно проверять, если результат исключения просто перенаправляет исключение методу, вызывающему SUT. Для меня это является примером «безумия модульного теста».
Исключением из этого правила является случай, когда зависимое исключение перехвачено и поток SUT изменяется. В этом случае необходимо выбросить исключение — так же, как необходимы действительные проверенные данные — но основное внимание в тесте должно быть уделено проверке того, что логика в SUT работает должным образом.
Хорошего дня!
Дальнейшее чтение
7 советов по написанию лучших модульных тестов на Java