[ASP.NET Core] 테스트 코드(단위 테스트) 작성하기

Arthur·2024년 3월 6일
0
post-thumbnail

테스트 코드를 작성하는 이유


  1. 소프트웨어가 어떻게 작동하는지 이해하는데 도움을 준다.
  2. 소프트웨어 수정 시 예상치 못한 부작용을 방지
  3. 개발자 간의 협업을 원활하게 하고, 유지 보수하는 데 필요한 문서화 작업을 줄일 수 있다.

위의 내용은 통상적으로 테스트 코드를 작성하는 이유에 대해서 나오는 내용입니다.


1. 소프트웨어가 어떻게 작동하는지 이해하는데 도움을 준다.

구현한 부분을 테스트 코드를 작성할 때도 작동 원리를 이해하는데 도움이 됩니다.
그리고 작성되어 있는 테스트 코드를 통해 코드의 구조나 의도를 파악할 수 있습니다.


using System;
using Xunit;

public class CalculatorTests
{
    [Fact(Display = "계산기 덧셈")]
    public void Add_ReturnsCorrectSum()
    {
        // Arrange
        Calculator calculator = new Calculator();

        // Act
        int result = calculator.Add(3, 5);

        // Assert
        Assert.Equal(8, result);
    }

    [Fact(Display = "계산기 뺄셈")]
    public void Subtract_ReturnsCorrectDifference()
    {
        // Arrange
        Calculator calculator = new Calculator();

        // Act
        int result = calculator.Subtract(10, 4);

        // Assert
        Assert.Equal(6, result);
    }

    [Fact(Display = "계산기 곱셈")]
    public void Multiply_ReturnsCorrectProduct()
    {
        // Arrange
        Calculator calculator = new Calculator();

        // Act
        int result = calculator.Multiply(2, 7);

        // Assert
        Assert.Equal(14, result);
    }
}

위 예시 코드는 계산기 로직을 테스트 하는 코드입니다.
위와 같이 계산기 클래스의 코드가 없어도 테스트 코드로 의도를 파악할 수 있습니다.
그리고 로직이 담겨있는 코드가 길고 복잡할수록 테스트 코드를 활용하면 좋습니다.

테스트 코드는 필요한 객체 및 데이터(Arrange), 실질적으로 행동하는 객체(Act), 검증(Assert)의 단계가 나뉘어져 있습니다.


2. 소프트웨어 수정 시 예상치 못한 부작용 방지

이전에 작성한 로직은 테스트 케이스를 통과한 것들입니다.
안정성이 있던 코드를 수정 시 다시 테스트 케이스를 돌려보면서 버그를 방지할 수 있습니다.


3. 개발자 간의 협업 원활, 유지 보수 시 필요한 문서화 단축

이 부분은 작동 중인 소프트웨어를 이해하는데 테스트 코드가 도움을 주기 때문입니다.
로직을 이해하는데 도움을 주기 때문에 협업도 원활해지고 유지 보수에 필요한 문서를 줄일 수 있습니다.



ASP.NET Core에서의 테스트


제가 ASP.NET Core에서 테스트 코드를 작성하는 방식을 순서대로 나열하면 아래와 같습니다.

  1. 테스트 대상이 되는 프로젝트의 코드 작성
  2. 테스트 프로젝트 생성
  3. 테스트 코드 작성
  4. 테스트 실행

테스트 대상이 되는 ASP.NET Core 프로젝트 생성 및 코드 작성은 과정에서 생략하겠습니다.

2. 테스트 프로젝트 생성

  • 파일 -> 새로만들기 -> 프로젝트

프로젝트 생성을 위해 '프로젝트' 탭을 클릭합니다.


xUnit을 검색해서 xUnit 테스트 프로젝트를 클릭합니다.
다음을 누른 후 테스트 프로젝트를 이름을 입력하고 생성합니다.


3. 테스트 코드 작성

테스트 클래스에 테스트 코드를 작성합니다.

public class AccountServiceTest
{
    private readonly Mock<IAccountRepository> _mockRepository;
    private readonly Mock<PasswordEncryptor> _mockPasswordEncryptor;
    private readonly Mock<IMapper> _mockImapper;
    private readonly AccountService _accountService;

    public AccountServiceTest()
    {
        _mockRepository = new Mock<IAccountRepository>();
        _mockPasswordEncryptor = new Mock<PasswordEncryptor>();
        _mockImapper = new Mock<IMapper>();

        _accountService = new AccountService(_mockPasswordEncryptor.Object, _mockRepository.Object, _mockImapper.Object);
    }

