Test ON!

2023년 11월 23일 TIL

Today I Learned

날짜

2023년 11월 23일 목요일

내용

생각보다 빠르게 Task 하나를 완료했다. 태용님께 우리 회사의 일하는 방식, 우리 서비스의 구조에 대한 설명을 들었다.

이슈 해결하기

Logic 살펴보기

  1. 호출되는 곳

  2. Parameter

계획 세우기

문제 : 어드민 내에서 스토어 상태 처리 변경 미작동

원인 :

  • 리뷰의 status를 변경할 때, 관계가 있는 product를 조인함.
  • 하지만 어떤 리뷰들은 관련된 product가 없음.
  • 따라서 join문에서 반환되는 값이 없기 때문에 선택한 리뷰 레코드를 DB에서 찾아내지 못함.

해결방안 :

  • LEFT JOIN 으로 있을때때만 product를 출력하는 것.

1차 테스트(실패)

실패

  • 선택과 무관하게 현재 목록의 모든 review의 상태가 변경된다.

해결 과정

문제 상황

Action 클릭시 reviewIds 배열에 해당 페이지의 모든 리뷰 아이디가 들어간다.

원인

체크된 리뷰들을 변수로 사용하지 않음.

해결

제대로 작동하는 All 탭을 가져와서 똑같이 변경.

분석

왜 LEFT JOIN 이후에 발생하게 됐는가?

기존 JOIN에서도 Review ids가 모두 10개가 들어왔지만 JOIN 문이 잘못되어서 모두 탈락해버림. 그래서 작동을 안하였음. 하지만 JOIN 문이 제대로 작동한 이후에는 탈락을 안하기 때문에 10개 모두 영향을 받게 되었음.

2차 테스트(성공)

테스트 코드 관련 공부하기

테스트 코드?

  • 작성한 코드의 정확성을 확인하기 위해 작성하는 코드
  • 잦은 테스트를 통해 에러를 미리 감지하고 원인을 파악하여 디버깅 시간을 감소시킬 수 있다.
  • 개발 단계에선 단위(unit), 통합(integration), 기능 테스트 등 이 있다.
  • 단위 테스트만 통과한다고 전체가 잘 작동한다는 보장은 없다.
  • 통합 테스트만 두면, 어디가 문제인지 알 수 없어 의미가 없다.
  • 따라서 단위테스트 + 통합 테스트가 필요함
  • 당장은 테스트로 인해 시간이 지체될 수 있지만 전체적인 관점에서 볼때 버그나 에러로 인한 시간 낭비를 줄일 수 있다.
  • 각 파일에 대응하여 테스트를 위한 파일들을 생성하거나(같은 곳에, 혹은 테스트 파일끼리 따로 모아서)
  • 테스트할 메서드 바로 밑에 작성한다.

단위 테스트

  • 코드의 가장 작은 기능적 단위를 테스트하는 프로세스(함수 or 메서드)
  • 작은 기능적 단위를 작성
    • 코드 단위별 단위 테스트 작성
    • 소프트웨어 코드를 변경할 때마다 테스트 코드를 자동으로 실행’
  • 입력 및 캡처된 assert(boolean) 출력을 통해서만 코드 블록과 상호 작용할 수 있음.
  • 격리된 상태에서 실행되야 함 ⇒ 데이터베이스, 객체, 네트워크 통신 필요시 데이터 스텁을 사용
  • 같은 파일 내에 작성하거나 같은 디렉토리에 작성

    ### 단위 테스트 전략

    • 논리 검사 : 시스템의 올바른 계산, 주어진 입력의 올바른 경로
    • 경계 검사 : 주어진 입력에 대한 시스템의 반응, 극단적(edge case) or 잘못된 경우에 대한 반응
    • 오류 처리 : 입력에 오류가 있을 때 반응
    • 객체 지향 검사 : 코드 실행 후 영구 객체의 상태 변경시 올바른 업데이트

    ### 단위 테스트 예시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
      # 테스트 대상
      def add_two_numbers(x, y):
        
          return x + y
        
      # 해당 단위 테스트
      def test_add_positives():
        
          result = add_two_numbers(5, 40)
        
          assert result == 45
        
      def test_add_negatives():
        
          result = add_two_numbers(-4, -50)
        
          assert result == -54
        
      def test_add_mixed():
        
          result = add_two_numbers(5, -5)
        
          assert result == 0
    

이점

  • 효율적인 버그 발견 : 회귀 기반 버그 감지, 빠른 디버깅
  • 설명서 : 코드를 이해하기 위한 문서화의 한 형

