Print
카테고리: [ Development ]
조회수: 2116

1. 목적

숨어있는 결함(fault)을 찾아내기 위해 소프트웨어를 실행하는 행위와 절차


2. 원칙


3. 전략

전담테스트 조직을 운영함으로서 전문성과 효율성을 높인다.

사용자를 참여시켜 테스트의 신뢰성을 높인다.

품질관리(통제, 감사)와 연계한다.


4. 기능적 테스트 / 비기능적 테스트

4.1. 기능적 테스트란?

4.2. 기능적 테스트의 특징


5. 테스트 피라미드

2009년 마틴 파울러가 제시한 테스트 방법(TestPyramid (martinfowler.com))으로 테스트를 크게 세가지 범주로 구분한다.

  1. Unit Test 
  2. Integration Test 
  3. UI Test 

1이 피라미드 하부, 3이 피라미드 상부이다. 상부로 올라갈 수록 테스트 수는 줄어든다. 이 말은 단위 테스트는 많이 진행된다는 것이다.

5.1. Unit Test

가장 쉽고 빠르게 적용할 수 있다. 주로 단일 함수나 메소드 단위로 애플리케이션 로직을 테스트한다. 핵심적인 목표는 기능 정상 동작 여부이다. <테스트 커버리지>라는 지표를 통해 전체 코드 대비 얼만큼 테스트되었는지 측정할 수 있다.

5.2. Integration Test

화면과 같은 사용자 인터페이스를 경유하여 테스트하는 것으로 서비스 테스트라고도 하며, 코드와 시스템이 어떻게 상호작용하는지 확인한다. 모놀리식 애플리케이션과 마이크로서비스 간에 약간 차이가 있는데, 모놀리식은 화면에 서비스를 제공하는 대상만 테스트할 수 있지만 마이크로서비스의 경우는 관련된 개별 서비스까지 테스트할 수 있게 된다.

이 테스트는 실패할 경우 어디에서 문제가 발생하였는지 찾기가 어렵긴 하다. 

5.3. UI Test

E2E 레벨의 전반적인 UI/UX 테스트이다. 만약 테스트를 통과하면 강한 확신을 얻을 수 있다.


6. 테스트 자동화

테스트 자동화를 위한 장애물이 많다.

6.1. 테스트 자동화 범위

6.2. 장점

6.3. 리스크

6.4. API 테스트 자동화

6.4.1. 수동 테스트

자동화가 되지 않은 API 테스트 시에는, 도구를 이용한다고 해도 결국 사람이 결과값을 확인해야 한다. 또 매뉴얼하게 수행함에 따라 테스트 이력관리의 어려움이 있고 결함이 발생한 정확한 시점을 알기 어렵다. 

수동 테스트는 다음과 같은 방법이 있다.

6.4.2. 자동 테스트

자동화 방식은 다음과 같다.

6.4.3. 업무 선정

전체 API를 자동화할 수는 없다. 현실적으로 많은 비용이 투여되고, 이미 운영 중인 API에 대해 테스트 자동화하기는 어렵기 때문이다. 따라서 목적에 맞는 대상을 잘 선별하여 자동화하는 것이 중요하다. 

6.4.4. 관련 도구


7. 스모크 테스트

예비 테스트라고도 한다. 새로 추가된 기능이 기존 핵심 기능에 영향을 미치는지 쭉 돌려보는 테스트다.

이름의 유래는 하드웨어 전원을 켰을 때 연기가 나지 않으면 테스트 통과, 라는 것에서 유래한다.

스모크 테스트는 다음 조건을 필요로 한다.


8. 유닛 테스트

함수와 메소드에 대한 테스트를 케이스를 작성하여 특정 모듈이 의도한대로 동작하는지 검증하는 테스트이다. 

유닛 자체의 불확실성을 제거하므로 상향식 테스트에 적합하다. 

코드 커버리지는 테스트가 얼마나 충분한지 나타내는 지표이다. 예를 들어 100줄의 코드 중 80줄의 테스트가 진행되면 커버리지는 80%다. 코드 커버리지의 기준은 다음과 같다.

테스트에 소요되는 시간이 없지 않지만 그래도 얻는 장점이 많고 궁극적으로는 생산성을 올려준다고 할 수 있다. 장점은 다음과 같다.

기존 코드가 있을 때 모두 다 테스트 코드를 작성해야 할까? 가성비를 따져야겠지만, 기존 코드는 E2E 테스트로 커버하고 가능하면 새로 개발하는 부분에 대해 테스트를 시작하는 것이 좋다.

일반적인 작성 방법은 다음과 같다.


9. MSA 테스트

Bounded Context에서 마이크로서비스가 생겨나서 제공자와 소비자간의 합의를 통해 스펙이 결정되며, REST/HTTP 기반 혹은 PUB/SUB 기반 통신을 하고 있다.

