탄생배경

문제

복잡한 CI/CD 환경

  • 코드는  GitHub에서 관리, 외부 도구로 CI/CD 구축
    • Jenkins, Travis CI, CircleCI 

비효율성 발생

  • 여러 외부 도구 간 연동 문제
  • 복잡한 설정 필요
  • 관리의 불편함

탄생

문제 해결을 위한 GitHub Actions를 발표

GitHub Actions

  • GitHub 내에서 CI/CD 구현 : 코드 변경 사항에 따라 자동으로 빌드, 테스트, 배포 수행
  • 이벤트 기반(event-driven) 워크플로우 : 푸시(push), 풀 리퀘스트(pull request), 이슈(issue) 생성 등 다양한 이벤트에 반응

요약

GitHub Actions는 개발자들이 GitHub 플랫폼 내에서 직접 워크플로우를 자동화하고, CI/CD(지속적인 통합 및 배포) 프로세스를 간소화할 수 있는 필요성에서 탄생했습니다.

  • 개발자들의 필요성 충족
  • 효율적인 개발 프로세스 구축

사용하는 이유

1. 플랫폼 내 통합된 CI/CD 환경

  • 원스톱 솔루션: GitHub 내에서 직접 CI/CD 파이프라인을 구축
  • 심리스한 통합: 리포지토리와 깊게 연동되어 코드 변경 사항에 즉각적으로 반응.
    • seamless integration : 시스템이나 도구 간에 경계나 단절 없이 매끄럽게 연동되는 것을 의미

2. 자동화된 워크플로우 구축

  • 이벤트 기반 실행: 푸시, 풀 리퀘스트, 이슈 생성 등 다양한 이벤트에 따라 자동으로 작업이 실행
  • 유연한 설정: YAML 파일을 통해 원하는 워크플로우를 손쉽게 정의하고 커스터마이징 가능

3. 사용 편의성

  • 쉬운 설정: 복잡한 스크립트 없이도 간단한 설정만으로 자동화된 프로세스 구축
  • 직관적인 인터페이스: GitHub의 친숙한 UI를 통해 워크플로우를 모니터링하고 관리 가능

4. 커뮤니티 지원 및 확장성

  • 액션 마켓플레이스: GitHub Marketplace에서 수많은 사전 구축된 액션을 이용하여 개발 시간을 절약
  • 오픈소스 생태계: 커뮤니티에서 제공하는 액션을 활용하거나, 자신만의 액션을 만들어 공유 가능

5. 다양한 플랫폼 및 언어 지원

  • 크로스 플랫폼 지원: Linux, macOS, Windows 등 다양한 운영체제에서 워크플로우를 실행
  • 다양한 언어 호환: JavaScript, Python, Go 등 다양한 프로그래밍 언어를 지원하여 범용성 향상

6. 비용 효율성

  • 무료 이용 가능: 공개 리포지토리의 경우 무료로 이용, 제한된 범위 내에서 사설 리포지토리도 무료로 사용 가능
  • 유연한 결제 옵션: 필요에 따라 추가 러너나 기능을 구매하여 사용할 수 있어 비용 관리가 용이

7. 보안 및 권한 관리

  • 시크릿 관리: API 키나 토큰과 같은 민감한 정보를 안전하게 저장하고 사용
  • 세분화된 권한 설정: 워크플로우에 대한 접근 권한을 세밀하게 제어하여 보안 강화

8. 효율적인 협업 지원

  • 팀 생산성 향상: 자동화된 테스트와 배포를 통해 팀원 간의 협업 원활
  • 코드 품질 개선: 지속적인 통합을 통해 코드의 안정성과 품질 향상

9. 모니터링 및 피드백

  • 실시간 로그 제공: 워크플로우 실행 중 발생하는 로그를 실시간으로 확인
  • 알림 기능: 워크플로우의 성공 여부나 오류 발생 시 알림을 받아 빠르게 대응 가능

장단점