방법

  • TDD : 요구사항을 확인하기 위한 테스트를 먼저 빌드한 뒤 코딩
  • 코드 블록 완성 후 : 코드 블록이 완성 된 후 단위 테스트를 개발하여 실행
  • DevOps : AWS CodeBuild에 테스트 자동화 설정

예외 상황

  • 상대적으로 덜 유용한 상황
  • 제한된 시간
  • UI/UX 애플리케이션
  • 레거시 코드베이스
  • 잦은 요구 사항 변화

지킬 것

  • 자동화할 것 : 브랜치 변경 사항 푸시 전, 배포 전 등 이벤트에서 트리거되도록!
  • assert는 1번만 작성할 것 : 하나의 결과만 확인하기 위해
  • 코드의 일부분만 따로 테스트할 것 : 정확한 오류 지점을 찾기 위한 번거로움이 없도록 하기 위해
  • AAA(Arrange, Act, Assert)에 맞게 짤 것
    • 모든 변수와 결과들을 나열(Arrange)
    • 결과를 바로 고치도록 행동(Act)
    • 가설이 정확한지 확인(Assert)
  • 테스트 길이는 짧게 유지할 것
  • 로직은 간단하게 만들것
  • 최선의 경우(Happy Path) 부터 테스트할 것
  • 극단적인 경우(Edge Cases)도 테스트할 것(예를 들어, 변수의 가장 끝 숫자)
  • 버그를 고치기 전에 가장 먼저 테스트부터 작성할 것
  • 테스트 자체가 무겁지 않게 만들 것
  • 테스트는 각각 독립적으로 만들 것
  • 무조건 성공 혹은 실패로 나타나도록 할 것

각 프레임워크 내 테스트

Angular

  • Karma, spec.ts 로 테스트
  • 코드 커버리지 테스트 : 유닛 테스트가 테스트 하는 범위 확인, 규칙 생성 가능
  • 서비스, 컴포넌트, 파이프 등 단위별 테스트 기능

TestClient

  • FastAPI에서 사용.
  • pytest 로 실행

Pytest

  • 테스트 파일, 코드 실행하는 프레임워크
  • 테스트 파일, 함수는 무조건 test_로 시작해야 함
  • 특정 디렉토리, 파일 만 실행할 수도 있음
  • mock을 통해 환경변수값을 임의로 설정할 수있음.
  • 명령어 옵션
    • $ pytest {테스트파일명}.py -k {테스트함수명} : 특정 테스트 함수만 실행
    • $ pytest -v : 테스트함수 각각의 실행 결과도 출력

현재 우리 서비스에 이미 shop, review에 test 디렉토리가 있길래 실행해봄

위와 같은 오류난다. 아마도

  • pytest.ini 설정하기 ⇒ 테스트를 위한 환경변수 모두 갖추어야 해결된다.
  • 결국 테스트를 위헤선 테스트에 지장이 없도록 전용 환경변수를 설정해야 하는것 같다.
  • 어떤 메서드에만 테스트 코드를 작성할 때도 환경 변수를 다 갖춰야할까?

단일 메서드를 위한 유닛 테스트 만들어보기

  • 다른 변수의 영향을 안받기 위해 src 디렉토리내에 새로 test.py하나 만들어봤다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      from fastapi import FastAPI
      from fastapi.testclient import TestClient
        
      app = FastAPI()
        
      @app.get("/testjunsu")
      async def test_cal():
          return { "test": "success"}
        
      client = TestClient(app)
        
      def test_test_cal():
          response = client.get("/testjunsu")
          assert response.status_code == 200
          assert response.json() == {"test": "success"}
    
  • 결과 : 성공

    ======== 1 passed, 1 skipped, 53 warnings in 0.55s ========

  • 이미 작성되있는 파일들에 테스트 코드를 추가했을땐 여러 환경변수와 관련된 파일들이 오류를 일으켜 해당 테스트가 작동하지 않는 것 같다.
  • 이미 작성된 코드들의 테스트를 위해선 테스트용 환경변수 설정이 중요할 것 같다.
  • mock을 써도 되지만 pytest.ini 로 설정하는것이 바람직한 방향이라고 한다.

회고

클린코드 책들도 읽고, 기존에 좋은 개발자가 되기 위한 여러 아티클들을 읽다보니 테스트 코드의 중요성쯤은 알고 있었지만 생각보다 되게 어렵다.. 괜히 사람들이 안하는게 아니구나. 하지만 어렵고 귀찮은 만큼 가치있으니 공부좀 열심히 해봐야겠다.