Юнит-тест - Что такое издевательство? и почему?

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

mockito-logo

Проще говоря, насмешка - это создание объектов, имитирующих поведение реальных объектов. См. Следующий пример из практики:

Проверено:

  1. Java 1.8

  2. Юнит 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));

        }

}

Готово. Ваш отзыв приветствуется