목차

요약: Layer 테스트는 빠른 피드백, 문제의 원인 파악, 코드 안정성, 테스트 범위 확장 등을 제공하여 E2E 테스트와 상호 보완적으로 동작합니다. Layer 테스트는 기능과 성능을 빠르게 검증하는 데 유리하고, E2E 테스트는 최종 사용자 경험을 보장하는 데 필수적입니다.

1. 탄생 배경

Layer 테스트는 소프트웨어가 복잡해지고 규모가 커지면서 등장했습니다. 기존의 E2E 테스트만으로는 충분한 검증이 어려웠고, 특히 애플리케이션이 여러 계층(컨트롤러, 서비스, 리포지토리 등)으로 나뉘면서 각 계층을 개별적으로 테스트할 필요성이 제기되었습니다. 더불어 빠른 피드백을 제공하고 문제를 효율적으로 디버깅하기 위해 Layer 테스트가 도입되었습니다.

2. 사용하는 이유

  1. 복잡성 증가에 따른 필요성
    • 현대 소프트웨어는 점점 복잡해지면서 여러 계층(예: 컨트롤러, 서비스, 리포지토리)으로 나뉘어 설계됩니다. 각 계층의 기능을 개별적으로 검증해야 각 계층이 올바르게 작동하는지를 보장할 수 있기 때문에 Layer 테스트가 필요합니다.
  2. 빠른 피드백 제공
    • Layer 테스트는 특정 계층만 테스트하기 때문에 훨씬 빠르게 완료됩니다. 이를 통해 빠른 피드백을 받고, 수정해야 할 부분을 신속히 개선할 수 있습니다. 
  3. 문제의 원인 파악이 용이함
    • E2E 테스트에서 에러가 발생할 경우, 문제의 정확한 원인을 찾기 어렵습니다.
    • Layer 테스트는 각 계층을 독립적으로 검증하기 때문에, 특정 계층에서 문제가 발생했을 때 이를 신속하게 식별할 수 있습니다.
    • 이러한 방식은 디버깅을 효율적으로 만들어 문제 해결 시간을 단축시킵니다.
  4. 세부적인 기능 검증
    • Layer 테스트는 서비스나 비즈니스 로직 같은 개별 기능을 세밀하게 검증합니다.
    • E2E 테스트에서는 놓칠 수 있는 세부적인 로직을 더 정확하게 확인할 수 있습니다.
    • 각 계층의 로직이 기대대로 동작하는지 보장하여 시스템의 일관성을 높입니다.
  5. 유지보수성 향상
    • 코드가 자주 수정되고 기능이 추가되는 상황에서도 안정적으로 시스템을 유지하기 위해 Layer 테스트는 중요한 역할을 합니다.
    • 특정 계층의 변경이 다른 계층에 영향을 미치지 않도록 보장하여 리팩토링 시에도 안정성을 유지할 수 있습니다.
  6. 테스트 범위 확장
    • E2E 테스트만으로는 모든 시나리오를 포괄하기 어렵습니다.
    • Layer 테스트를 통해 특정 시나리오나 예외 상황까지 세밀히 검증할 수 있습니다.
    • 이로써 다양한 케이스를 커버하여 테스트의 신뢰성을 높입니다.
  7.  테스트 비용 절감
    • E2E 테스트는 리소스를 많이 소모하지만, Layer 테스트는 가볍고 빠르게 실행됩니다. 이를 통해 자주 테스트를 실행할 수 있어 비용이 절감됩니다.
    • 개발 주기를 최적화하고 리소스를 효율적으로 사용할 수 있습니다.
  8. 디자인 품질 향상
    • Layer 테스트를 염두에 두고 코드를 작성하면 더 모듈화되고 테스트 가능한 구조가 됩니다.
    • 코드가 더 응집력 있게 설계되며, 가독성과 유지보수성이 향상됩니다.
    • 설계 품질이 높아지면 향후 확장성에도 유리합니다.

3. 장점과 단점

