MSA 환경 내 TDD

인턴일지

목록 보기
7/7
post-thumbnail

테스트 종류

1. 단위(유닛) 테스트

  • 서비스 내부의 개별 컴포넌트, 함수 및 메소드 수준에서 수행된다.
  • 각 단위가 독립적으로 동작을 제대로 수행하는지 검증한다.
    • 이때, 외부 의존성을 최소화하는 작업이 필요하다.
    • 오류를 사전에 방지하기 위한 존재 ⇒ 로직이 조건을 만족하는지 확인하기 위해서 여러 시나리오를 검증한다.
  • MSA 환경 내에서 각 서비스가 개별적으로 동작 필요 ⇒ 내부 로직 작동 확인 여부가 중요하다.
  • 기능 변경이 필요할 때 예상치 못한 사이드 이펙트를 줄이는 효과가 있다.
  • JUnit, Jest, Pytest 등과 같은 테스트 프레임 사용한다.
검증 시나리오가 빈번하게 광고주의 요청으로 인해 변경될 때가 많았다. 
그때마다 유닛 테스트를 활용하여 로직 수정으로 인한 side effect가 없었는지 확인한 후 pr을 올리는 습관을 들였다. 

pytest

  • Python 환경에서 제공해주는 테스트 라이브러리: assert 구문으로 단위 테스트를 식별하고 결과 확인이 가능하다.
@pytest.mark.parametrize(
    "box_coords, expected",
    [
        ([(0, 0), (10, 0), (10, 5), (0, 5)], (0, 0, 10, 5)),
        ([(0.9, 1.2), (2.8, 8.7), (2.7, 3.4), (1.1, 0.5)], (0, 0, 2, 8)),
    ],
)
def test_get_bbox_bounds_normal(box_coords, expected):
    assert get_bbox_bounds(box_coords) == expected


@pytest.mark.parametrize(
    "box_coords, expected_exception",
    [
        # 1) 빈 리스트: min()/max()가 ValueError
        ([], ValueError),
        # 2) None 등 비서술(인덱싱 불가): pt[0]에서 TypeError
        ([None], TypeError),
    ],
)
def test_get_bbox_bounds_invalid(box_coords, expected_exception):
    with pytest.raises(expected_exception):
        get_bbox_bounds(box_coords)

간단하게 설명을 덧붙이자면, pytest에서 제공하는 parametrize에 추측되는 시나리오를 나열한다.
테스트 코드 함수 내부에 예상되는 결과물을 assert로 비교해보면 유닛 테스트 코드 작성 끝!

[예외 발생 메세지 검사]
pytest.raises는 메서드 형태나 컨텍스트 관리자 형태로 호출하여, 예외 메세지 검사가 가능하다.
⇒ match 구문을 사용하기도 한다.

[테스트 시나리오 파라미터화]
pytest.mark.parameterize 데코레이터를 사용하면 테스트케이스를 파라미터화 할 수 있다.
특정 테스트 함수의 데이터 집합을 사용할 때는 pytest.mark.parameterize를 주로 사용했다.

[fixture]
공통으로 사용되는 객체를 제공하는 메커니즘으로 여러 객체를 생성하거나 데이터를 노출하는 것 외에도 직접 호출되지 않는 함수를 수정하거나 사용될 객체를 미리 설정하는 등의 사전 조건 설정에 사용된다.

텍스트 비교할 때가 있었는데, 기존 텍스트를 fixture로 선언해서 여러번 정의할 필요없게 테스트 코드를 구현했다.

[mocking]
만약에, 내부 함수가 들어있다면, mocking 처리로 단위 테스트가 가능케 만들어줬다.

# unittest.mock.patch 컨텍스트 매니저
with patch("your_module.your_function.requests.get") as mock_get_request:

