본문 바로가기
4. Backend Development/1. Spring Framework

1. Spring IoC (Inversion of Control)

by H232C 2020. 4. 29.

 

1. IoC (Inversion of Control) : 제어의 역전

- IoC는 코드의 의존성을 외부에서 관리하여 복잡성과 오류를 줄이며 테스트를 용이하게 함
- IoC를 설명하기 위해 아래의 예제 코드를 사용(Book, BookService, BookRepository, BookStatus)

// Book.class

package org.springframework.samples.petclinic.book;

import java.util.Date;

public class Book {

    private Date created;
    private BookStatus bookStatus;


    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public BookStatus getBookStatus() {
        return bookStatus;
    }

    public void setBookStatus(BookStatus bookStatus) {
        this.bookStatus = bookStatus;
    }

}

 

// BookRepository.class

package org.springframework.samples.petclinic.book;
import org.springframework.stereotype.Repository;

@Repository
public class BookRepository {

    public Book save(Book book){
        return null;
    }
}

 

// BookService.class

package org.springframework.samples.petclinic.book;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class BookService {

    @Autowired // BookRepository는 의존성 주입을 받고있음
    // 이 코드는 BookRepository와 BookService간의 의존관계를 나타내고 있음
    private BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public Book save(Book book){
        book.setCreated(new Date());
        book.setBookStatus(BookStatus.DRAFT);
        return bookRepository.save(book);
    }
}

 

// BookStatus.class

package org.springframework.samples.petclinic.book;

public enum BookStatus {

    DRAFT, PUBLISHED;
}

 

- IoC 미적용 코드(내가 사용할 의존성은 내가 만들어서 사용)

// BookService.class

package org.springframework.samples.petclinic.book;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;


public class BookService {

    BookRepository bookRepository = new BookRepository(); // BookRepository를 직접 인스턴스화
    
    public BookService(BookRepository bookRepository){
    	this.bookRepository = bookRepository;
        }

    public Book save(Book book){
        book.setCreated(new Date());
        book.setBookStatus(BookStatus.DRAFT);
        return bookRepository.save(book);
    }
}

 

- IoC 적용 코드(내가 사용할 의존성을 외부에서 누군가가 주입해줌, 의존성의 타입만 맞으면 어떤거든 상관없음)

// BookService.class

package org.springframework.samples.petclinic.book;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class BookService {

    @Autowired // BookRepository는 의존성 주입을 받고있음
    private BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    } // 이 코드는 BookRepository와 BookService간의 의존관계를 나타내고 있음
    
    public Book save(Book book){
        book.setCreated(new Date());
        book.setBookStatus(BookStatus.DRAFT);
        return bookRepository.save(book);
    }
}

 

- BookServiceTest (IoC 미적용 코드 테스트)

public class BookServiceTest {
    private BookRepository bookRepository;

    @Test
    public void save(){
        Book book = new Book();

        BookRepository bookRepository = new BookRepository();
        BookService bookService = new BookService(bookRepository);

        Book result = bookService.save(book);
        //bookRepository의 return 값이 null이기 때문에 아래의 코드는 에러를 발생시킨다.

        assertThat(book.getCreated()).isNotNull();
        assertThat(book.getBookStatus()).isEqualTo(BookStatus.DRAFT);
        assertThat(result).isNotNull();
}
        

 위의 "테스트 코드는 에러 코드"를 유발한다. bookService.save(book)의 Return 값이 현재 Null로 되어있기 때문에 해당 코드가 asserThat 코드에서 기대하는 결과값을 뱉어내게끔 동작 테스트를 하고자 한다면 직접 BookService.class 내의 코드를 변경해주어야 한다.

- BookServiceTest (IoC 적용 코드 테스트)

package org.springframework.samples.petclinic.book;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;

import static org.junit.Assert.*;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
public class BookServiceTest {

    @Mock // Mock 객체 생성 (Mock은 일종의 가짜 객체이다.)
    private BookRepository bookRepository;

    @Test
    public void save(){
        Book book = new Book();
        when(bookRepository.save(book)).thenReturn(book); // 목킹작업, save(book)이 들어오면 book을 리턴해라
        // RunWith 어노테이션을 사용하지 않으면 Mock이 발생되지 않음 -> NullPointException 발생
        BookService bookService = new BookService(bookRepository);

        Book result = bookService.save(book);

        assertThat(book.getCreated()).isNotNull();
        assertThat(book.getBookStatus()).isEqualTo(BookStatus.DRAFT);
        assertThat(result).isNotNull();
    }
}

 위와 같은 단위 테스트 수행시 IoC 컨테이너에서 관리되는 Bean 객체를 Mock이라는 가짜 Bean 객체로 주입받은뒤 when 기능을 이용하여 특정 값을 리턴하도록 테스트 시나리오를 작성하고 assertThat으로 결과값에 대한 검증이 가능하다.

'4. Backend Development > 1. Spring Framework' 카테고리의 다른 글

6. DispatcherServlet  (0) 2020.09.01
5. Servlet  (0) 2020.09.01
4. MacOS Security Management System  (0) 2020.07.21
3. Spring Framework 기반 웹 프로젝트  (0) 2020.06.09
2. Spring IoC Container  (0) 2020.04.30

댓글