Unit Test

Judy·2022년 5월 8일
0

iOS

목록 보기
7/28

테스트를 왜 할까?

  • 코드의 안정성 보장
  • 유지 보수에 유리
  • 스펙의 문서화
  • 깔끔한 코드

1. 유닛 테스트(Unit Test)

소스 코드의 특정 모듈이 의도대로 작동하는지 검증하는 절차

= 모든 함수와 메서드에 대한 테스트 케이스(Test case)를 만드는 절차

  • 코드 변경으로 문제가 발생해도 단시간에 파악할 수 있음
  • 테스트 케이스는 서로 분리되어야 함 -> 테스트를 위한 가짜 객체를 생성하기도 함

유닛 테스트는 다른 말로 단위 테스트라고 한다. 하나의 함수, 메서드를 기준으로 진행되는 가장 작은 단위틔 테스트이다. -> 메서드를 하나 하나 테스트해보는 개념

왜 유닛 테스트를 할까?

  • 메서드에 대해 독립적으로 테스트해서 빠른 테스트 진행과 빠른 리팩터링이 가능
  • 코드의 확장과 리팩터링 시 안정성을 확보

통합 테스트(UITest)

단위 테스트보다 큰 단위로 주로 객체 사이의 동작에 대한 테스트

UI에 대한 사용성을 테스트하는 것인데 비산 비용과 실제 사용성과 차이가 있어 많이 사용되지는 않는다.

테스트 방법

예상 값결과 값을 비교하는 방식으로 진행

! 테스트 가능한 코드를 짜는 것도 중요 (안되는 경우 예시 - 의존성이 너무 강해 메서드 외부 코드의 영향을 매우 많이 받는 경우)

좋은 유닛 테스트 - FIRST 원칙

1. Fast

  • 테스트는 빠르게 동작할 수 있어야 한다.

2. Independent/Isolated

  • 테스트는 서로 독립적이며, 서로 의존해서는 안 된다.

3. Repeatable

  • 테스트의 결과는 언제 어디서나 동일하게 반복되어야 한다.

4. Self-Valudating

  • Bool을 이용해 스스로 성공/실패 검증이 가능해야 한다.

5. Timely

  • 실제 코드를 구현하기 직전에 구현해야 한다,


2. Unit Test 작성

1) Test Navigator에서 새로운 Unit Test를 생성

  • 앱과 테스트는 다른 Target이므로 테스트에 관련된 info.plist가 새로 생성됨(Xcode 13 버전 이후로는 별도로 생성되지 않고 Target 세팅에 숨겨져 있음)

    info.plist : 실행 패키지에 관한 필수 설정 정보가 포함된 구조화된 텍스트 파일


2) 생성한 유닛 테스트 파일 확인

  • import XCTest : 유닛 테스트, 퍼포먼스 테스트, UI 태스트를 만들고 실행하는 프레임워크
  • XCTest : 테스트의 기본이 되는 추상 클래스
  • XCTestCase : XCTest의 하위 클래스로 테스트를 작성하기 위해 상속하는 클래스 -> 테스트를 위한 다양한 프로퍼티와 메서드를 사용할 수 있음
  • setUpWithError() : 테스트가 같은 상태와 조건에서 실행할 수 있도록 테스트 케이스가 실행되기 전에 호출되는 메서드
  • tearDownWithError() : 각 테스트의 실행이 끝난 후 호출되는 메서드
  • testExample() : 테스트 케이스가 되는 메서드(test로 시작하는게 일반적이며 문서화를 위해 한글로 짓기도 함)
  • testPerformanceExample() : 성능을 테스트하기 위한 메서드, measure(block:) 메서드를 통해 성능 측정

3) 타겟 설정

@testable import 파일이름
  • @testable : 앱 타겟에 있는 코드에 접근하기 위한 키워드

4) 테스트 초기화 설정

  • 테스트할 메서드의 타입을 프로퍼티로 생성
  • setUpWithError()에서 프로퍼티 초기화
  • tearDownWithError()에서 다시 nil로 초기화
  • 메서드 내부에서 super를 불러오는 이유 = XCTestCase를 상속받아 메서드롤 override해서 사용하기 때문에

5) 테스트 코드 작성

  • 테스트할 input을 작성 -> given
  • 실제 메서드를 이용한 값을 result로 받음 -> when
  • 두 값이 같은지 비교 -> then

테스트 결과를 확인하는 함수

  • XCTAssertEqual : 비교하는 두 값이 같은지 확인하는 함수
  • XCTAssertGreaterThan : 대소 비교
  • XCTAssertNil : nil인지 판별
  • XCTAssertThrowsError : 무엇을 throw 하는지
  • XCTAssertTrue, XCTAssertFalse: 반환 된 Bool 타입


6) 테스트 실행하기

  • 방법1) 테스트 네비게이터에서 실행
  • 방법2) 거터(gutter, 라인 넘버가 표시되는 곳)의 다이아몬드 버튼 누르기
  • 방법3) 단축키 : Command + U


3. Code Coverage

실제 앱 코드에서 테스트가 어느 정도 진행되었는지 알 수 있는 툴
= 테스트의 가치를 측정해주는 툴


확인할 수 있는 요소

  • 실제 테스트에서 어떤 코드가 실행되었는지
  • 정확성과 성능에 대해 충분히 테스트가 이루어졌는지
  • 테스트가 포함하고 있지 않은 코드는 무엇인지

확인 방법

Product -> Scheme - Edit Schemev - Test - Options - Code Coverage 활성화
이후 Report 네이베이션에서 Test log에서 Coverage 선택

  • cover되지 않은 코드 영역은 빨간 박스로 알려준다