장점:

  • 깊은 통합성: GitHub 리포지토리와 직접 통합되어 있어 코드 변경 사항에 즉각적으로 반응하는 워크플로우 설정 가능
  • 사용 편의성: YAML 형식의 설정 파일을 통해 워크플로우를 손쉽게 작성 및 관리 가능
  • 커뮤니티 지원: GitHub Marketplace를 통해 다양한 사전 구축된 액션을 이용할 수 있어 개발 시간을 절약
  • 유연성: 다양한 프로그래밍 언어와 플랫폼을 지원하며, 병렬 작업 및 매트릭스 빌드를 통해 복잡한 테스트 시나리오를 구현 가능
  • 비용 효율성: 공개 리포지토리의 경우 무료로 이용 가능하며, 필요한 경우에만 추가 리소스를 구매

단점:

  • 벤더 종속성: GitHub 플랫폼에 종속되므로 다른 CI/CD 도구로의 이식성이 낮음
  • 제한된 리소스: 무료 계정이나 제한된 플랜의 경우 사용 가능한 러너나 실행 시간이 제한됨
  • 복잡한 설정: 복잡한 워크플로우나 고급 기능을 구현하려면 YAML 설정에 대한 깊은 이해가 필요
  • 비용 문제: 사설 리포지토리나 대규모 프로젝트의 경우 추가 비용이 발생

목차

엔터프라이즈 vs. 웹 서비스

엔터프라이즈 vs 웹 서비스

  엔터프라이즈 웹 서비스
트래픽 그다지 많지 않음 굉장히 많은(전 세계)
성장성 적당한 정도 (한정된 성장률) 폭발적(100%, 200%, 300%)
신뢰성 사수(목숨을 걸고 지키다) 99%
트랜잭션 많이 사용 그다지 많이 사용하지 않음
사용예시 은행의 금융 거래 관리 시스템, 대형 리테일사의 재고 관리 시스템 등, 조직 내 여러 부서가 함께 사용할 수 있도록 설계된 서비스 외부 애플리케이션이 날씨 정보, 환율 정보 등을 받아볼 수 있는 공개 API, 결제 처리를 위한 결제 API

 

신뢰성

엔터프라이즈

  • 장애가 발생해서 데이터가 없어지거나 하면 실제로 돈이 사라지기도 한다.
  • 만일 장애가 발생되면 피해자로부터 손해배상 청구를 받게 되거나 구해야하는 사람 목숨을 구하지 못하는 등의 사태도 발생할 수 있다.

웹 서비스

  • 높은 레벨의 신뢰성이 요구 되지 않는다.

웹 서비스의 인프라

웹 서비스의 인프라에서 중요시되는 것

  1. 저비용 고효율 - 100% 신뢰성 목표로 하지 않고 비용을 낮추어 효율을 높이는 방향
  2. 확장성이나 응답성 - 서비스의 성장속도를 모를때 장래를 위한 확장성, 사용자 경험(UX)를 위한 서비스의 응답성을 위한 설계
  3. 유연성 - 서비스 사양이 바뀌는 경우에 유연하게 대응할 수있는 인프라를 위한 설계
  4. 개발속도를 중시한 인프라 - 서비스에 대해 기동성 있게 리소스를 제공
    • 앱 배포를 가능한 한 간편하게, 또한 배포할 때 마침 처리 중인 요청에 영향이 없도록 인프라 구성
    • 필요한 서버를 즉시 추가할수 있도록 인프라 구성
    • 배포한 코드에 문제가 발견됐을 때에 곧바로 이전 상태로 돌아갈 수 있도록 인프라 구성

 

클라우드 vs.  자체 구축 인프라

클라우드의 장단점

클라우드 컴퓨팅?

  • 인터넷을 통해 원격으로 컴퓨팅 자원 및 서비스를 제공하는 컴퓨팅 기술 -Samsung SDS-
  • IT 리소스를 인터넷을 통해 온디맨드로 제공하고 사용한 만큼만 비용을 지불하는 것 - Amazon Web Service-
  • 컴퓨팅 리소스(스토리지 및 인프라)를 인터넷을 통해 서비스로 사용할 수 있는 주문형 서비스 - Google Cloud -
  • 인터넷(“클라우드”)을 통해 서버, 스토리지, 데이터베이스, 네트워킹, 소프트웨어, 분석, 인텔리전스 등의 컴퓨팅 서비스를 제공하는 것 - Microsoft Azure - 

