简单来说,模拟就是创建模仿真实对象行为的对象。 请参考以下案例研究:
经测试:
- Java 1.8
- JUnit 4.12
- Mockito 2.0.73-beta
模拟对象
阅读此Wikipedia Mock对象 。
1. Java示例
一个简单的作者和书籍示例。
1.1 BookService
返回作者姓名的书籍列表。
package com.mkyong.examples.mock;
import java.util.List;
public interface BookService {
List<Book> findBookByAuthor(String author);
}
package com.mkyong.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<Book> findBookByAuthor(String name) {
return bookDao.findBookByAuthor(name);
}
}
package com.mkyong.examples.mock;
import java.util.List;
public interface BookDao {
List<Book> findBookByAuthor(String author);
}
package com.mkyong.examples.mock;
import java.util.List;
public class BookDaoImpl implements BookDao {
@Override
public List<Book> findBookByAuthor(String name) {
// init database
// Connect to DB for data
// return data
}
}
1.2书籍验证者。
package com.mkyong.examples.mock;
public interface BookValidatorService {
boolean isValid(Book book);
}
package com.mkyong.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 AuthorServiceImpl
,它具有BookService
依赖关系(取决于BookDao
)和BookValidatorService
,这使得单元测试有点难以编写。
package com.mkyong.examples.mock;
public interface AuthorService {
int getTotalBooks(String author);
}
package com.mkyong.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<Book> books = bookService.findBookByAuthor(author);
//filters some bot writers
List<Book> filtered = books.stream().filter(
x -> bookValidatorService.isValid(x))
.collect(Collectors.toList());
//other business logic
return filtered.size();
}
}
2.单元测试
为AuthorServiceImpl.getTotalBooks()
创建单元测试
2.1 AuthorServiceImpl
具有两个依赖项,您需要确保两个配置都正确。
package com.mkyong.mock;
import com.mkyong.examples.mock.AuthorServiceImpl;
import com.mkyong.examples.mock.BookDaoImpl;
import com.mkyong.examples.mock.BookServiceImpl;
import com.mkyong.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("mkyong");
//3. Verify result
assertThat(qty, is(2));
}
}
要通过上述单元测试,您需要在DAO层中建立一个数据库,否则bookService
将不返回任何内容。
2.3执行上述测试的一些缺点:
- 此单元测试很慢,因为您需要启动数据库才能从DAO中获取数据。
- 这个单元测试不是孤立的,它总是取决于外部资源,例如数据库。
- 此单元测试不能确保测试条件始终相同,数据库中的数据可能会随时间变化。
- 测试一种简单方法的工作量太大,导致开发人员跳过该测试。
2.4 解决方案
解决方案很明显,您需要BookServiceImpl
类的修改版本–它将始终返回相同的数据进行测试,即模拟对象 !
什么在嘲笑?
再次,模拟是创建模仿真实对象行为的对象。
3.单元测试–模拟对象
3.1创建一个新的MockBookServiceImpl
类,并始终为作者“ mkyong”返回相同的书籍集合。
package com.mkyong.mock;
import com.mkyong.examples.mock.Book;
import com.mkyong.examples.mock.BookService;
import java.util.ArrayList;
import java.util.List;
//I am a mock object!
public class MockBookServiceImpl implements BookService {
@Override
public List<Book> findBookByAuthor(String author) {
List<Book> books = new ArrayList<>();
if ("mkyong".equals(author)) {
books.add(new Book("mkyong in action"));
books.add(new Book("abc in action"));
books.add(new Book("bot"));
}
return books;
}
//implements other methods...
}
3.2再次更新单元测试。
package com.mkyong.mock;
import com.mkyong.examples.mock.AuthorServiceImpl;
import com.mkyong.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("mkyong");
//3. Verify result
assertThat(qty, is(2));
}
}
上面的单元测试更好,更快,更孤立(不再需要数据库),并且测试条件(数据)始终相同。
3.3但是,如上所述,手动创建模拟对象有一些缺点:
- 最后,您可以创建许多模拟对象(类),仅用于单元测试目的。
- 如果接口包含许多方法,则需要覆盖它们中的每一个。
- 仍然工作太多,而且杂乱无章!
3.4 解决方案
尝试Mockito ,一个简单而强大的模拟框架。
4.单元测试– Mockito
4.1再次更新单元测试,这一次,通过Mockito框架创建模拟对象。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.0.73-beta</version>
</dependency>
package com.mkyong.mock;
import com.mkyong.examples.mock.AuthorServiceImpl;
import com.mkyong.examples.mock.Book;
import com.mkyong.examples.mock.BookServiceImpl;
import com.mkyong.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<Book> books = Arrays.asList(
new Book("mkyong in action"),
new Book("abc in action"),
new Book("bot"));
BookServiceImpl mockito = mock(BookServiceImpl.class);
//if the author is "mkyong", then return a 'books' object.
when(mockito.findBookByAuthor("mkyong")).thenReturn(books);
AuthorServiceImpl obj = new AuthorServiceImpl();
obj.setBookService(mockito);
obj.setBookValidatorService(new FakeBookValidatorService());
//2. Test method
int qty = obj.getTotalBooks("mkyong");
//3. Verify result
assertThat(qty, is(2));
}
}
做完了 感谢您的反馈
参考文献
翻译自: https://mkyong.com/unittest/unit-test-what-is-mocking-and-why/