[WWDC] Engineering For Testability

임승섭·2024년 3월 6일
0

WWDC

목록 보기
1/1

WWDC 영상이 내려갔지만, 내용이 꽤 좋아보여서 여러 레퍼런스 참고해서 정리하였습니다

Engineering For Testability

  1. Testable app code
    • Testable한 코드가 무엇인지 알고, App 코드를 Testable하게 개선하는 방법
  1. Scalable test code
    • 앱의 크기나 복잡성이 커져도 대응 가능한 확장성 있는 Test 코드를 작성하는 방법

1. Testable App Code


Unit Test의 구조

  • AAA 패턴 : (Arrange - Act - Assert)
    • input : Arrange
    • test : Act
    • output : Assert

Testable Code의 특징

  • Control over inputs
    • 입력을 제어할 수 있어야 한다
  • Visibility into outputs
    • 출력을 검사할 수 있어야 한다
  • No hidden state
    • (코드 동작에 영향을 줄 수 있는) 내부 상태에 의존하지 말아야 한다

Testability Techniques

1. Protocols and Parameterization

Sample View

  • segment control (View / Edit) + button (open)

Testable하지 않은 코드

  • 버튼 클릭 시 동작하는 메서드
    • URL 구성하는 비즈니스 로직
    • UIApplication 을 통해 URL에 해당하는 앱 전환

  • Unit Test 실행
    • (Arrange - Act - Assert) 에서 Assertion을 해야 하는데,
      앱을 전환하기 위해 생성된 URL을 검사할 방법이 없다.
    • XCTAssertTrue(예상 URL == Output URL) 을 해야 하는데, url 자체를 꺼내올 수 없는 상황

코드의 문제점

  1. 비즈니스 로직(메서드)이 VC에 있다
    • 테스트 시 VC를 생성해야 함
  1. 뷰의 입력 상태(segmentControl.selectedsegmentIndex)를 직접 가져온다
    • 테스트 시 뷰의 속성값을 지정해서 간접적으로 입력을 제공주어야 함
  1. UIApplication 인스턴스 사용
  1. canOpenURL의 반환값에 따라 openTapped의 동작 변화
    • 제어하지 못하는 input이 되어버림.
  1. open을 통해 앱을 열었을 때 side effect에 대한 Unit Test 작성 불가
    • 추가적으로, 앱을 다시 foreground로 가져올 방법이 없다.