# pytest fixture
monkeypatch  
  1. monkeypatch (pytest 제공 fixture)
    • 특징
      • pytest의 내장 fixture → 인자로 함수에 주입받아서 사용한다.
      • 주로 속성 교체, 환경 변수 변경, 모듈/함수 치환에 사용한다.
      • 테스트 함수가 끝나면 자동으로 원래 상태로 복구된다. (clean-up 신경 쓸 필요 없음)
      • patching 범위: 해당 테스트 함수 스코프
    • 예시
      # app.py import os  
      def get_env_var(key):     
         return os.environ.get(key, "default")  
         
      # test_app.py 
      def test_get_env_var(monkeypatch):     
      monkeypatch.setenv("API_KEY", "test_key")     
      assert get_env_var("API_KEY") == "test_key"
  2. with patch (unittest.mock.patch)
    • 특징
      • Python 표준 라이브러리 unittest.mock의 기능.
      • 컨텍스트 매니저(with 구문) 또는 데코레이터로 사용된다.
      • 지정한 객체/함수를 임시로 대체(mock)MagicMock 또는 원하는 값으로 설정 가능.
      • patching 범위: with 블록 또는 데코레이터 적용된 함수 실행 구간.
    • 예시
      from unittest.mock import patch 
      import app 
       
      def test_get_env_var():     
         with patch("app.os.environ.get", return_value="mocked_key"):         
            assert app.get_env_var("API_KEY") == "mocked_key"

2. 통합 테스트

  • 서비스 간의 상호작용을 검증하는 테스트다.
  • MSA 특성 상 여러 서비스가 협력하여 동작하는 경우가 많은데, 서비스 간의 통신이 원활한지 확인해주기 위한 작업이다.
    • API 호출, 메시지 큐를 통한 비동기 통신, 데이터 일관성 유지와 같은 요소들 검증한다.
함수 리팩토링 당시 많은 도움을 줬던 테스트 방식이다. 
회사 특성 상 여러 인턴들이 자기들만의 생각으로 하나의 서비스를 완성시키다 보니, 함수의 복잡성이 리팩토링 이전은 높았다. 
통합 테스트를 작성해보면서, 함수를 정리해보는 시간을 가졌고 이를 토대로 리팩토링을 진행했다.

3. E2E 테스트 (End-to-End)

  • 실제 사용자가 서비스를 사용하는 것과 비슷한 환경을 구축한 후에 해당 동작을 테스트 해보는 것
  • 그러나, 환경 및 시나리오 구축에 많은 비용이 들어 꼭 필요한 경우에만 실행하는 것이 일반적이다.
사내 시스템과 관련되다 보니 인프라를 맘대로 구축하거나 변경할 수 없는 환경이어서 진행해보진 못했다.

TDD(Test-Driven-Development) 고찰

서비스를 출시할 때 기능 개발과 함께 테스트도 병행하는 작업이 꽤나 중요하다고 생각이 들었다.

학교 프로젝트 진행 시에는 코드 작성을 한 후에 테스트 코드 작성의 흐름을 따랐지만, TDD의 핵심은 기존에 테스트 코드를 작성한 후에 실제 코드를 작성하는 것이 맞다고 한다.

이렇게 하면, 개발자가 기능 구현 시 생각 정리를 할 수 있고 명확한 기능의 흐름을 구상할 수 있기에 추천하는 방식같다.

TDD는 크게 Red-Green-Blue 3가지 단계를 거친다고 한다:
1. Red: 실제 구현을 하기 전에, 먼저 실패하는 테스트 코드를 작성한다. 그 후 테스트를 실행한다. 실제 코드가 작성되지 않았기에 테스트 코드는 당연히 실패한다.
2. Green: 테스트를 통과하기 위해 가장 간단한 형태로 코드를 작성한다. 그 후 테스트를 실행한다. 테스트는 실제 구현이 되었기에 통과한다.
3. Blue: Green 단계의 코드를 더 좋은 형태로 리팩토링한다. 이 과정에서 지속적으로 테스트를 실행해서 테스트가 통과하는지 확인한다.

이러한 형태로 개발하게 되면 여러 가지 장점이 있다.
1. 코드의 동작이 명확해진다.
2. 구현을 잘못했을 경우 바로 확인이 가능하다.
3. 코드 작성 과정에서 확신을 얻을 수 있다.

서비스와 같이 사용자가 있는 환경에서는 새로운 기능 개발 시에 사이드 이펙트를 줄일 수 있는 좋은 개발 방식이라고 생각된다.

앞으로 프로젝트를 진행할 때 테스트 코드 작성을 습관화 하는 것이 중요할 것 같다고 생각이 들었다.


참고 자료:
https://sunrise-min.tistory.com/entry/Pytest

https://www.msap.ai/docs/msa-expert-from-concepts-to-practice/implementing-msa/msa-service-development-deployment/msa-service-development-test/unit-testing-integration-testing/

profile
밝은 미래 FE 개발자의 기록

0개의 댓글