장점

  • 확장의 유연성

단점

  • 획일적인 호스트 사양
  • 때때로 멈춤 현상 발생

자체 구축 인프라의 장점

장점

  • 서버 하드웨어 구성을 유연하게 구성할 수 있다. 
  • 서비스로부터의 요청에 유연하게 대응할 수 있다. 
  • 병목현상을 제어할 수 있다.

자체 구축 인프라의 기술 모델

  • 수직통합 모델 - 물리적 계층부터 서비스 설계까지 한 기업에서 구축하는 모델 ( Amazon, Google)
  • 수평분산 모델 - 각 계층마다 다른 기업이 제공하는 것으로 각각이 모여 전체 시스템이 구축되는 모델

하테나에서 클라우드 서비스 사용 예시

 

서비스 활용 현황

  • 현재 활용 중인 서비스: AWS (Amazon Web Services)
  • 주요 서비스: Amazon CloudFront
    • 용도: 미디어 파일 전송을 위한 CDN (Content Delivery Network)
  • 자체 구축 인프라 중심 운영: 애플리케이션 및 DB의 클라우드 저장소 본격 도입은 보류

 

클라우드 컴퓨팅의 적합성

  • 적합한 경우: 소규모 서비스, 트라이얼 용도
  • 혜택: 비용 효율성, 유연한 확장성
  • 한계: 대규모 시스템의 경우 확장성 문제 발생 가능

Amazon EC2의 확장성 이슈

  • EC2 확장성: 대규모 시스템 지원에 한계가 존재할 수 있음
    • 대규모 서비스 예시: Facebook, Google 수준의 시스템
  • 소규모 서비스에는 EC2가 적합하지만, 대규모 서비스는 자체 구축이 더 유리

 

목차

 

테스트 우선하기

많은 개발자들은 빨리 구현해야 한다는 압박에 코드를 충분히 테스트하지 않고 기능을 구현한다.

 

압박 → 스트레스 테스트 안함 

 

시간이 흐르면

  • 코드가 복잡해짐( 가독성 저하)
  • 소프트웨어 품질 저하
  • QA 를 거치면서 버그 발생 
  • 버그 수정으로인한 버그 발생

TDD 를 적용!!

 

이점

  • 회귀테스트로 사용 할 수 있다.
    • 코드를 수정하거나 추가할 때 앞서 작성한 테스트 코드를 사용하여 다른 기능에 문제가 없는비 바로 확인
    • 변경한 코드로 인해 소프트웨어가 비정상적으로 동작하는 것을 사전에 막아줌
  • 버그 수정도 쉬워진다.
    • 버그가 발생하는 상황에 대한 테스트 코드를 추가하고 이를 통과시키면된다.
    • 버그를 고치는 과정에서 발생할 수 있는 새로운 버그를 놓치지 않을까 걱정할 필요가 없어진다.

TDD 전파하기

TDD 가 익숙해지면 오는 효과

  • 결함 감소
  • 스트레스 감소
  • 빠른 피드백

먼저 TDD 에 익숙해 지기위해서 "개인 프로젝트" 를 통해 TDD 연마 필수

 

레거시 코드에 대한 테스트 추가 방법

  • 레거시 코드에 대한 테스트를 많이 해보자
    • 어떻게 테스트 코드를 만들어야 할지 감이 잡힌다.
  • 테스트 코드를 만들기 힘든 부분은 일부 코드를 리팩토링해서 구조 변경
    • 테스트 하고 싶은 일부 코드부분을 분리하여 별도 클래스로 구현 : 범위가 작을수록  수월
    • 분리하려 만든 코드를  테스트 코드로 작성 : 점진적으로 코드를 분리 및 테스트 작성

TDD와 개발 시간

