Модульный тест - что такое насмешка? и почему?

Проще говоря, насмешка - это создание объектов, имитирующих поведение реальных объектов. См. Следующий пример из практики:
Проверено:
-
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 {
List findBookByAuthor(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 List findBookByAuthor(String name) {
return bookDao.findBookByAuthor(name);
}
}
BookDao.java
package com.example.examples.mock;
import java.util.List;
public interface BookDao {
List findBookByAuthor(String author);
}
BookDaoImpl.java
package com.example.examples.mock;
import java.util.List;
public class BookDaoImpl implements BookDao {
@Override
public List findBookByAuthor(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) {
List books = 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 List findBookByAuthor(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
List books = 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));
}
}
Готово. Ваш отзыв приветствуется