장점

  • 빠른 테스트 속도:  E2E 테스트보다 실행 속도가 빠릅니다.
  • 효율적인 디버깅:  문제가 발생할 때 정확한 계층을 쉽게 파악할 수 있습니다.
  • 높은 테스트 커버리지: 특정 비즈니스 로직이나 예외 상황을 세밀하게 테스트할 수 있습니다.
  • 디자인 품질 향상: Layer 테스트를 염두에 둔 코드는 더 모듈화되고 테스트 가능한 구조가 됩니다.
  • 테스트 비용 절감: 더 적은 리소스와 비용으로 빈번한 테스트를 수행할 수 있습니다.

단점

  • 제약된 실제 환경 테스트: Layer 테스트는 개별 계층만 테스트하므로 실제 사용자 경험이나 전체 시스템의 통합을 충분히 검증할 수 없습니다.
  • 추가적인 코드 작성 필요: Layer 테스트를 작성하기 위해서는 더 많은 테스트 코드와 설정이 필요합니다.
  • 복잡성 증가: 각 계층에 대해 별도의 테스트를 작성하고 유지해야 하므로 프로젝트 관리가 복잡해질 수 있습니다.
  • 전체 시스템 통합을 검증하는 데 제한적: Layer 테스트만으로는 전체 애플리케이션의 흐름을 보장할 수 없으므로 E2E 테스트와 함께 사용해야 합니다.

4. E2E 테스트와의 차이점

비교 항목  Layer 테스트 E2E 테스트
테스트 범위 특정 계층(컨트롤러, 서비스, 리포지토리 등)을 독립적으로 테스트 애플리케이션의 전체 워크플로우를 검증
속도 빠름 느림
문제 분석 문제 발생 시 원인 계층을 쉽게 식별 가능 문제의 원인을 특정하기 어려움
테스트 목적 특정 비즈니스 로직이나 계층의 정확성 검증 전체 시스템의 통합과 사용자 흐름 검증
비용 리소스와 비용이 적게 듦 많은 리소스와 비용이 필요함
테스트 환경 독립적이며 모의 객체(Mock)나 가짜 서비스(Fake)를 사용 실제 환경에서 모든 구성 요소를 테스트
유지보수 테스트 코드가 많아 복잡할 수 있음 한 번 작성하면 잘 변경되지 않음

 

목차

가상화 기술

가상화 기술의 목적

  • 확장성 - 오버헤드 최소화
  • 비용대비 성능 - 리소스 사용률 향상, 운영의 유연함(환경의 단순화)
  • 고가용성 - 환경의 격리

가상화 기술의 효용

  • IPMI(Intelligent Platform Management Interface)를 대체하는 하이퍼바이저
    • IPMI : 벤더 서버에 있는 리모트 관리기능
    • 하이퍼바이저 : 호스트 OS, 서버 상에 최초로 기동하는 OS
  • 하드웨어 간 차이 흡수( → 환경 추상화)
    • 새로운 하드드웨어나 오래된 하드웨어로도 차분에 신경 쓰지 않고 사용 가능
  • 준 가상화(ParaVirtualization) 사용 - Xen 에 특화된 내용
  • 리소스 소비 제어
    • 과부하 경고
    • 부하 조정

가상화 서버 구축정책

가상화 기술을 도입하는 가장 기본적인 목적은 하드웨어의 이용효율 향상

 

방법

- 남아있는 리소스를 주로 이용하는 게스트 OS를 투입

  • CPU 리소스가 남아있다 ▶ 웹 서버 
  • I/O 리소스가 남아있다  ▶ 캐시 서버

주의할 점

  • 같이 두는 것을 피하는 형태의 조합
    • 리소스 소비경향이 비슷하고 부하가 높은 용도의 게스트 OS 끼리(각가의 웹 서버끼리 등)는 리소스를 서로 점유하려고 하기 때문
  • 중앙 스토리지는 사용하지 않는다.
    • 고가의 스토리지를 사용하지 않으면 충분한 안정성 확보 불가.

구체적인 게스트OS 구성예시 그림

가상화로 얻은 장점 

물리적인 리소스 제약에서 해방

  • 리소스를 동적으로 변경
  • VM(게스트 OS)의 마이그레이션이나 복제 용이

