单元测试–什么是模拟? 为什么?

模拟徽标

简单来说,模拟就是创建模仿真实对象行为的对象。 请参考以下案例研究:

经测试:

  1. Java 1.8
  2. JUnit 4.12
  3. Mockito 2.0.73-beta

模拟对象
阅读此Wikipedia Mock对象

1. Java示例

一个简单的作者和书籍示例。

1.1 BookService返回作者姓名的书籍列表。

BookService.java
package com.mkyong.examples.mock;

import java.util.List;

public interface BookService {

    List<Book> findBookByAuthor(String author);

}
BookServiceImpl.java
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);
    }

}
BookDao.java
package com.mkyong.examples.mock;

import java.util.List;

public interface BookDao {

    List<Book> findBookByAuthor(String author);
    
}
BookDaoImpl.java
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书籍验证者。

BookValidatorService.java
package com.mkyong.examples.mock;

public interface BookValidatorService {

    boolean isValid(Book book);

}
FakeBookValidatorService.java
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 ,这使得单元测试有点难以编写。

AuthorService.java
package com.mkyong.examples.mock;

public interface AuthorService {

    int getTotalBooks(String author);

}
AuthorServiceImpl.java
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具有两个依赖项,您需要确保两个配置都正确。

AuthorServiceTest.java
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执行上述测试的一些缺点:

  1. 此单元测试很慢,因为您需要启动数据库才能从DAO中获取数据。
  2. 这个单元测试不是孤立的,它总是取决于外部资源,例如数据库。
  3. 此单元测试不能确保测试条件始终相同,数据库中的数据可能会随时间变化。
  4. 测试一种简单方法的工作量太大,导致开发人员跳过该测试。

2.4 解决方案
解决方案很明显,您需要BookServiceImpl类的修改版本–它将始终返回相同的数据进行测试,即模拟对象

什么在嘲笑?
再次,模拟是创建模仿真实对象行为的对象。

3.单元测试–模拟对象

3.1创建一个新的MockBookServiceImpl类,并始终为作者“ mkyong”返回相同的书籍集合。

MockBookServiceImpl.java
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再次更新单元测试。

AuthorServiceTest.java
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但是,如上所述,手动创建模拟对象有一些缺点:

  1. 最后,您可以创建许多模拟对象(类),仅用于单元测试目的。
  2. 如果接口包含许多方法,则需要覆盖它们中的每一个。
  3. 仍然工作太多,而且杂乱无章!

3.4 解决方案
尝试Mockito ,一个简单而强大的模拟框架。

4.单元测试– Mockito

4.1再次更新单元测试,这一次,通过Mockito框架创建模拟对象。

pom.xml
<dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.0.73-beta</version>
	</dependency>
AuthorServiceTest.java
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));

	    }

}

做完了 感谢您的反馈

参考文献

  1. Mockito官方网站
  2. DZone – Mockito
  3. 维基百科–模拟对象
  4. 使用Mockito进行单元测试

翻译自: https://mkyong.com/unittest/unit-test-what-is-mocking-and-why/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值