[MVVM] 4.Dependency Injection과 Service Locator 패턴, DI와 MVVM을 활용한 테스트 가능한 구조 설계

최근 소프트웨어 개발에서 효율성과 유지보수성을 높이기 위해 다양한 디자인 패턴이 등장하고 있습니다. 그 중에서도 Dependency Injection (DI)Service Locator 패턴은 특히 인기 있는 방법론으로, WPF (Windows Presentation Foundation) 개발에 강력한 도구가 될 수 있습니다. 본 글에서는 이 두 패턴의 개념을 설명하고, MVVM (Model-View-ViewModel) 아키텍처와 결합하여 테스트 가능성을 높이는 방법에 대해 다루겠습니다.

1. Dependency Injection (DI)란?

Dependency Injection은 클래스가 필요로 하는 의존성을 외부에서 주입하는 디자인 패턴입니다. 이 방법은 객체의 생성과 생애 주기를 관리하는 책임을 줄여, 보다 유연하고 테스트 가능한 코드를 작성하는 데 도움을 줍니다.

1.1 실용적인 정의

DI는 기본적으로 클래스의 의존성을 명시적으로 생성자, 메서드 인자, 혹은 프로퍼티를 통해 외부에서 주입함으로써 객체 간의 의존성을 감소시킵니다. DI를 이용하면 코드의 결합도를 줄이고, 재사용성과 테스트 용이성을 높일 수 있습니다.

1.2 DI의 장점

  • 역할 분리: 객체 간의 의존성을 줄여서 테스트가 용이해집니다.
  • 모듈화: 각 서비스나 컴포넌트를 쉽게 교체하고 수정할 수 있습니다.
  • 유연성: 구성 파일에서 설정하여, 런타임 시에 의존성을 변경할 수 있습니다.

2. Service Locator 패턴

Service Locator 패턴은 애플리케이션의 전역 상태에서 객체를 조회하는 방법입니다. DI와는 정반대로, 의존성을 주입하는 것이 아니라 특정 서비스의 위치를 인식하고 해당 서비스를 검색하여 사용하는 방식입니다.

2.1 서비스 로케이터의 정의

Service Locator는 애플리케이션의 다른 구성 요소가 필요로 하는 서비스의 인스턴스를 관리하고 제공하는 객체입니다. 서비스 로케이터는 주로 전역적으로 사용되지만, 코드의 가독성을 해치고 테스트하기 어려운 경향이 있습니다.

2.2 서비스 로케이터의 장점

  • Quick Access: 필요한 서비스에 쉽게 접근할 수 있습니다.
  • Centralized Management: 모든 서비스 인스턴스를 중앙에서 관리할 수 있습니다.

2.3 단점

  • 테스트 불가: 서비스 로케이터를 사용하면, 테스트 시 Mock 객체를 활용하기가 어렵습니다.
  • 코드 가독성 저하: 의존성 주입 방식보다 코드가 복잡해질 수 있습니다.

3. DI와 MVVM을 활용한 테스트 가능한 구조 설계

MVVM은 WPF 애플리케이션의 아키텍처 패턴 중 하나로, UI와 비즈니스 로직을 분리하여 개발하는 방법입니다. 이와 DI를 결합하면, 테스트 가능한 구조를 쉽게 설계할 수 있습니다.

3.1 MVVM의 기본 개념

MVVM은 Model, View, ViewModel의 세 가지 구성 요소로 이루어져 있습니다. 각 요소의 역할은 다음과 같습니다:

  • Model: 애플리케이션의 데이터와 비즈니스 로직을 포함합니다.
  • View: 사용자 인터페이스(UI) 요소를 정의합니다.
  • ViewModel: Model과 View 간의 상호작용을 관리합니다.

3.2 DI와 MVVM의 결합

DI를 MVVM 패턴에서 활용하는 방법은 주로 ViewModel을 외부의 의존성에 주입함으로써 이루어집니다. 이렇게 하면 ViewModel이 직접 Dependency를 생성하는 것이 아니라, DI 컨테이너에 의해 주입받아 테스트할 수 있는 환경을 만듭니다.

3.3 예제 코드


public interface IDataService
{
    List<string> GetData();
}

public class DataService : IDataService
{
    public List<string> GetData()
    {
        // 실재 구현
    }
}

public class MainViewModel
{
    private readonly IDataService _dataService;

    public MainViewModel(IDataService dataService)
    {
        _dataService = dataService;
        Data = _dataService.GetData();
    }

    public List<string> Data { get; }
}

3.4 IoC 컨테이너 사용하기

IoC (Inversion of Control) 컨테이너는 DI를 구현하는 일반적인 방법입니다. C#에서 널리 사용되는 IoC 컨테이너로는 Autofac, Unity, Ninject 등이 있습니다. 다음은 Autofac을 사용하여 DI를 설정하는 예입니다.


var builder = new ContainerBuilder();
builder.RegisterType<DataService>().As<IDataService>();
builder.RegisterType<MainViewModel>();
var container = builder.Build();

var mainViewModel = container.Resolve<MainViewModel>();

4. 테스트 가능성

DI와 MVVM 패턴의 조합을 통해 테스트가 용이한 시스템을 구축할 수 있습니다. 주요 테스트 가능한 구조의 이점은 다음과 같습니다:

  • 모의(Mock) 객체를 통한 독립적인 테스트가 가능합니다.
  • 각 구성 요소의 결합도가 낮아 변경 시 영향을 최소화할 수 있습니다.

4.1 단위 테스트 예제

다음은 Moq 라이브러리를 사용하여 ViewModel에 대한 단위 테스트입니다.


using Moq;
using Xunit;

public class MainViewModelTests
{
    [Fact]
    public void LoadData_ShouldGetDataFromService()
    {
        // Arrange
        var mockDataService = new Mock<IDataService>();
        mockDataService.Setup(service => service.GetData()).Returns(new List<string> { "Test1", "Test2" });

        var viewModel = new MainViewModel(mockDataService.Object);

        // Act
        var data = viewModel.Data;

        // Assert
        Assert.Equal(2, data.Count);
        Assert.Contains("Test1", data);
        Assert.Contains("Test2", data);
    }
}

결론

Dependency Injection과 Service Locator 패턴은 각각 장단점이 있으며, 이를 MVVM 아키텍처와 결합함으로써 테스트 가능한 구조를 설계할 수 있습니다. DI를 통해 의존성을 관리하고, 서비스 로케이터의 단점을 보완하여 가독성과 테스트 용이성을 얻는 것이 가능합니다. WPF 개발 시 이러한 패턴을 잘 활용하면 코드의 유지보수성과 확장성을 크게 향상시킬 수 있습니다.