4. 비동기 메서드 테스트

Random 값을 테스트할 수 있을까?

불가능하다. 값은 무작위하게 결정되므르 기대 값과 예상 값을 설정할 수 없기 때문이다.

completionHandler에 대한 테스트

URLSession을 통해 랜덤 값을 받아오는 메서드일 때 테스트 방식은?
-> 메서드를 호출하고 랜덤 값과 비교해 범위 내에 있는지 확인하면 되나?
-> ❌ 테스트 결과 함수를 아예 실행하지 않고 지나감
-> 비동기적으로 처리하는 메서드이기 때문!!

비동기 메서드 테스트

  • expectation(description:) : 수행되어야 하는 내용을 description으로 정해줌
  • fulfull() : 정의한 expectation가 충족될 때 호출
  • wait(for:timeout:) : expectation을 배열로 전달해 배열의 모든 요소가 fulfill이 될 때까지 기다린다. timeout으로 시간을 제한할 수 있음

여러 비동기 작업을 기다려야 한다면 여러 개의 expectation을 만들어 기다릴 수 있다.



5. 테스트를 위한 객체 만들기

이전에 했던 방식은 네트워크 환경이 구축된 상태에서만 가능하다.
-> Respeatable 원칙을 벗어남

하지만 메서드가 네트워크를 통해 동작하는데 어떻게 네트워크 없이 테스트를 할까?
-> 테스트 객체를 만들어서 해결


Test Double

테스트를 진행하기 어려운 경우 대신해서 테스트할 수 있도록 만드는 객체

실제 객체를 테스트 더블로 바꿔서 테스트를 진행한다. -> 마치 스턴트 배우가 대신 해주듯이!

테스트 더블의 역할

  • 테스트 대상 코드를 격리
  • 테스트 속도 개선
  • 예측 불가능한 실행 요소 제거
  • 특수한 상황을 시뮬레이션
  • 감춰진 정보를 얻음

테스트 더블의 종류

1) Dummy

  • 가장 기본적인 테스트 더블
  • 기능이 구현되어 있지 않은 인스턴스화된 객체로 사용 -> Dummy 메서드는 동작하지 않음
  • 객체를 전달하기 위한 목적

2) Stub

  • Dummy가 실제로 동작하는 것처럼 만들어 실제 코드를 대신해서 동작하는 객체
  • 테스트 불가한 객체를 도려내 최소한의 역할만 대신하도록 간단하게 구현

3) Fake

  • Stub보다 구체적으로 동작해서 실제처럼 보이지만 실제 동작과 적합하지 않은 객체
  • 실제 코드와 비슷하지만 동작을 단순화해서 구현

4) Spy

  • Stub의 역할을 가지며 호출된 내용이나 방법 등 정보를 기록하는 객체

5) Mock

  • 실제 객체와 가장 비슷하게 구현된 객체
  • Stub : 상태 기반 테스트 = 메서드를 호출해 결과 값과 예상 값을 비교
  • Mock : 행위 기반 테스트 = 행위에 대한 시나리오를 만들어놓고 비교

의존성 주입(Dependency Injection)

하나의 객체가 다른 객체의 의존성을 제공하는 기술

의존성(Dependency)

어떤 객체가 내부에서 생성해 가지고 있는 객체

의존성 주입 방식

내부에서 초기화되지 않고 외부에서 객체를 생성하여 내부에 주입시키는 방식

사용 이유

객체간의 결합도를 낮추기 위해

  • 리팩터링이 쉬워짐
  • 테스트 코드 작성이 쉬워짐

작동 방식

네트워크 이용(원래)
URLSession -> datatask -> URLSessionDataTask -> resume -> Nestworking -> completion Handler

  • URLSession Data Task를 만들어 네트워킹을 통해 값을 얻어와 completion Handler를 실행

Test Double
DummyData -> StubURLSession -> datatask(DummyData) -> Stub URLSessionDataTask -> resume -> completion Handler

  • 네트워킹으로 받아오는 대신 직접 Data를 만들어 completion Handler에 전달
  • URLSession와 URLSessionDataTask대신 DummyData를 전달할 수 있는 Stub객체로 바꿔치기

미리 request에 대한 답을 설정해두고 전달하는 방식으로 테스트를 진행



6. TDD

FIRST 원칙에서 Timely에 따르면 테스트는 실제 코드 구현 직전에 구현해야한다고 한다. 아직 메서드가 구현되지 않았는데 어떻게 테스트를 구현할 수 있을까?

이 말은 테스트를 작성하면서 동시에 메서드를 구현해간다는 의미이다. 이러한 테스트 방법론을 TDD(Test-Driven Development)라고 한다.

테스트 주도 개발 (TDD)

테스트를 작성하면서 코드를 완성시키는 방법론

  • 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스

방법

1) 요구사항을 검증하는 자동화된 테스트 케이스 작성
2) 테스트 케이스를 통과하는 최소한의 코드 생성
3) 코드를 표준에 맞게 리팩터링

TDD의 영역

  • Red : 실패하는 테스트 작성 구간
  • Green : 실패하는 테스트를 최소한의 변경으로 성공하게 하는 구간
  • Refactor : 테스트의 성공을 유지하며 개선하는 구간

장점

  • 단순한 설계를 장려
  • 안전한 코드
  • 기능 먼저 구현하다보면 테스트 불가능한 코드를 작성하게 될 수도 있음
  • 의존성이 낮음
  • 유지 보수가 용이

단점

  • 느린 개발 속도





참고: 야곰닷넷 - Unit Test

profile
iOS Developer

0개의 댓글