→ 서버 증설이 용이  → 더 나은 확장성 확보

 

소프트웨어 레벨의 강력한 호스트 제어

  • 비정상 동작 시 문제 국소화
  • 호스트 제어가 용이

→ 하드웨어와 운용 비용 저하 → 비용대비 성능 향상, 고가용성으로 발전

 

가상화 도입 시 주의할 점

  • 성능상 오버헤드 존재
  • 가상화 기술이 병목현상의 원인이 될 수도 있다.
  • PC 라우터에서는 가상화를 사용하기 때문에 성능이 떨어지는 경우 존재

하드웨어와 효율향상

저가 하드웨어의 유용한 이용 방침

  • 최소한의 관리기능
  • 많은 코어CPU
  • 대량의 메모리
  • flexible 한 I/O 성능
    • Diskless
    • 하드웨어 RAID-10
    • SSD RAID-10
  • 관리용 하드콘솔 불필요
    • IPMI 기능 →Intel AMT

SSD

하테네에서는 주로 다수의 DB 슬레이브 서버에 사용 I/O  때문

 

액세스 성능

  • 양호한 랜덤액세스 성능
  • 메모리 > SSD > HDD RAID-0/10 > HDD RAID-1

 

목차

다중성 확보

AP 서버

서버가 멈추면 이에 대한 대응으로 로드밸런서로 페일오버(failover, 장애극복) 페일백(failback, 정상복귀) 하여 고장난 서버를 자동적으로 분리하고 서버가 복귀 되면 원상태로 복귀시키는 작업을 수행하고 있다. 

 

페일오버(failover, 장애극복) 페일백(failback, 정상복귀) 방식 그림

 

위의 그림처럼, 서버가 10대 정도 있으면 1, 2대 서버가 정지하더라도 서버에 큰 영향을 주지 않아 운영하는데 수고를 줄일 수 있다.

DB 서버

DB 서버도 마찬가지로 서버를 여러대 나열해서 1, 2대 정지하더라도 충분한 처리능력이 있도록 해두는 것이 중요

 

다중화 ( 24시간 365일 100% 가동률) 을 위해서 멀티 마스터 방법을 사용

멀티 마스터?

- 쌍방으로 레플리케이션, 즉 서로가 서로의 슬레이브가 되는 상태로 해두고 한쪽에 쓰기작업을 하면 다른 한쪽으로 전달하고 반대쪽에 쓰더라고 다른쪽으로 전달하는 양방향 레플리케이션 방법

 

MySQL의 경우

한쪽에서 쓰기 작업을 수행하면 데이터가 반대쪽으로 전달되기까지 약간의 지연이 발생

→ 특정 시점에서는 데이터가 완전히 일치하지 않는 경우가 존재

→ 이 시점에 한쪽 서버가 분리되어 다운되면, AP 서버가 특정 데이터를 쓰려고 시도했을 때 이 데이터가 실제로는 쓰이지 않는 모순된 상황이 발생

해결방안

  • 엔터프라이즈에서는 이를 대처하기 위해 레플리케이션을 동기적으로 처리
  • 웹 서비스에서는 동기가 맞지 않는 리스크를 어느 정도 받아들임으로써 성능을 중시

 

멀티 마스터

 

MySQL 서버 구축에서 주류

 

페일오버(장애 극복)에 대한 구체적인 동작

- 상호간에 VRRP(Virtual Router Redundancy Protocol)라는 프로토콜로 감시.

- VRRP에 의해 한쪽이 분리된 것을 알게 되면 자신이 Active 마스터로 승격

 

멀티 마스터 구성  =  기본적으로 서버 2대 + Active/Standby 구성 → 항상 Active 쪽만 쓰기작업을 한다.

방식

Active 인 서버가 다운되면 Standby 였던 쪽이 Active로 승격해서 새로운 마스터가 된다.

다운된 서버는 수작업으로 복구 후

→ Active 역할을 맡았던 서버를 계속 Active로 유지하고 복구된 서버를 Standby로 설정 

문제가 있던 서버를 다시 Active로 설정하고 기존의 Standby 서버를 다시 Standby로 설정

 