전체 개발 시간을 줄이려면 코딩 시간뿐만 아니라 테스트 시간과 디버깅 시간을 줄여야 한다.

  • 테스트 시간을 줄이는 방법 = 테스트 자동화 ▶ TDD 를 활용한 테스트 자동화
  • 디버깅 시간을 줄이는 방법  = 빠른 버그 발견 ▶ TDD 를 활용하여 초기에 버그 발견
  • 코딩 시간을 줄이는 방법 = 리팩토링을 통한 코드 구조와 가독성 개성 ▶ TDD 를 하면서 리팩토링
    • 리팩토링은 코드의 구조와 가독성을 개선한다. 즉, 미래에 코드 추가나 수정을 쉽게 해준다. 이는 곧 미래의 코딩 시간을 줄여준다.

# 하나의 기능을 구현할 때 테스트를 작성하여 통과할 때까지 구현을 하면 개별 요소에 대한 개발 시간을 단축 할 수 있다. 또한 브라우저를 실행해서 기능을 확인하는 하고 수정하는 과정도 줄일 수 있다. 

목차

테스트가 어려운 코드

❏ 하드 코딩된 경로

...
Path path = Paths.get("D:\\data\\pay\\cpoo1.csv");
..

   - 완전하게 일치하는 경로에 파일이 없으면 테스트 불가능, 맥OS 나 리눅스 를 사용하는 개발자 역시 테스트 불가능

 

테스트를 어렵게 하는 하드코딩 예 : 파일의 경로, IP 주소, 포트 번호

 의존 객체를 직접 생성

public class PaySync {
  //의존 대상을 직접 생성                 
  private PayInfoDao payInfoDao = new PayInfoDao();

  public void sync() throws IOException{
    ...
    payInfos.forEach(pi -> payInfoDao.insert(pi);
  }
}

 

이 코드를 테스트하려면 PayInfoDao가 올바르게 동작하는데 필요한 모든 환경을 구성해야 한다.

DB를 준비해야 하고 필요한 테이블도 만들어야 한다

 

테스트를 실행하면 데이터가 DB에 추가되므로 같은 테스트를 다시 실행하기 전에 기존에 들어간 데이터를 삭제해야 한다.

그렇지 않으면 중복 데이터로 인해 데이터 갑입에 실패하게 된다.

 정적 메서드 사용

클래스의 인스턴스가 아니라 클래스에 직접 연결되어 있기 때문에, 일반적인 의존성 주입이나 모킹이 어렵다.

 

테스트할 때 특정 메서드의 행동을 모의로 대체하거나 변경하고 싶다면,

    인스턴스 메서드의 경우 해당 객체를 모킹가능,

    정적 메서드의 경우 그럴 수 없어 실제 메서드를 호출

이로 인해 테스트가 복잡해지고, 테스트 환경과 실제 환경 사이의 격리가 어려워진다.

 

 가능하다면 정적 메서드 대신 인스턴스 메서드를 사용, 필요한 의존성을 생성자나 세터를 통해 주입하는 것이 좋다.

 실행 시점에 따라 달라지는 결과

현재 시간이나 날짜를 기준으로 사용자의 구독상태를 계산하는 경우

Random 을 이용해서 임의 값을 사용하는 코드

→ 시점에 따라 테스트 결과가 달라지면 믿을 수 없는 테스트 코드가 된다.

 역할이 섞여 있는 코드

여러 역할이 섞여 있는 코드는 특정 기능만 테스트하기가 쉽지 않다.

 그 외 테스트가 어려운 코드

  • 메서드 중간에 소켓 통신 코드가 포함되어 있다.
  • 콘솔에서 입력을 받거나 결과를 콘솔에 출력한다.
  • 테스트 대상이 사용하는 의존 대상 클래스나 메서드가 final 이다. 이 경우 대역으로 대체가 어려울 수 있다.
  • 테스트 대상의 소스를 소유하고 있지 않아 수정이 어렵다.

테스트 가능한 설계

앞에서 살펴본 테스트가 어려운 주된 이유는 의존하는 코드를 교체할 수 있는 수단이 없기 때문