모놀리식 아키텍처에 비해 MSA는 민첩성을 얻을 수 있지만 코드 변경은 여전히 여러 위험을 초래한다.

게다가 서비스가 점점 작아지고/많아지고, API 개선이 잦아지고, 글로벌리 팀간의 협업이 요구되기도 한다. 게다가..

MSA에서 테스트를 제대로 하려면 런타임에 모든 앱간의 의존관계가 확립되어야 하고, 통합 환경에서 전 구간의 테스트를 수행해야 한다. 그러나 방대한 구간의 환경을 수행하는데 정말 많은 시간과 비용이 필요하다.

일단 클라우드 네이티브한 배포 전략은 다음과 같다.

각 배포의 자세한 설명은 이미 너무 많은 자료들이 있어 생략한다.

9.1. CDC (소비자 주도 계약, Consumer Driven Contract)

소비자 주도 계약은 소비자의 요구사항 중심으로 제공자 서비스를 진화히시키 위한 협업 패턴으로 E2E 테스트에 투여되는 비용을 줄일 수 있다. 대체적인 장점은 다음과 같다.

소비자(Consumer) 관점의 테스트로 서비스에 대한 소비자의 기대 사항을 정의한다. 이 기대 사항은 테스트될 수 있도록 코드로 표현한다. 테스트는 생산자가 수행한다. 특성은 다음과 같다.

이 계약이 완벽하게 수행되기 위해서는 생산자의 CI/CD의 일부로 포함되어야 한다.

9.2. Contract 테스트

API의 제공자와 사용자간의 규약에 대한 테스트이다.

API Spec이 바뀌면 다수의 소비자가 영향을 받으므로 제공자는 Contract 테스트가 필요하다. 문제가 발견되면 다행이나 물론 시간적 손실은 피할 수 없다.

배포 전에 Contract 테스트가 이루어져야 한다. 그리고 문제가 있다면 배포되지 않아야 한다. 만약 문제가 있는데 배포가 되면 UI 상에서 기능이 동작하지 않게 된다.

이렇듯 Contract 정합성 유지와 스펙과 일치하는 테스트 코드가 필요한데, 이를 해결하는 프레임워크들이 있다.

이를 위한 도구는 Pact와 Spring Cloud Contract이다.

Spring Cloud Contract는 소비자가 제공자 코드에 그루비 코드를 작성해야 한다는 문제점이 있다. 반면 Pact를 사용하면 제공자 코드를 건드릴 일은 없다.

Spring Cloud Contract의 주요 기능은 다음과 같다.

Spring Cloud Contract의 DSL 공유 방안 예는 다음과 같다.

  1. 소비자는 생산자와 협의된 Contract을 생성하고 Git Repo에 Push한다.
  2. Git은 변경을 인지하고 빌드를 수행하며 Nexus에 업로드한다.
  3. 생산자는 빌드 수행시마다 Nexus에서 최신 DSL을 다운받아 해당 파일을 통해 개발/테스트한다.
  4. 테스트가 정장 수행되면 stub.jar 파일이 생성되고 Nexus에 엄로드된다.
  5. 소비자는 Contract 수행시 최신 버전의 stub.jar 파일 다운하여 mocking 하고 테스트 진행한다.

차이점을 정리하면 다음과 같다.

  Pact Spring Cloud Contract
환경   스프링 프레임워크
언어 루비, 자바, 자바스크립트, C# 자바
Contact 명세 위치 서비스 소비자 서비스 제공자

 

소스 안의 API 호출 지점과 Swagger에서 확인된 API Endpint 정보를 기반으로 서비스간 API 호출관계를 파악할 수 있다.

9.3. Documentation Driven Contract

요청자가 Contract를 Document 형태로 제공하여 개발하도록 하는 것

9.4. Cross Functional 테스트

비기능 테스트 및 속성 테스트

9.5. Component 테스트

마이크로서비스 내 모든 객체, 메소드에 대한 단위 테스트를 완료한 후 외부와 격리된 상태에서 전체를 테스트한다. 이렇게 격리된 상태에서 테스트를 수행하기 위해 타 마이크로서비스 호출에 대한 mock 및 stub이 필요하다.


10. E2E 테스트

E2E 테스트는 사용자 입장의 Workflow와 Latency를 측정하고 점검할 수 있다. 일반적으로 테스트는 프론트엔드, 백엔드, E2E 모든 영역에서 진행해야 하는 것이 맞지만 만약 여러가지 이유로 하나만 해야 한다면 E2E만 하는 것을 추천한다. 관련 테스트 도구로는 Selenium, Cypress, TestCafe 등이 있다.


11. UI 테스트 자동화

