의존성 주입 - 라라벨과 함께
의존성 주입은 객체지향 프로그래밍에서 흔히 사용되는 설계 패턴입니다. 몇 가지 미리 정해진 관례를 통해 의존성 생성을 더 쉽게 관리할 수 있습니다. 의존성을 기반으로 하는 코드를 변경할 필요 없이 의존성을 선언하거나 교체하거나 심지어 모크할 수 있습니다.
예를 들어, 어떤 인증 논리가 있다고 가정해 봅시다
클래스 AuthLogic
{
function authenticate(Request $request): bool
{
시도해 {
$payload = $request->all();
$token = $this->verifyAuthToken($payload);
$this->verifyClient($token);
$this->verifyPermissions($token);
진짜로 반환;
} catch (예외 $e) {
return false;
}
}
}
이 로직과 상호작용하는 모든 코드에 대해 테스트를 작성하고자 합니다. 많은 데이터를 처음부터 시딩해야 합니다. 인증된 컨트롤러 테스트에서는 클라이언트, 사용자, 권한 몇 개를 시딩하고, 위에 언급한 인증 토큰을 생성해야 합니다. 의존성 주입을 사용하면 이 모든 설정을 피하고 'authenticate'를 모크해서 항상 'true' 또는 'false'를 반환할 수 있습니다. Laravel에서 어떻게 하는지 살펴보겠습니다!
의존성 주입 수행법
대부분의 프레임워크와 마찬가지로, Laravel은 의존성 주입(dependency injection)을 사용해 코드를 조직할 수 있게 해줍니다. 이제 어떻게 하는지 살펴보겠습니다! 다음은 우리가 수행해야 할 단계들입니다.
- 의존성 정의
- 의존성을 결합한다
- 의존성 주입
- (선택사항) 의존성을 모의/대체하기
의존성을 정의하세요
먼저, 다른 클래스들이 의존할 객체 클래스를 정의해야 합니다. 여기서는 코드의 다른 부분에서 사용할 간단한 'HelperService' 클래스를 만들 것입니다.
클래스 헬퍼서비스
{
함수 __construct() {
$this->카운터 = 0;
}
function incrementAndGetCounter(): int
{
$this->카운터 += 1;
$this->카운터로 반환;
}
}
의존성을 묶어라
이제 의존성을 구성하고 Laravel 서비스 컨테이너에 바인딩해야 합니다. Laravel에서는 보통 서비스 제공자를 생성하고 등록함으로써 이 작업이 이루어집니다.
php artisan make:provider HelperServiceProvider
생성된 파일에서는 바인딩과 그 바인딩이 필요할 때마다 실행해야 할 함수를 등록합니다. 여기서 클래스를 구성합니다.
Illuminate\Support\ServiceProvider를 사용하세요;
class HelperServiceProvider가 확장하는 ServiceProvider
{
함수 레지스터()
{
$this->app->singleton을 사용해 한 번만 인스턴스화하세요
$this->app->bind(HelperService::class, function() {
새 HelperService();
});
}
}
마지막으로, 'config/app.php'에서 'HelperServiceProvider'를 Laravel 서비스 컨테이너에 바인딩하면 됩니다
'제공자' => [
# ... 기존 제공자
App\Providers\HelperServiceProvider::class,
]
의존성을 주입해
이제 의존성 바인딩이 Laravel 서비스 컨테이너에 선언되었으니, 필요한 곳 어디든 Laravel 코드에 주입할 수 있습니다. 예를 들어, 컨트롤러나 미들웨어에서 자동 인젝션을 통해 사용할 수 있습니다. Laravel은 당신이 원한다고 감지하여 이전에 선언한 바인딩과 일치시킬 수 있는 'HelperService' 클래스를 감지합니다.
클래스 HomeController가 컨트롤러를 확장합니다
{
function __construct(HelperService $helperService)
{
$this->helperService = $helperService;
}
함수 홈()
{
반환 [
'counter' => $this->helperService->incrementAndGetCounter()
];
}
}
또한 Laravel의 'App' 외관을 사용해 그 의존성을 다른 클래스나 함수에 직접 수동으로 주입할 수도 있습니다.
Illuminate\Support\Facades\App을 사용하세요;
클래스 UserService
{
function trySomething(HelperService $helperService)
{
print($this->helperService->incrementAndGetCounter());
}
}
인스턴스를 함수 인자로 자동으로 주입합니다
$result = App::call([new UserService, 'trySomething']);
또는 인스턴스를 해결하고 직접 전달할 수도 있습니다
$helperService = App::make(HelperService::class);
$result = (새로운 UserService)->trySomething($helperService);
이제 서비스 제공자에서 호출된 'new HelperService()'를 명시적으로 구성하지 않고도 모든 후속 코드에서 활용할 수 있음을 알 수 있습니다. 클래스의 구성은 Laravel 시작 프로세스에 의해 제어됩니다.
의존성을 모방/대체하다
이제 의존성 주입을 사용해 의존성 객체를 관리하면서, 실행 중에 객체를 모킹하고 교체할 수 있는 기능이 생겼습니다. 이는 테스트 중(예: 인증이 항상 true로 반환되도록 모킹하는 경우) 특히 유용합니다.
서비스 컨테이너에 객체를 바인딩하는 방법에 대해 더 알아보려면 다음 https://laravel.com/docs/9.x/container
Mockery\MockInterface를 사용하세요;
Illuminate\Foundation\Testing\RefreshDatabase를 사용하세요;
Tests\TestCase를 사용하세요;
class HomeControllerTest가 TestCase 확장
{
RefreshDatabase를 사용하세요;
공공 기능 테스트Home(): void
{
$this->mock(HelperService::class, function (MockInterface $mock) {
$mock->shouldReceive('incrementAndGetCounter')->andReturn(5);
});
$response = $this->getJson('/', []);
$this->assertEquals($response['counter'], 5);
}
}
이제 테스트에서 실제 'HelperService'를 인스턴스화하는 대신, 그 객체의 모크를 사용할 수 있습니다! 이것은 코드의 일부를 분리하여 단위 테스트를 더 쉽게 수행하는 데 매우 유용합니다.
조롱에 관한 더 많은 정보는 여기에서 확인할 수 있습니다: https://laravel.com/docs/9.x/mocking#mocking-objects
의존성 주입을 언제 사용해야 하는지.
의존성 주입은 코드를 정리하는 데 훌륭한 도구이지만, 다른 도구들처럼 적절한 경우에만 사용해야 합니다. 개인적으로 저는 의존성 주입이 다음 두 가지 목적에 가장 유용하다고 생각합니다:
요청 수명주기 전반에 걸쳐 어떤 상태를 유지하고 수정해야 합니다(예: 미들웨어, 컨트롤러, 서비스 클래스 전반에 걸쳐 사용되는 싱글턴). 이것은 여러 컴포넌트/서비스 계층을 거치며 어떤 객체를 전달하는 것이 번거로워질 때 Redux에 넣는 이유와 비슷합니다.
추상화하고 모커 가능하게 만들고 싶은 논리나 상태를 원합니다(예: http clients, 인증 상태 등)
좋은 예가 authenticate state입니다. 보통 인증된 요청을 만들기 전에 여러 단계를 거쳐야 할 수 있습니다. 이 과정은 너무 복잡해서 테스트가 인증 논리와 너무 결합될 수 있습니다. 매번 API 요청 테스트에서 인증 토큰을 생성하는 방법을 다시 테스트해야 하나요?
의존성 주입을 사용하면 최소한의 데이터를 시드하고 인증 계층을 모킹할 수 있습니다. 결과적인 테스트 코드는 다음과 같을 수 있습니다:
클래스 AuthMiddleware
{
function __construct(AuthLogic $authLogic)
{
$this->authLogic = $authLogic;
}
함수 핸들(요청 $request, 클로저 $next)
{
만약 ($this->authLogic->authenticate($request)) {
반환 $next($request);
} 그렇지 않으면 {
abort(응답:HTTP_FORBIDDEN);
}
}
}
class HomeControllerTest가 TestCase 확장
{
function testHome()
{
$this->mock(AuthLogic::class, function (MockInterface $mock) {
$mock->shouldReceive('인증')->andReturn(참);
});
$response = $this->getJson('/', []);
$this->assertNotEquals($response->코드, 응답:HTTP_FORBIDDEN);
}
}
결론
이제 Laravel에서 의존성 주입(Dependency Injection)을 왜, 언제, 어떻게 사용하는지 알게 되었습니다. 이 기법은 다른 프레임워크에도 적용할 수 있습니다. Java의 Spring Boot, Ruby on Rails, Django 등 대부분의 웹 프레임워크가 이 기법을 구현할 수 있는 방법을 가지고 있습니다. 적절할 때 꼭 사용하세요! 과도하게 모조하지 마세요! 언젠가는 실제 테스트가 필요합니다!