Модульный тест - что такое насмешка? и почему?
Проще говоря, насмешка - это создание объектов, имитирующих поведение реальных объектов. См. Следующий пример из практики:
Проверено:
-
Java 1.8
-
Юнит 4.12
-
Mockito 2.0.73-бета
Mock Object
Прочтите этоWikipedia Mock
object.
1. Примеры Java
Простой пример автора и книги.
1.1 BookService
to return a list of books by author name.
BookService.java
package com.example.examples.mock; import java.util.List; public interface BookService { ListfindBookByAuthor(String author); }
BookServiceImpl.java
package com.example.examples.mock; import java.util.List; public class BookServiceImpl implements BookService { private BookDao bookDao; public BookDao getBookDao() { return bookDao; } public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } @Override public ListfindBookByAuthor(String name) { return bookDao.findBookByAuthor(name); } }
BookDao.java
package com.example.examples.mock; import java.util.List; public interface BookDao { ListfindBookByAuthor(String author); }
BookDaoImpl.java
package com.example.examples.mock; import java.util.List; public class BookDaoImpl implements BookDao { @Override public ListfindBookByAuthor(String name) { // init database // Connect to DB for data // return data } }
1.2 A book validator.
BookValidatorService.java
package com.example.examples.mock; public interface BookValidatorService { boolean isValid(Book book); }
FakeBookValidatorService.java
package com.example.examples.mock; public class FakeBookValidatorService implements BookValidatorService { @Override public boolean isValid(Book book) { if (book == null) return false; if ("bot".equals(book.getName())) { return false; } else { return true; } } }
1.3 Reviews the AuthorServiceImpl
, it has dependencies on BookService
(depends on BookDao
) and BookValidatorService
, it makes the unit test a bit hard to write.
AuthorService.java
package com.example.examples.mock; public interface AuthorService { int getTotalBooks(String author); }
AuthorServiceImpl.java
package com.example.examples.mock; import java.util.List; import java.util.stream.Collectors; public class AuthorServiceImpl implements AuthorService { private BookService bookService; private BookValidatorService bookValidatorService; public BookValidatorService getBookValidatorService() { return bookValidatorService; } public void setBookValidatorService(BookValidatorService bookValidatorService) { this.bookValidatorService = bookValidatorService; } public BookService getBookService() { return bookService; } public void setBookService(BookService bookService) { this.bookService = bookService; } //How to test this method ??? @Override public int getTotalBooks(String author) { Listbooks = bookService.findBookByAuthor(author); //filters some bot writers List filtered = books.stream().filter( x -> bookValidatorService.isValid(x)) .collect(Collectors.toList()); //other business logic return filtered.size(); } }
2. Модульный тест
Создайте модульный тест дляAuthorServiceImpl.getTotalBooks()
2.1 The AuthorServiceImpl
has two dependencies, you need to make sure both are configured properly.
AuthorServiceTest.java
package com.example.mock; import com.example.examples.mock.AuthorServiceImpl; import com.example.examples.mock.BookDaoImpl; import com.example.examples.mock.BookServiceImpl; import com.example.examples.mock.FakeBookValidatorService; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; public class AuthorServiceTest { @Test public void test_total_book_by_mock() { //1. Setup AuthorServiceImpl obj = new AuthorServiceImpl(); BookServiceImpl bookService = new BookServiceImpl(); bookService.setBookDao(new BookDaoImpl()); //Where Dao connect to? obj.setBookService(bookService); obj.setBookValidatorService(new FakeBookValidatorService()); //2. Test method int qty = obj.getTotalBooks("example"); //3. Verify result assertThat(qty, is(2)); } }
Чтобы пройти вышеупомянутый модульный тест, вам необходимо настроить базу данных на уровне DAO, иначеbookService
ничего не вернет.
2.3 Some disadvantages to perform tests like above :
-
Этот модульный тест выполняется медленно, потому что вам нужно запустить базу данных, чтобы получить данные из DAO.
-
Этот модульный тест не изолирован, он всегда зависит от внешних ресурсов, таких как база данных.
-
Этот модульный тест не может гарантировать, что условия теста всегда одинаковы, данные в базе данных могут изменяться во времени.
-
Слишком много работы, чтобы протестировать простой метод, заставит разработчиков пропустить тест.
2.4 Solution
The solution is obvious, you need a modified version of the BookServiceImpl
class – which will always return the same data for testing, a mock object!
What is mocking?
Опять же, насмешка - это создание объектов, имитирующих поведение реальных объектов.
3. Модульный тест - макет объекта
3.1 Create a new MockBookServiceImpl
class and always return the same collection of books for author “example”.
MockBookServiceImpl.java
package com.example.mock; import com.example.examples.mock.Book; import com.example.examples.mock.BookService; import java.util.ArrayList; import java.util.List; //I am a mock object! public class MockBookServiceImpl implements BookService { @Override public ListfindBookByAuthor(String author) { List books = new ArrayList<>(); if ("example".equals(author)) { books.add(new Book("example in action")); books.add(new Book("abc in action")); books.add(new Book("bot")); } return books; } //implements other methods... }
3.2 Update the unit test again.
AuthorServiceTest.java
package com.example.mock; import com.example.examples.mock.AuthorServiceImpl; import com.example.examples.mock.FakeBookValidatorService; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; public class AuthorServiceTest { @Test public void test_total_book_by_mock() { //1. Setup AuthorServiceImpl obj = new AuthorServiceImpl(); /*BookServiceImpl bookService = new BookServiceImpl(); bookService.setBookDao(new BookDaoImpl()); obj.setBookService(bookService);*/ obj.setBookService(new MockBookServiceImpl()); obj.setBookValidatorService(new FakeBookValidatorService()); //2. Test method int qty = obj.getTotalBooks("example"); //3. Verify result assertThat(qty, is(2)); } }
Вышеупомянутый модульный тест намного лучше, быстрее, изолирован (больше нет базы данных), а условия тестирования (данные) всегда одинаковы.
3.3 But, there are some disadvantages to create mock object manually like above :
-
В конце вы можете создать множество фиктивных объектов (классов) только для целей модульного тестирования.
-
Если интерфейс содержит много методов, вам нужно переопределить каждый из них.
-
Еще слишком много работы и грязно!
3.4 Solution
Try Mockito, a simple and powerful mocking framework.
4. Модульный тест - Mockito
4.1 Update the unit test again, this time, create the mock object via Mockito framework.
pom.xml
org.mockito mockito-core 2.0.73-beta
AuthorServiceTest.java
package com.example.mock; import com.example.examples.mock.AuthorServiceImpl; import com.example.examples.mock.Book; import com.example.examples.mock.BookServiceImpl; import com.example.examples.mock.FakeBookValidatorService; import org.junit.Test; import java.util.Arrays; import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class AuthorServiceTest { @Test public void test_total_book_by_mockito() { //1. Setup Listbooks = Arrays.asList( new Book("example in action"), new Book("abc in action"), new Book("bot")); BookServiceImpl mockito = mock(BookServiceImpl.class); //if the author is "example", then return a 'books' object. when(mockito.findBookByAuthor("example")).thenReturn(books); AuthorServiceImpl obj = new AuthorServiceImpl(); obj.setBookService(mockito); obj.setBookValidatorService(new FakeBookValidatorService()); //2. Test method int qty = obj.getTotalBooks("example"); //3. Verify result assertThat(qty, is(2)); } }
Готово. Ваш отзыв приветствуется