IoC의 분류
DL(Dependency Lookup) 과 DI (Dependency Injection)
- DL : 저장소에 저장되어 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 Lockup하는 것
- DI : 각 클래스간의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것
- Setter Injection (수정자 주입)
- Constructor Injection (생성자 주입)
- Method Injection (필드 주입)
스프링 컨테이너 (= IoC 컨테이너)의 종류
스프링 컨테이너가 관리하는 객체를 빈(Bean)이라고 하고,
이 빈들을 관리한다는 의미로 컨테이너를 빈 팩토리(BeanFactory) 라고 부릅니다.
- 객체의 생성과 객체 사이의 런타임 관계를 DI 관점에서 볼 때 컨테이너를 BeanFactory라고 한다.
- BeanFactory에 여러가지 컨테이너 기능을 추가한 어플리케이션컨텍스트(ApplicationContext) 가 있다.
1. BeanFactory
- BeanFactory 계열의 인터페이스만 구현한 클래스는 단순히 컨테이너에서 객체를 생성하고 DI를 처리하는 기능만 제공한다.
- Bean을 등록, 생성, 조회, 반환 관리를 한다.
- 팩토리 디자인 패턴을 구현한 것으로 BeanFactory는 빈을 생성하고 분배하는 책임을 지는 클래스이다.
- Bean을 조회할 수 있는 getBean() 메소드가 정의되어 있다.
- 보통은 BeanFactory를 바로 사용하지 않고, 이를 확장한 ApplicationContext를 사용한다.
2. ApplicationContext
- Bean을 등록, 생성, 조회, 반환 관리하는 기능은 BeanFactory와 같다.
- 스프링의 각종 부가 기능을 추가로 제공한다.
- BeanFactory 보다 더 추가적으로 제공하는 기능
- 국제화가 지원되는 텍스트 메시지를 관리 해준다.
- 이미지같은 파일 자원을 로드할 수 있는 포괄적인 방법을 제공해준다.
- 리스너로 등록된 빈에게 이벤트 발생을 알려준다.
따라서 대부분의 어플리케이션에서는 빈팩토리 보다는 어플리케이션콘텍스트를 사용하는 것이 더 좋습니다.
TDD(Test-Driven Development) = 테스트 주도 개발
테스트 주도 개발(TDD, Test-Driven Development)은 소프트웨어 개발 방법론 중 하나로, 개발자가 기능을 구현하기 전에 테스트를 먼저 작성하고, 이를 통해 구현된 코드가 요구사항을 충족하는지를 지속적으로 검증합니다. TDD의 기본 사이클은 Red-Green-Refactor라고 불리며, 이 세 가지 단계를 반복하면서 코드를 개발합니다.
TDD의 기본 사이클: Red-Green-Refactor
- Red: 실패하는 테스트 작성
- 기능을 구현하기 전에, 해당 기능이 어떻게 동작해야 하는지에 대한 테스트를 작성합니다.
- 이 단계에서 테스트는 당연히 실패합니다(테스트가 빨간색으로 표시됨). 이는 아직 기능이 구현되지 않았기 때문입니다.
- Green: 최소한의 코드로 테스트 통과
- 테스트를 통과하기 위해 최소한의 코드를 작성합니다.
- 이 단계의 목표는 테스트가 성공하도록(녹색으로 표시됨) 코드를 작성하는 것입니다.
- Refactor: 리팩토링
- 테스트가 통과하면, 코드를 리팩토링하여 중복을 제거하고, 가독성을 높이며, 최적화합니다.
- 리팩토링 후에도 테스트는 계속 통과해야 합니다.
예제: TDD를 사용한 간단한 계산기 구현
아래 예제에서는 간단한 계산기 클래스를 TDD를 사용하여 구현합니다.
1. 실패하는 테스트 작성 (Red 단계)
먼저, 계산기 클래스의 덧셈 기능을 테스트하기 위한 실패하는 테스트를 작성합니다.
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
}
이 단계에서 Calculator 클래스와 add 메서드는 아직 구현되지 않았기 때문에 테스트는 실패합니다.
2. 최소한의 코드로 테스트 통과 (Green 단계)
테스트를 통과시키기 위해 최소한의 코드를 작성합니다.
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
이제 CalculatorTest를 실행하면 테스트가 통과합니다.
3. 리팩토링 (Refactor 단계)
코드를 리팩토링하여 가독성을 높이고, 유지보수성을 개선합니다. 이 예제에서는 리팩토링할 필요가 없지만, 더 복잡한 경우에는 이 단계에서 중복 코드를 제거하거나 성능을 최적화할 수 있습니다.
TDD의 장점
- 높은 품질의 코드: 기능을 구현하기 전에 테스트를 작성함으로써, 요구사항을 명확히 이해하고 충족할 수 있습니다.
- 빠른 피드백: 테스트를 통해 코드 변경이 기존 기능에 미치는 영향을 빠르게 확인할 수 있습니다.
- 유지보수성 향상: 테스트가 포함된 코드베이스는 리팩토링과 기능 추가 시 안전성을 보장합니다.
- 디버깅 시간 단축: 버그를 빠르게 찾아 수정할 수 있습니다.
TDD의 단점
- 초기 투자 비용: 테스트를 작성하는 데 시간이 걸리며, 초기 개발 속도가 느릴 수 있습니다.
- 테스트 유지보수: 코드가 변경됨에 따라 테스트도 함께 유지보수해야 합니다.
- 테스트 작성의 어려움: 복잡한 로직이나 외부 의존성이 많은 경우 테스트 작성이 어려울 수 있습니다.
Unit Test : 단위 테스트
단위 테스트(Unit Test)는 소프트웨어 개발에서 개별 단위(보통 메서드나 클래스)의 기능을 검증하는 테스트 방법입니다. 단위 테스트는 코드의 특정 부분이 의도한 대로 작동하는지를 확인하는 데 초점을 맞춥니다. 이는 개발 초기 단계에서 버그를 발견하고 수정할 수 있게 해주며, 코드의 품질을 향상시키는 데 중요한 역할을 합니다.
단위 테스트의 특징
- 작고 집중된 테스트: 각 테스트는 가능한 한 작고 단순해야 하며, 특정 기능이나 메서드를 검증하는 데 집중해야 합니다.
- 독립적: 각 테스트는 다른 테스트와 독립적으로 실행되어야 합니다. 하나의 테스트가 실패하더라도 다른 테스트에는 영향을 미치지 않아야 합니다.
- 빠른 실행: 단위 테스트는 빠르게 실행되어야 합니다. 이를 통해 개발자는 코드 변경 후 즉시 피드백을 받을 수 있습니다.
- 자동화: 단위 테스트는 자동화되어야 하며, 빌드 프로세스의 일부로 쉽게 통합될 수 있어야 합니다.
자바에서 단위 테스트 작성
자바에서는 JUnit이라는 테스트 프레임워크를 사용하여 단위 테스트를 작성합니다. JUnit은 자바 언어용 단위 테스트 프레임워크로, 테스트 케이스 작성, 실행, 결과 검증을 도와줍니다.
JUnit 5
- JUnit Platform: 테스트 프레임워크를 실행하는 데 필요한 인프라를 제공합니다.
- JUnit Jupiter: JUnit 5에서 새로운 프로그래밍 모델과 확장 모델을 정의합니다. @Test, @BeforeEach, @AfterEach 등 JUnit 5의 새로운 애노테이션을 제공하고, 이를 기반으로 테스트를 작성할 수 있게 합니다. ' junit-jupiter-engine '을 의존성에 추가하면, JUnit Jupiter와 함께 JUnit Platform도 자동으로 포함됩니다.
- JUnit Vintage: JUnit 3와 JUnit 4 기반의 테스트를 실행할 수 있게 합니다.
JUnit 5의 주요 기능
- 새로운 애노테이션: JUnit 4에 비해 더 다양한 애노테이션을 제공하여 테스트를 유연하게 작성할 수 있습니다.
- 동적 테스트: @TestFactory를 사용하여 런타임에 테스트 케이스를 생성할 수 있습니다.
- 확장 모델: Extension API를 사용하여 테스트 실행 전후에 추가적인 작업을 수행할 수 있습니다.
JUnit 5의 주요 애노테이션
- @Test: 테스트 메서드를 나타냅니다.
- @BeforeEach: 각 테스트 메서드 실행 전에 실행됩니다.
- @AfterEach: 각 테스트 메서드 실행 후에 실행됩니다.
- @BeforeAll: 모든 테스트 메서드 실행 전에 한 번 실행됩니다.
- @AfterAll: 모든 테스트 메서드 실행 후에 한 번 실행됩니다.
- @DisplayName: 테스트 메서드의 표시 이름을 정의합니다.
- @Disabled: 테스트 메서드 또는 클래스가 실행되지 않도록 합니다.
- @TestFactory: 동적 테스트를 생성합니다.
- @Nested: 내부 클래스에서 테스트 그룹을 정의합니다.
- @Tag: 테스트를 분류할 수 있습니다.
Assert (어썰트)
Assert는 단위 테스트에서 어썰션을 수행하는 구체적인 메서드를 말합니다. JUnit에서는 다양한 Assert 메서드를 제공하여 테스트 조건을 검증합니다.
JUnit에서 Assert 메서드
JUnit에서는 다양한 Assert 메서드를 제공합니다. 가장 많이 사용되는 메서드들은 다음과 같습니다:
- assertEquals(expected, actual): 기대값과 실제값이 같은지 검증합니다.
- assertNotEquals(unexpected, actual): 기대값과 실제값이 다른지 검증합니다.
- assertTrue(condition): 조건이 참인지 검증합니다.
- assertFalse(condition): 조건이 거짓인지 검증합니다.
- assertNull(object): 객체가 null인지 검증합니다.
- assertNotNull(object): 객체가 null이 아닌지 검증합니다.
- assertThrows(expectedType, executable): 실행 시 특정 예외가 발생하는지 검증합니다.
Assertion (어썰션)
Assertion은 코드의 상태를 검증하기 위해 사용되는 표현입니다. 일반적으로 코드가 실행되는 동안 특정 조건이 참인지 확인하는 데 사용됩니다. Assertion은 코드 내에서 논리적 오류를 찾는 데 도움을 주며, 테스트 자동화를 통해 코드의 신뢰성을 높입니다.
Test Fixture
Test Fixture는 테스트 환경을 설정하고 정리하는 데 사용되는 구성 요소와 메서드를 말합니다. 이는 테스트 코드가 실행되기 전에 필요한 초기 설정을 하고, 테스트가 끝난 후에 정리 작업을 수행하여 일관된 테스트 환경을 유지하도록 합니다. Test Fixture는 테스트 데이터, 설정, 초기화 및 정리를 포함할 수 있습니다.
Test Fixture의 주요 구성 요소
- 설정 메서드 (Setup Method): 테스트 실행 전에 호출되어 필요한 리소스를 준비하거나 초기 상태를 설정하는 메서드입니다. JUnit에서는 @BeforeEach와 @BeforeAll 애노테이션을 사용하여 설정 메서드를 정의합니다.
- 정리 메서드 (Teardown Method): 테스트 실행 후에 호출되어 리소스를 해제하거나 테스트 환경을 정리하는 메서드입니다. JUnit에서는 @AfterEach와 @AfterAll 애노테이션을 사용하여 정리 메서드를 정의합니다.
- 테스트 데이터: 테스트를 수행하기 위해 필요한 데이터입니다. 이는 설정 메서드에서 초기화될 수 있습니다.