이 방식은 전환 타이밍에 따라 동기가 맞지 않은 위험이 남아 있음

현 상황에서는 깨긋이 받아들이고(시스템이나 네트워크에서 발생할 수 있는 어떤 이슈나 문제를 인정하고) 수동으로 복구

 

참고

DB 서버 (슬레이브방식)

  • 서버를 여러 대를 나열한다.
  • 서버 1, 2대 정지하더라도 충분히 처리할 수 있도록 해둔다.

스토리지 서버

이미지 파일과 같은 미디어 파일을 저장하기 위해서 분산 스토리지 서버로 MogileFS가 있다.

 

분산 파일 시스템을 사용

- 확장성 : 대량의 파일의 보존 가능

- 다중성 : 일부 서버가 다운되더라도 전체 장애가 되지 않음

 

MogileFS

- 파일은 파일로 저장

- 파일이 어디에 위치해 있는지를 나타내는 메타 정보를 별도로 관리하는 방법

 

시스템 안정화

시스템 안정화와 상반관계

 

* 안정성 ↔ 자원효율

  한계에 이를 때까지 CPU 사용 - 자원 효율 향상 - 비용절감

    하지만 서버 1대가 다운 → 전체 처리능력 초과 → 장애

* 안정성 ↔ 속도

  한계에 이를 때까지 메모리를 튜닝 - 속도 향상

    하지만 메모리소비가 늘어난다 → 성능 저하   장애 

시스템의 불안정 요인

전형적인 요인들

◆ 애플리케이션/서비스 레벨 → 부하 증가

  1. 기능추가, 메모리 누수
    • 새로운 기능을 추가했는데 그 기능이 예상보다 무거워서 전체적인 부하가 늘어나 서비스가 다운
    • 메모리 누수는 완전히 배제하기 어려운 문제 - 버퍼가 너무 작으면 시간이 지남에 따라 버퍼를 다 사용해서 스왑을 사용하기 시작해서 부하가 증가
  2. 지뢰
    • 특정 URL이 읽히면(지뢰를 밟으면) 아무리 시간이 지나도 응답이 오지 않아서 마치 지뢰처럼 장애의 원인이 되는 현상
  3. 사용자의 액세스 패턴 :
    • 인기많은 사이트에 링크를 걸어두면 그 사이트 사용자가 집중적으로 접속해서 다운되는 경우와 같은 사용자의 패턴
  4. 데이터량 증가
  5. 외부연계 추가
    • Amazon API 와 연결된 서비스가 있을때 Amazon이 다운되면 해당 서비스도 다운

◆ 하드웨어 → 처리능력 저하

  1. 메모리, HDD 장애, NIC(Network Interface Card) 장애

시스템 안정화 대책

적절한 여유(버퍼) 유지

  • 메모리량, CPU 부하 → 한계의 7할 운용

불안정 요인 제거

  • SQL 부하의 상한선을 미리 정함
  • 메모리 누수를 줄임
  • 이상 동작시 자율제어
    • 자동 DOS 판정
    • 자동 재시작 (AP 서버, 호스트 OS)
    • 자동 쿼리제거

목차

Test Fixture 란

소프트웨어 테스트에서 테스트 환경을 설정하고 관리하기 위한 코드 집합을 의미합니다. 테스트가 정확하고 일관성 있게 실행될 수 있도록 준비하는 과정입니다. 이는 테스트를 위해 필요한 모든 상태나 환경을 포함합니다. 예를 들어, 데이터베이스를 설정하거나, 필요한 객체를 생성하거나, 특정 상태로 초기화하는 작업이 포함됩니다.

Test Fixture를 구성하는 기본 원칙

  • 독립적인 테스트 환경 구성: 이전 테스트의 실행이 이후 테스트에 영향을 주지 않도록 하기 위해, beforeEach 또는 afterEach를 사용해 환경을 초기화하고 정리
  • Setup과 Teardown 구분 
    • SetUp : beforeAll, beforeEach 메서드를 사용하여 필요한 초기 설정
    • Teardown : afterAll, afterEach를 사용해 자원을 정리
  • 재사용 가능한 코드 작성: 공통된 설정이나 데이터 준비 작업을 별도의 헬퍼 함수나 클래스로 추출하여, 필요할 때 재사용할 수 있게 작성

