ユニットテスト - モッキングとは何ですか?なぜ?

単体テスト–モッキングとは何ですか? なぜ?

mockito-logo

簡単に言えば、モックとは、実際のオブジェクトの動作を模倣するオブジェクトを作成することです。 次のケーススタディを参照してください。

テスト済み

  1. Java 1.8

  2. JUnit 4.12

  3. 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 :

  1. DAOからデータを取得するにはデータベースを起動する必要があるため、この単体テストは低速です。

  2. この単体テストは分離されておらず、常にデータベースなどの外部リソースに依存しています。

  3. この単体テストでは、テスト条件が常に同じであることを保証できません。データベース内のデータは時間とともに変化する可能性があります。

  4. 簡単な方法をテストするには手間がかかりすぎて、開発者はテストをスキップします。

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 :

  1. 最後に、単体テストの目的で、多くのモックオブジェクト(クラス)を作成できます。

  2. インターフェイスに多くのメソッドが含まれている場合は、それぞれをオーバーライドする必要があります。

  3. まだ手間がかかり、面倒です!

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));

        }

}

完了しました。 あなたのフィードバックは大歓迎です