Testable한 코드 만들기

  1. 비즈니스 로직을 VC에서 분리
    • DocumentOpener 라는 클래스 생성 후 로직 이동
    • 테스트 시 VC 생성할 필요 x
    • documentmode 매개변수를 넣어줄 수 있게 해서 뷰의 상태 가져올 필요 x

  1. UIApplication 주입
    • 생성자에서 UIApplication 인스턴스 주입
    • DocumentOpenr에서 직접 호출하지 않도록 함


  1. testDocumentOpenerWhenItCanOpen (NOT completed)
    • Test를 위해 app 인스턴스를 만들어서 넘겨주어야 한다.
    • 하지만, 타입인 UIApplication을 맞춰주어야 하는데,
      UIApplication은 싱글톤이기 때문에 인스턴스를 생성하려고 하면 에러가 발생한다.
    • 그렇다고 UIApplication을 상속하려고 해도, 싱글톤 성격을 강제하기 때문에 예외가 발생할 수 있다.
      (And throws an exception to try to make a second instance, even if it's a subclass)

  1. 필요한 메서드를 가진 프로토콜 생성 후 생성자 타입 변경
    • 이미 UIApplication에 구현된 메서드를 선언하면, 추가적으로 구현할 필요가 없다
    • 이미 매개변수의 개수와 타입이 완벽히 일치하는 메서드들이 구현되어 있기 때문
      메서드 시그니쳐가 동일하다


  1. 제어가 불가능한 UIApplication 대신 제어가 가능한 Mock 객체 생성
    • 프로토콜을 준수하는 Mock 객체 생성
    • canOpenURL은 DocumentOpener로부터의 인풋처럼 행동
      • Test가 컨트롤해야 함
    • open 은 DocumentOpener의 출력처럼 행동
      • Test는 이 메서드를 통과하는 URL에 접근하고자 함. -> 프로퍼티에 저장해두고 나중에 Test가 읽을 수 있도록 함

  1. testDocumentOpenerWhenItCanOpen (Completed)
    • Mock 객체를 DocumentOpener의 생성인자로 넘겨준다.
    • DocumentOpeneropen 메서드에 input
    • 예상 URL과 mock의 URL 비교 후 검증 (assert)

Summary

  • Reduce references to shared instances & Accept parameterized input
    • UIApplication 싱글톤 인스턴스에 대한 직접 참조를 없애고, 매개변수화된 Input으로 대체
      의존성 주입
  • Intoduce a protocol
    • 클래스에 직접 의존하기보다는 프로토콜을 이용하여 인터페이스 분리
  • Create a testing implementation
    • 테스트 시 UIApplication 객체를 mock 객체로 대체


2. Separating Logic and Effects

Sample

  • 캐시에 저장할 아이템을 정의한 구조체 + 저장된 아이템을 불러오는 프로퍼티

  • cleanCache(maxSize: Int) : 캐시 저장 용량을 초과한 아이템을 비워주는 메서드

특징

  • Input 1 : 캐시의 저장 용량.
    • 저장 용량은 인자로 넣어줄 수 있기 때문에 제어 가능하다
  • Input 2 : 캐시에 저장된 아이템 (FileManager)
    • 캐시에 저장된 아이템을 불러오기 위해 FileManager 사용
      -> 실제 File System에 대한 의존성을 가지고 있다는 문제 발생

      side effect : 메서드가 외부의 속성을 변경하는 것. ex). 메서드 호출 시 전역 변수 변경

    • 따라서, cleanCache 메서드를 통해 실제 File System의 아이템이 clean 되는 게 아니라,
      어떤 것을 clean할 지 리턴 값으로 주는 것이 더 낫다!

Protocols and Parameterization 도입

  • FileManager에 직접 의존하지 않으면서, 아이템을 받으면 뭘 삭제할지 반환하는 구조로 만들어야 한다.
  • 또한, 어떤 캐시 정책으로 아이템을 관리할지도 분리한다
    -> 직접 지우는 코드와 뭘 지울지 결정하는 코드 분리 가능

  1. 캐시에 있는 아이템 받아서 뭘 삭제할지 반환하는 프로토콜

  1. 삭제할 아이템 반환 메서드 구현

  1. testMaxSizeCleanupPolicy
    1. input 정의
    2. 메서드 호출
    3. Output 받아
    4. 예상 값과 일치하는지 확인
    • Testable Code의 특징 만족
      • 입력 제어 가능
      • 출력 검사 가능
      • 그 외에 어떤 영향을 주는 hidden state x

  1. cleanCache
    • 프로토콜을 채택한 구조체를 paramter로 받고 해당 캐시 정책을 통해 어떤 아이템 제거할지 리턴으로 받는다.
    • 즉, cleanCache에는 실제 데이터에 영향을 주는 side effect만 남게 되었다

Summary

  • Extract algorithms
    • side effect를 고려하여 비즈니스 로직과 알고리즘 분리
  • Functional style with value type
    • Input과 Output을 보이기 위해 값 타입을 사용하는 functional style
    • 알고리즘이 input과 output을 보일 때, functional style을 취한다.
  • Thin layer on top to execute effects
    • cleanCache 메서드는 side effect를 일으키는 소량의 코드만 존재
      (side effect : FileManger를 통해 외부 실제 데이터에 영향 주는 것)



레퍼런스

https://gist.github.com/timd/3acdb6aa75efb40c03bb25cb60709c6d

[Unit Test] Testable한 코드를 위한 2가지 스킬과 예제
[WWDC17] Engineering for Testability 정리 (1) Testable App Code
WWDC2017 Engineering for Testability / Testable App Code - 번역

0개의 댓글