Test Fixture를 사용하는 이유

  1. 테스트가 항상 동일한 조건에서 실행되며, 테스트의 독립성과 신뢰성을 보장
  2. 사용하면 코드가 간결해지고, 유지보수성이 높아지며, 자동화된 테스트 실행이 용이

예제 상황

  1. 데이터베이스 테스트: 데이터베이스를 사용하는 애플리케이션의 경우, 각 테스트 전에 데이터베이스를 특정 상태로 초기화하고, 테스트 후에 정리하는 작업을 Test Fixture로 구성
  2. HTTP 요청 테스트: API 테스트에서는 각 요청에 대한 환경 설정(예: 모의 데이터 또는 인증 토큰 설정)을 Test Fixture로 구성

NestJS에서의 Test Fixture

NestJS에서 테스트를 작성할 때, beforeEach, afterEach, beforeAll, afterAll을 사용하여 테스트 픽스처를 구성할 수 있습니다. 주로 TestingModule을 사용하여 애플리케이션 모듈의 인스턴스를 생성하고, 의존성을 주입하는 작업이 포함

 

구성 요소 설명

  • TestingModule: NestJS의 테스트 모듈을 생성하여 의존성을 주입하고, 각 컴포넌트가 올바르게 동작하는지 확인합니다.
  • beforeAll: 테스트 스위트 내에서 한 번만 실행되는 초기화 코드입니다. 여기서 TestingModule을 컴파일하고 컨트롤러와 서비스를 인스턴스화합니다.
  • beforeEach: 각 테스트가 실행되기 전에 실행되어, 테스트 상태를 초기화하거나 Jest의 mock을 재설정합니다.
  • afterAll: 테스트가 모두 종료된 후 한 번 실행되어, 테스트 환경을 정리하거나 자원을 해제합니다.

NestJS와 Jest를 사용하여 테스트 픽스처를 구성하는 방법을 보여주는 예제

import { Test, TestingModule } from '@nestjs/testing';
import { MyService } from './my.service';
import { MyController } from './my.controller';

describe('MyController', () => {
  let controller: MyController;
  let service: MyService;

  beforeAll(async () => {
    // 모든 테스트 전에 실행되는 setup 작업 (한 번만 실행)
    const module: TestingModule = await Test.createTestingModule({
      controllers: [MyController],
      providers: [MyService],
    }).compile();

    controller = module.get<MyController>(MyController);
    service = module.get<MyService>(MyService);
  });

  beforeEach(() => {
    // 각 테스트 전에 필요한 초기화 작업 수행
    jest.clearAllMocks(); // Jest의 모든 mock 초기화
  });
  
  afterEach(() => {
    // 각 테스트 후 자원 해제나 초기화 작업
  });
  
  // 테스트
  it('should return expected data', async () => {
    const result = 'expected result';
    jest.spyOn(service, 'getData').mockImplementation(() => result);

    expect(await controller.getData()).toBe(result);
  });
  

  afterAll(() => {
    // 모든 테스트 후에 실행되는 teardown 작업 (한 번만 실행)
    // 테스트 모듈 정리 또는 자원 해제
  });
});

 

 

반복되는 코드를 모두 Fixture를 만들어서 해야 할까?

 

경우에 따라 다르겠지만, 테스트 상황에서 기본적으로 몰라도 되는 확인해보지 않아도 되는 코드는  Fixture를 생성해서 구현하는 것이 좋아 보인다. 예를 들어, 데이터 베이스 초기화같은 것이 있겠다.

 

하지만, 유저 생성, 토큰생성 등을 beforeAll 이나 beforeEach 에 구현해 버리면 각 테스트 마다 확인하기 위해 코드의 위 아래를 오가야하므로 오히려 비효율적이라고 생각한다.

 