 하드 코딩된 상수를 생성자나 메서드 파라미터로 받기

생성자나 세터를 이용해서 경로를 전달 받기

메서드를 실행할 때 인자로 전달받기

 의존 대상을 주입 받기

생성자를 통해서 의존 대상을 주입하게 수정

 테스트하고 싶은 코드를 분리하기

역할이 섞여 있는 코드에서는 테스트하고 싶은 코드를 별도 기능으로 분리해서 테스트를 진행

 시간이나 임의 값 생성 기능 분리하기

테스트 대상이 사용하는 시간이나 임의 값을 제공하는 기능을 별도로 분리해서 대역으로 처리

 외부 라이브러리는 직접 사용하지 말고 감싸서 사용하기

외부 라이브러리와 연동하기 위한 객체를 따로 만들고, 테스트 대상을 분리한 타입을 사용하게 변경

 

변경전

public class LoginService {
    private String authKey = "somekey";
    private CustomerRepository customerRepo;

    public LoginService(CustomerRepository customerRepo) {
        this.customerRepo = customerRepo;
    }

    public LoginResult login(String id, String pw) {
        int resp = 0;
        boolean authorized = AuthUtil.authorize(authKey);
        if (authorized) {
            resp = AuthUtil.authenticate(id, pw);
        } else {
            resp = -1;
        }
        if (resp == -1) return LoginResult.badAuthKey();

        if (resp == 1) {
            Customer c = customerRepo.findOne(id);
            return LoginResult.authenticated(c);
        } else {
            return LoginResult.fail(resp);
        }
    }

}

 

 

변경 후 코드

public class AuthService {
    private String authKey = "somekey";

    public int authenticate(String id, String pw) {
        boolean authorized = AuthUtil.authorize(authKey);
        if (authorized) {
            return AuthUtil.authenticate(id, pw);
        } else {
            return -1;
        }
    }
}

public class LoginService {
    private AuthService authService = new AuthService();
    private CustomerRepository customerRepo;

    public LoginService(CustomerRepository customerRepo) {
        this.customerRepo = customerRepo;
    }

    public void setAuthService(AuthService authService) {
        this.authService = authService;
    }

    public LoginResult login(String id, String pw) {
        int resp = authService.authenticate(id, pw);
        if (resp == -1) return LoginResult.badAuthKey();

        if (resp == 1) {
            Customer c = customerRepo.findOne(id);
            return LoginResult.authenticated(c);
        } else {
            return LoginResult.fail(resp);
        }
    }
}

 

의존하는 대상이 final 클래스거나 의존 대상의 호출 메서드가 final인 경우에도 동일한 기법으로 테스트 가능

 

목차

 

대역 필요성

"자동이체 기능"을 테스트하기 위한 외부 서비스에서 제공되는 정보를 이용하여 구현 시 문제가 되는 상황들

  • 해당 업체에서 상황별로 테스트 할 수 있는 카드번호 받아야함
  • 외부서비스에서 실수로 유효한 카드번호 삭제
  • 발급 받은 유요한 카드번호의 유효기간 지남

외부 서비스에서 제공하는 HTTP URL을 이용하여 제공되는 정보 구현이 어려움

 

외부요인이 테스트에 관여하는 주요 예시

  • 테스트 대상에서 파일 시스템을 이용
  • 테스트 대상에서 DB로 부터 데이터를 조회하거나 데이터를 추가
  • 테스트 대상에서 외부 HTTP 서버와 통신

TDD 는 "테스트 작성 -> 통과시킬 만큼 구형 -> 리팩토링" 의 과정을 짧은 흐름으로 반복해야 한다.

하지만, 테스트 대상이 이런 외부 요인에 의존하면 테스트를 작성하고 실행하기 어려워지며 실행 결과를 예측할 수 없게 만든다.

 

→ 이럴 때 외부 요인을 대신하는 대역을 이용하여 테스트를 작성한다.

 

대역을 이용한 테스트

대역을 이용하여  "자동이체 기능" 을 테스트하는 코드 작성해보기

 

CardNumberValidation을 대신한 대역 클래스

public class StubCardNumberValidator extends CardNumberValidator {
    private String invalidNo;
    private String theftNo;