11.1. 도구 종류

  1. Selenium
    - 가장 유명한 도구라고 할 수 있다.
    - WebDriver라고 하는 웹 자동화 도구와 통합하는 작업이 진행되었다.
    - Script 작성 -> Selenium 라이브러리 -> Selenium 드라이버
     
  2. Cypress
    - Selenium과 비교하여 현재 개발 사례와 밀접하게 연계되어 있다.
     
  3. Appium
    - 모바일 쪽에서 많이 사용된다.
    - 1) UIAutomator2 :  안드로이드 GUI 객체 조작
    - 2) FacebookWDA : iOS의 GUI 객체 조작
     
  4.  SikuliX
    - Windows, Linux/Unix, Mac에 화면에 표시된 모든 것을 자동화한다.
    - 카드 결제 자동화 시에 적용 가능하는 경우가 있다.
     
  5. Katalon

11.2. 고민사항

CI/CD 파이프라인에서 모듈이 컴파일되고, 단위 테스트가 실행되고 통과하면 애플리케이션이 패키징된다. 그리고 이제 통합 테스트가 실행된다. 

테스트가 실패하면 다양한 사람에게 메일이 가거나.. 할 것이다. 혹은 대시보드에서 확인하게 될 것이다.

문제는 개발자 환경에서는 잘 되지만 CI/CD 파이프라인 상에서 실패하는 것이다.

11.3. Selenium & CI/CD


12. 자바스크립트 테스트

12.1. QUnit

12.2. Jasmin

12.3. Mocha.js


13. Mock & Stub

테스트 환경 구축을 위해 많은 시간이 필요할 때 mock 객체를 사용한다. 예를 들어 DB서버, 웹서버, FTP 서버 등.. 

또 테스트를 위해서 특정 모듈이 필요한데 이에 대한 추가적인 협의, 정책이 필요한 경우 mock 객체로 커버한다.

이렇듯 전체를 구성하지 않고 흉내를 내는 mock을 활용한 테스트는 가성비가 좋다. 하지만 테스트가 통과한다고 하더라도 실제 서비스에서는 실패할 가능성이 있고 소비자가 요구하는 스펙과 일관성을 유지하기 어려운 것은 여전한 문제이다. 테스트는 임의의 케이스를 만들어서 성공하게 되는데, 이 케이스의 정합성이 맞는지 보장할 수 없고 스펙으로 정의한 데이터 파일의 지속적인 관리가 어렵다.

아래에 mock과 stub에 대한 차이점을 설명할테지만 간략한 차이점은 다음과 같다.

유형 장점 단점
Mock
  • 테스트 객체를 효율적으로 생성할 수 있음
  • 올바르게 호출되었는지 확인 가능
  • 단위 테스트에서 결과를 명확하게 제공
  • 어려움
Stub
  • 만들기 쉬움
  • 유연성에 제약 있음
  • 단위 테스트에서 결과를 명확하게 제공하지 않음
  • 올바르게 호출되었는지 확인하는 기능 없음

13.1. Mock

테스트를 위한 다른 위장 객체들과 다르게 행위검증 사용을 추구한다. 즉 행위를 기록하는 식의 로직이 들어가 있다.

"행위를 테스트한다 - test the behavior of some other object"

이러한 mock을 위한 프레임워크가 존재하는데 다음과 같다. 

  1. EasyMock
    - 오픈 소스이며 오래된 mock 프레임워크이다.
    - CreateMock(mock 객체 생성), Record(mock 객체의 예상되는 동작 지정), Replay(테스트 메소드 내에서 mock 객체 사용) 단계를 거친다.
     
  2. jMock
    - call-chain(연쇄 호출)이라는 특징이 있다. 이는 동일 객체에 여러 메시지를 보낼 수 있는 것이다.
     
  3. Mockito
    - 요즘 대세이다.
    - 작성이 쉽고 리팩토링이 쉽다. 
    - CreateMock(mock 객체 생성), Stub(mock 객체의 동작 지정), Exercise(테스트 메소드 내에서 mock 객체 사용), Verify(메소드가 예상대로 호출되었는지 검증) 단계를 거친다. 

이러한 mock 프레임워크를 사용하면 mock 클래스 관리의 부담을 덜고, 시간도 절약할 수 있다. Mockito를 사용한다면 별도의 클래스를 만들지 않고 Mock 객체를 만들어 테스트할 수 있으며, Verify 단계를 통해 호출 여부를 검증할 수 있다.

그런데 mock 프레임워크를 사용할 때는 여러 고민이 필요하다.

13.2. Stub

mock이 행위를 검증한다면, stub은 상태를 검증한다.  

예를 들면 환율 정보 제공 API일 때, 미리 정해진 환율 정보를 리턴한다.

앞서 stub은 상태를 검증한다고 했는데 이러한 환율 정보가 상태이고 이것을 검증(assertEquals 등)하는 것이다.

또다른 예로는 Order 서비스를 예로 들 수 있는데, 외부 Payment와 연동한다고 할 때 실제 Payment를 대체하여 만들어진 응답 결과를 반환할 것이다.