차라리, 해당 기능들을 함수나 클래스를 만들어서 각 테스트에서 호출하여 사용하는 방법이 해당 테스트를 더 빨리 이해할 수 있을 것 같다.

  

 

'TDD' 카테고리의 다른 글

[ TDD ] [ 책 ] 6 장 테스트 코드의 구성  (0) 2024.10.27

목차

계층과 확장성

확장성에대한 요구

  • 서버 1대로 작동하는 서비스가 다수
    • 하테나 표준 서버 (4 core CPU, 8 GB 메모리) 예시
      • 피크 시 성능은 수천 요청/ 분 → 월 100 만 page view 처리 가능
    • 고성능 서버 (4 core CPU 2개, 32 GB 메모리)
      • 피크 시 : 월 200 만 page view 처리가능
  • 하테나에서 수억 page view / 월
    • 대규모 서비스는 서버 1대로는 동작할 수 없다.

계층별 확장성

  • AP 서버 ▶ 비교적 간단하게 확장가능
    • 구성이 동일하고 상태를 갖지 않기 때문
  • DB / 파일 서버 ▶ 분산, 확장성 확보가 어려움
    • read 분산 : 비교적 용이  ▶ 메모리를 많이 탑재, 기타
    • write 분산 : 매우 어려움

→ AP 서버 와  데이터 소스를(DB / 파일 서버) 구분해서 확장을 고려해야 한다.

부하 파악,  튜닝

부하파악 - 가시화

어떻게 서버를 확장할 것인지를 검토하기 위해서는 먼저 서버 부하를 파악할 수 있어야 한다.

 

서버군을 관리하고 각각의 부하를 적절하게 그래프화하는 것이 중요

 

하테나 북마크 예시

서버의 역할을 나열하여 관리하는 화면

 

부하를 측정하기 위한 항목 - Load Average,  CPU 관련, 메모리 관련

 

가장먼저 Load Average 확인

*Load Average ?
     프로세스가 언제든지 동작할 수 있는 상태이지만, 아직 실제 CPU가 할당되지 않아서 대기상태에 있는 프로세스의 수의 평균치
    (ex) 5분 동안 Load Average가 1이면 => 5분동안 평균 1개의 프로세스가 대기상태로 되어 있다는 의미, 이때 CPU의 코어 수 이하이면 부하가 양호한 편 (보통 CPU의 코어 수 이하에서 절반 정도로 맞춰지도록 제어함)

 

메모리 사용처 확인

 사용자 공간이 소비되고 있는 메모리공유되고 있는 메모리, 그리고 커널이 사용하고 있는 버퍼의 메모리

이런 항목을 통해 ▶ " 이런 거동을 하고 있으니 이런 동작을 보이고 있어" 와 같이 파악 ▶ 서버의 성능을 더 끌어내 고성능 시스템을 실현

 

용도에 맞는 튜닝 - 사용자용 서버, 봇용 서버

 

서버 분리

  • 봇용 : 응답시간이 중요하지 않음 → 요청 처리 수를 최대화시키는 방향으로 튜닝 가능
  • 사용자용 : 응답시간 중요 → 처리대기 프로세스를 쌓아두지 않고 양호한 응답을 유지하는 방향으로 튜닝 가능

봇과 사용자를 분리하여 AP 서버의 튜닝 정책을 변경해서

  • 효율을 중시할지
  • 응답시간을 중시할지
  • 리소스를 최대한 사용하는 것을 중시할지

결정하여 운영방향을 나눌 수 있다.

 

DB 도 사용자용과 봇용으로 나눠서 응답을 중요시할지, 리소스를 소진하는 것을 중요시할지 나누어서 운영할 수 있다.

 

튜닝 및 확장성 확보

 

튜닝

  • 부하파악, 상태 감시 : 서버관리툴
  • 부하를 가시화해서 병목이나 이상현상을 파악할 수 있도록 한다.
    • 여러 서버의 그래프를 겹쳐봄으로써 병목이나 이상현상 파악가능
  • OS의 동작원리를 알고 서버 성능을 올바르게 끌어내기 

확장성 확보

  • 로드밸랜서이용
  • 파티셔닝(DB분할)

+ Recent posts