    public void setInvalidNo(String invalidNo) {
        this.invalidNo = invalidNo;
    }

    public void setTheftNo(String theftNo) {
        this.theftNo = theftNo;
    }

    @Override
    public CardValidity validate(String cardNumber) {
        if (invalidNo != null && invalidNo.equals(cardNumber)) {
            return CardValidity.INVALID;
        }
        if (theftNo != null && theftNo.equals(cardNumber)) {
            return CardValidity.THEFT;
        }
        return CardValidity.VALID;
    }
}

- StubCardNumberValidation 에서 실제 카드번호 검증 기능을 구현하지 않고, VALID, INVALID 를 리턴하는 단순한 구현으로 대체

 

테스트 코드에 적용

....
@BeforeEach
void setUp() {
    stubValidator = new StubCardNumberValidator();
    stubRepository = new StubAutoDebitInfoRepository();
    register = new AutoDebitRegister(stubValidator, stubRepository);
}

@Test
void invalidCard() {
    stubValidator.setInvalidNo("111122223333");

    AutoDebitReq req = new AutoDebitReq("user1", "111122223333");
    RegisterResult result = this.register.register(req);

    assertEquals(INVALID, result.getValidity());
}

 

이와 같이 대역을 이용하면 필요한 외부 서비스 api 를 흉내내서 테스트에 적용할 수도 있다.

또한, DB 없이 DB 역할을 하는 클래스를 간단한게 구현하여 적용할 수도 있다.

 

대역의 종류

대역 종류  설명
스텁(Stub) 구현을 단순한 것으로 대체한다. 테스트를 위해 필요한 값만 제공하면 된다.
가짜(Fake) 제품에는 적합하지 않지만, 실제 동작하는 구현을 제공한다. DB 대신에 메모리를 이용해서 구현하는 방식이 이에 해당한다.
스파이(Spy) 호출된 내역을 기록한다. 기록한 내용은 테스트 결과를 검증할 때 사용한다.
모의(Mock) 기대한 대로 상호작용하는지 검증한다. 기대한 대로 동작하지 않으면 익셉션을 발생할 수 있다. 모듸 객체는 스텁이자 스파이도 된다.

 

❏ 약한 암호 확인 기능에 스텁 사용 ➡︎ 테스트를 위해 필요한 값 (암호가 약하다고 응답)만 제공

  • 실제 동작을 구현할 필요 x → 단순하게 약한 암호인지만 알려주면 된다.(스텁)
    • 스텁으로 하여금 암호 확인 요청이 오면 암호가 약하다고 응답하라고 설정
    • 실제 테스트 하는 클래스에 스텁용 클래스를 주입하여 결과를 리턴

      

❏ 리포지토리를 가짜 구현으로 사용 ➡︎ 리포지토리의 기본 기능을 구현

  • 동일한 ID를 가진 회원이 존재할 경우 익셉셥을 발생하는 테스트 → 가짜로 회원을 추가
    • User 클래스를 생성 - 엔티티 구현
    • 가짜 DB 로 MemoryUserRepository 클래스 구현
    • UserRepository 를 구현해서 가짜 DB에 메서드 추가

         Note : 상수를 이용하여 테스트를 통과시킨 다음에 구현을 일반화할 방법이 떠오르지 않으면
                     예를 추가하면서 점진적으로 구현을 완성해 나가면 된다.

 

❏ 이메일 발송 여부를 확인하기 위해 스파이를 사용

 

회원 가입에 성공하면 회원 가입 안내 메일 발송 여부를 확인하는 방법

  • 회원 가입 후 EmailNotifier "메일 발송 기능" 을 실행할 때 "가입한 이메일 주소" 를 사용했는지 확인 

EmailNotifier 의 스파이 대역(class SpyEmailNotifier) 구현하여 이메일 발송여부를 확인