    [Fact(DisplayName = "유저 회원가입")]
    public void AddAcount()
    {
        // Arrange
        AccountSignupReqDto reqDto = new()
        {
            AccountName = "test1234",
            Password = "test1234",
            ConfirmPassword = "test1234",
            Nickname = "testNick",
        };

        Account account = null;

        _mockRepository.Setup(r => r.AddAccount(It.IsAny<Account>()))
            .Callback<Account>(x => account = x);

        // Act
        _accountService.AddAccount(reqDto);
        _mockRepository.Verify(x => x.AddAccount(It.IsAny<Account>()), Times.Once);

        // Assert
        Assert.Equal(reqDto.AccountName, account.AccountName);
        Assert.Equal(reqDto.Nickname, account.Nickname);

        bool passwordMatch = _mockPasswordEncryptor.Object.IsmatchPassword(reqDto.Password, account.Password);
        Assert.True(passwordMatch);
    }
}

위 예시는 제가 작성한 테스트 코드입니다.
xUnit 테스트 프로젝트에 맞게 작성한 코드입니다.


4. 테스트 실행

테스트를 실행하기 전에 테스트 탐색기를 먼저 켜줍니다.

  • 보기 -> 테스트 탐색기

테스트 탐색기에서 왼쪽 상단에 있는 초록색 화살표를 누르시면 테스트가 실행됩니다.
ASP.NET Core에서 간단한 테스트 코드를 작성해 실행하는 과정은 간단합니다.

여기서 구현한 로직이 복잡해지거나 외부 의존성이 높은 경우에는 코드 작성이 힘들어 질 수 있습니다.

저는 모의 객체를 생성하는 프레임워크인 Moq를 사용해서 의존성을 떨어트렸습니다.



Moq 프레임워크로 모의 객체 생성


Moq 프레임워크는 모의 객체를 시뮬레이션하는 기능을 제공합니다. 예를 들어 실제로 데이터베이스와 통신하지 않고도 데이터베이스에 대한 호출을 테스트할 수 있습니다.

Moq에서 모의 객체는 아래 예시와 같이 작성할 수 있습니다.

private readonly Mock<IAccountRepository> _mockRepository;
private readonly Mock<PasswordEncryptor> _mockPasswordEncryptor;
private readonly Mock<IMapper> _mockImapper;
private readonly AccountService _accountService;

public AccountServiceTest()
{
    _mockRepository = new Mock<IAccountRepository>();
    _mockPasswordEncryptor = new Mock<PasswordEncryptor>();
    _mockImapper = new Mock<IMapper>();

    _accountService = new AccountService(_mockPasswordEncryptor.Object, _mockRepository.Object, _mockImapper.Object);
}

데이터베이스에 접근하는 Repository 객체를 멤버 변수 Mock(모의) 객체를 생성했습니다.
이 외에도 외부 유틸리티인 PasswordEncryptor(패스워드 암호화)클래스도 Mocking 했습니다.

이렇게 Mock 객체를 생성하면 테스트를 실행해도 데이터베이스에 데이터가 저장되지 않습니다.

그리고 외부 의존성이 있는 부분의 객체를 직접 가져오지 않고 모의 객체를 생성해 사용할 수 있습니다.



작성하면서 느낀점


테스트 코드의 작성은 필수는 아닙니다.
회사에서도 테스트 코드를 아예 작성하지 않는 곳도 있다고 합니다.

하지만 작성을 하면서 얻는 것들이 훨씬 많다는 것을 알 수 있습니다.
Rest API 테스트를 위해 직접 어플리케이션을 실행할 필요도 없어서 시간을 단축 시켜줍니다.

그리고 CI/CD를 구축할 때 테스트 코드를 통해 안정성을 일부분 검증받은 상태에서 배포를 할 수 있습니다.
테스트 코드의 중요도가 높은 회사는 모든 테스트가 통과되지 않으면 배포를 하지 못하도록 합니다.



참고 자료


  • C# .NET - xUnit을 이용하여 테스트의 종류와 구성 방법을 알아보자 => 링크
  • 단위 테스트: Moq Framework => 링크
  • NUnit vs. XUnit vs. MSTest: Unit Testing Frameworks => 링크
  • Why We ALL Use xUnit over NUnit or MSTest? => 링크
  • 요즘IT - 테스트 코드는 왜 만들까? => 링크
  • Moq Github => 링크
profile
기술에 대한 고민과 배운 것을 회고하는 게임 서버 개발자의 블로그입니다.

0개의 댓글