Автор оригинала: Joe Cavazos.
Модульные тесты являются абсолютно важным компонентом любого серьезного программного приложения. Это не новаторское утверждение, и большинство разработчиков согласятся с тем, что эффективные модульные тесты являются полезным способом проверки реализуемой функции или исправляемой ошибки и особенно полезны в качестве защиты от будущих регрессий и ошибок.
Часть моего процесса разработки любого приложения, которое я пишу, будь то профессионально или в качестве хобби, включает написание по крайней мере одного модульного теста для каждой функции, которую я пишу, или исправляю ошибку, и я настоятельно рекомендую другим разработчикам преследовать ту же цель.
Даже если разработчик пишет модульные тесты, иногда эти тесты могут быть, ну… не очень-то полезно. Это может произойти по ряду причин:
- Крайние сроки выпуска продукции
- Разработчик находит написание тестов скучным
- Существуют требования к охвату класса/линии
Что подтверждает ваш тест?
Каждый тест должен что-то проверять. В идеале это “что-то” является “контрактом” метода или процесса. Контракт можно рассматривать как “что делает этот метод?” или, возможно, “что этот метод определенно не делает?”
Пока что элементарные вещи. И все же в своей карьере я сталкивался со множеством модульных тестов, которые, похоже, не подтверждают ничего полезного. Давайте рассмотрим простой гипотетический пример:
Допустим, например, вы пишете модульный тест для контроллера сервлетов, который поддерживает веб-страницу JSP. Когда страница загружается, вы хотите загрузить список объектов из базы данных в сеанс, чтобы они были доступны на странице (возможно, не лучший дизайн, но давайте пока остановимся на этом).
// ClientPageController.java public String loadPage(HttpSession session, String clientId) throws Exception { Listcustomers = customerService.findByClientId(clientId); session.setAttribute("customers", customers); return "myPage"; }
Пока все довольно просто. Но затем вы видите этот модульный тест:
@Test public void testLoadPage() throws Exception { HttpSession session = mock(HttpSession.class); String result = clientPageController.loadPage(session, "my_client_id"); assertEquals("myPage", result); }
Здесь чего-то не хватает: частью контракта этого метода является настройка списка клиентов в сеансе HttpSession, и все же в тесте нет ничего, подтверждающего это. Вы проверяете правильность возвращаемого значения и получили некоторое покрытие теста, но в вашем тесте отсутствует важная проверка. Допустим, другой разработчик забредает в эту базу кода и ошибочно фиксирует это изменение:
// ClientPageController.java public String loadPage(HttpSession session, String clientId) throws Exception { // TODO: uncomment when compilation errors are fixed // Listcustomers = customerService.findByClientId(clientId); // session.setAttribute("customers", customers); return "myPage"; }
Приведенный выше модульный тест все еще проходит . Вот в чем проблема! Поэтому вы решаете скорректировать свой модульный тест с помощью проверки:
@Test public void testLoadPage() throws Exception { HttpSession session = mock(HttpSession.class); String result = clientPageController.loadPage(session, "my_client_id"); assertEquals("myPage", result); verify(session).setAttribute("customers", anyListOf(Customer.class)); }
У нас дела идут немного лучше, но все еще есть проблема.
Не злоупотребляйте Mockito() спички
Вы были бы удивлены, насколько это распространено. Правильная проверка может быть сложной или трудоемкой для написания, поэтому иногда разработчики используют ярлык, просто проверяя, вызывается ли метод с любыми аргументами, как указано выше. Но вспомните обсуждение “контракта” метода ранее и то, что мы проверяем. Должен ли метод loadPage()
просто установить атрибут сеанса в любой список? Или предполагается, что он загружает определенный список в сеанс?
Проверить, проверить, проверить
Мы хотим, чтобы метод loadPage()
загружал список клиентов для клиента в сеанс. Так вот что мы хотим проверить. Давайте посмотрим, как это сделать простым способом:
@Mock CustomerService customerService; @Test public void testLoadPage() throws Exception { ListfakeCustomers = new ArrayList<>(); fakeCustomers.add(buildFakeCustomer()); when(customerService.findByClientId("my_client_id")) .thenReturn(fakeCustomers); HttpSession session = mock(HttpSession.class); String result = clientPageController.loadPage(session, "my_client_id"); assertEquals("myPage", result); verify(session).setAttribute("customers", fakeCustomers); }
Намного лучше! Наш модульный тест теперь более надежен. Когда мы вызываем проверка(сеанс).Атрибут setAttribute("клиенты", поддельные клиенты)
мы эффективно проверяем, установлен ли HttpSession со списком клиентов, извлеченных из базы данных.
Еда на вынос
Не позволяйте вашим модульным тестам быть небрежными только потому, что вы гоняетесь за номерами классов/строк или считаете, что писать тесты скучно. Для каждого теста, который вы пишете, тщательно подумайте о том, что делает метод или процесс, и как лучше всего проверить, что метод ведет себя так, как задумано . Когда вы увидите, что ваши тестовые прогоны загораются зелеными галочками, вы почувствуете себя лучше, зная, что каждый из них имеет значение.
Оригинал: “https://www.codementor.io/@joecavazos/writing-effective-unit-tests-in-java-with-mockito-mfrkdmtpw”