  • 이메일 발송 기능을 호출 → SpyEmailNotifier 에서 호출 됨를 저장하여 True 로 반환
  • 이메일 발송 기능을 호출 → SpyEmailNotifier 에서 가입시 사용된 이메일을 저장하여 해당 이메일을 반환 
assertTrue(spyEmailNotifier.isCalled());
assertEquals("email@email.com", spyEmailNotifier.getEmail());

 

모의 객체로 스텁과 스파이 대체

- 주요기능 : 대역 객체(스텁, 스파이) 가 기대하는대로 상호작용했는지 확인하는 것

모의 객체를 위한 도구를 이용하면, 모의 객체가 기대하는대로 상호작용했는지 검증이 가능하다.

 

상황과 결과 확인을 위한 협업 대상(의존) 도출과 대역 사용

외부 API 를 직접 연동하여 테스트 코드에서 상황을 구현하거나 결과를 확인하기 어렵다.

이렇게 제어하기 힘든 외부상황이 존재하면 아래의 방법으로 의존을 도출하고 이를 대역으로 대신 할 수 있다.

  • 제어하기 힘든 외부 상황을 별도 타입으로 분리
  • 테스트 코드는 별도로 분리한 타입의 대역을 생성
  • 생성한 대역을 테스트 대상의 생성자 등을 이용해서 전달
  • 대역을 이용해서 상황 구성

"자동 이체 정보 등록 기능" 에서 외부 상황 분리

  • 카드번호가 유효한지 검사 하는 기능을 별도 타입으로 분리하고 대역을 생성
    • 별도 타입 : 카드번호 검사 기능 → 대역을 이용하여 구현 및 생성

회원 가입에 성공한 경우 이메일을 발송하는 기능

  • 회원 가입 기능 실행 이후에 이메일 발송 여부를 확인할 수단 필요
    • 이메일 발송 자체를 UserResiter 에서 구현하면 발송 여부를 확인하기 어려움
    • 결과 확인과 관련된 기능을 별도 타입으로 분리 → 대역으로 대체

당장 구현하기 어렵거나 오래걸리는 로직도 분리하기에 좋은 후보다.

대역과 개발 속도

TDD 과정에서 대역을 사용하지 않고 실제 구현을 사용한다면 발생하는 상황들

  • 카드 정보 제공 업체에서 도난 카드번호를 받을 때까지 테스트를 기다린다.
  • 카드 정보 제공 API가 비정상 응답을 주는 상황을 테스트하기 위해 업체의 변경 대응을 기다린다.
  • 회원 가입 테스트를 한 뒤에 편지가 도착할 때까지 메일함을 확인한다.
  • 약한 암호 검사 기능을 개발할 때까지 회원 가입 테스트를 대기한다.

모두 대기 시간이 발생 ➔ 대역을 사용하면 실제 구현이 없어도 실행 결과를 확인 가능

 

대역은 의존하는 대상을 구현하지 않아도 테스트 대상을 완성할 수 있게 만들어주며
이는 대기시간을 줄여주어 개발 속도를 올리는데 도움이 된다.

 

모의 객체를 과하게 사용하지 않기

 

모의 객체 사용의 편리성

  • 대역 클래스를 직접 만들 필요 없이 테스트 가능.

문제점

  • 결과 검증 코드가 길고 복잡해짐.
  • 여러 모의 객체 사용 시 코드 복잡도 증가.
  • 메서드 호출 검증 위주로 상호 작용이 조금만 바뀌어도 테스트가 깨질 위험.

주의사항

  • 모의 객체의 메서드 호출을 결과 검증의 주 수단으로 사용하지 말 것.

 

대안

  • DAO나 리포지토리 등 저장소에 대한 대역으로 메모리 기반의 가짜 구현 사용 권장.
  • 테스트 코드 관리가 용이해짐.

실용적 접근법

  • 모의 객체는 필요한 곳에 최소한으로 사용
  • 단위 테스트에서 모의 객체를 과도하게 사용하는 것보다, 통합 테스트를 통해 실제 상호작용을 검증하는 것이 좋음

 

 

 

 

 

+ Recent posts