UIColor 테스트 실패 해결 방안

Kwanghoon Choi·2022년 6월 9일
0

UIColor 테스트 실패 해결 방안

  1. 개요
    1.1 테스트 API
    1.1.1 UIColor API
    1.1.2 XCTest API
    1.2. 준비사항
  2. 이슈
    2.1 테스트 UIColor 객체 준비
    2.2 테스트 실행
  3. 분석방법
    3.1 디버깅 준비
    3.2 assembly 코드 설명
    3.3 assembly 명령어
    3.4 assembly 분석
    3.5 UIColor 비교 분석
  4. 이슈분석
  5. 해결방안

1. 개요

이 문서는 XCTest 프레임워크의 테스트 함수를 사용해서 다른 생성 방식으로 만들어진 두 개의 동일한 색상을 가지는 UIColor 객체의 동일성 테스트를 필요로 하는 개발자를 대상으로 하며, 테스트에서 발생하는 문제를 찾고 해결하는 과정을 설명합니다. Xcode lldb와 disassembly에 대한 지식이 필요하며, 내용을 이해하는데 필요한 최소한의 정보는 함께 제공합니다.

1.1 테스트 API

이 단락에서는 UIColor와 테스트 API를 이해하기 위한 기본적인 정보를 제공합니다. 익숙하신 분들은 다음 단락으로 건너띄고 필요할 경우 참고하셔도 무방합니다.

1.1.1 UIColor API

UIColor 객체를 만드는 방법은 아래 5가지로 분류할 수 있습니다.

  • 색상 값의 수치를 직접 입력

  • 만들어진 CIColor 전달

  • 만들어진 CGColor 전달

  • iOS 기본 제공 색상

  • color asset 리소스명 전달

    • UIColor(named: "red")

    상세한 내용은 이 링크(UIColor API)를 참조해주세요.

1.1.2 XCTest API

XCTest에서 두 객체의 비교에 사용하는 두 함수를 살펴보겠습니다.

  • XCTAssertEqual(객체 1, 객체 2) -> 객체 1과 2가 같다면 성공, 다르다면 테스트 실패
  • XCTAssertNotEqual(객체 1, 객체 2) -> 객체 1과 2가 같다면 성공, 다르다면 테스트 실패

객체는 비교를 위해 Equatable 프로토콜을 구현해야합니다. UIColor는 NSObject를 상속하는 객체이며, NSObject는 Equatable을 구현하고 있어 여기서는 신경쓰지 않으셔도 됩니다.

let a = UIColor(...)
let b = UIColor(...)

XCTAssertEqual(a, b) // 색이 다른 경우 상세 오류 안내
XCTAssertNotEqual(a, b) // 색이 같은 경우 상세 오류 안내

1.2. 준비사항

이 글의 진행을 문제없이 따라가기 위한 기본적인 준비사항을 설명합니다.

  • Xcode 13.4.1

  • 샘플 프로젝트(링크 추가 예정)

    • 모든 설명은 위 프로젝트를 시뮬레이터에서 실행한 결과를 바탕으로 진행합니다
  • 테스트를 하고자 하는 마음

2. 이슈

같은 방법으로 생성한 UIColor의 테스트는 문제가 없지만, 다른 방법으로 생성한 UIColor는 시각적으로는 같아보여도, 테스트 코드에서 의도치 않게 실패하는 경우를 만날 수 있습니다. 이 단락에서는 실패 상황을 재현하는 방법을 설명합니다.

2.1 테스트 UIColor 객체 준비

(설명 필요)

  1. 샘플 프로젝트에 준비되어있는 testColor 함수로 이동합니다.
class ColorTestTests: XCTestCase {

    func testColor() throws {
        
    }

}
  1. testColor 함수에 다음의 두 색상을 준비합니다.

    • 색상값으로 생성한 UIColor a

    • 미리 준비된 asset에서 가져온 UIColor b

      • a와 동일한 값으로 설정했습니다.
        • 상세 설정은 프로젝트에서 확인가능합니다.
  2. 만들어진 두 색상 a와 b를 XCTAssertEqual로 비교합니다.

코드는 아래를 확인하세요.

func testColor() throws {
    let a = UIColor(red: 0.9, green: 0.0, blue: 0.0, alpha: 1.0)
    let b = UIColor(named: "red", in: .main, compatibleWith: nil)!
    
    XCTAssertEqual(a, b)
}

2.2 테스트 실행

이제 테스트를 실행하고 결과를 살펴보겠습니다.

  1. 테스트를 실행하는 방법은 몇 가지가 있지만 여기서는 간단히 testColorFailed함수에서 바로 테스트를 실행하는 방법을 사용해보겠습니다. 아래의 캡처 좌측을 보시면 플레이 버튼이 보입니다. 마우스 커서로 클릭해주세요.
스크린샷 2022-06-09 오후 4.47.42
  1. 테스트 결과는 실패이며 결과를 살펴보면 동일한 값으로 보입니다. UIExtendedSRGBColorSpace는 컬러 스페이스값입니다. 동일합니다. 색은 차례대로 red 0.9, blue 0.0, green 0.0, alpha 1.0 이지만, 테스트는 두 색이 동일하지 않은걸로 판단하고 실패를 선언합니다.
스크린샷 2022-06-09 오후 4.49.12

다음 단락에서는 이 이슈를 분석하기 앞서 분석하기 위한 방법을 먼저 설명하겠습니다.

3. 분석방법

UIColor와 XCTAssertEqual 함수의 비교 코드는 외부에 공개되어 있지 않지만, lldb와 disassembly를 통하여 assembly 코드를 분석하면서 UIColor간의 구체적인 비교 방법을 알아보겠습니다. 이 단락에서는 성공하는 테스트 코드를 분석하는 과정을 통해 분석에 필요한 assembly 코드와 lldb 콘솔의 명령어를 설명하고, 그를 이용하여 UIColor를 비교하는 내부 코드의 디버깅 과정을 안내하려고 합니다.

3.1 디버깅 준비

lldb와 assembly를 이용한 보이지 않는 내부 코드의 디버깅에 익숙하신 분은 건너띄어도 무방합니다.

디버깅의 순서는 아래와 같이 진행합니다.

  1. 테스트 성공 확인

먼저 테스트의 성공 여부를 확인해보겠습니다.

testColorSucceed 함수로 이동합니다. 2.2 단락의 1번 항목에서 설명한 것처럼 testColorSucceed 좌측의 플레이 버튼을 클릭해서 테스트를 수행합니다. 성공했다는 결과가 아래와 같이 함수 좌측에 초록색 체크 마크가 표시되어야 합니다.

스크린샷 2022-06-09 오후 4.42.16
  1. 테스트 코드에 브레이크 포인트 추가

이제 비교 코드의 보이지 않은 부분을 탐험하기 위한 첫번째 과정으로 브레이크 포인트를 걸어보겠습니다.

이 코드를 보시면 21번째 라인에서 XCTAssertEqual 함수를 사용합니다. IDE에서 해당 라인의 21 숫자 부분을 마우서 커서로 클릭하시면 브레이크 포인트를 잡을 수 있습니다.

스크린샷 2022-06-09 오후 4.38.26
  1. testColorSucceed 테스트 코드 실행

2번의 과정을 통해 브레이크 포인트를 잡은 후 테스트 코드를 실행하면 21번째 라인에서 실행 과정이 일시적으로 멈추게 됩니다.

  1. Swift 소스를 disassembly 하기

위의 상태에서 코드의 깊은 부분으로 들어가기란 쉽지 않습니다. 위의 코드를 assembly 코드로 표시할 수 있어야 하며 그 방법을 disassembly라고 말합니다.

Xcode 메뉴 > Debug > Debug Workflow > Always Show Disassembly 를 선택합니다.

swift의 코드는 사라지고 assembly 코드가 아래와 같이 나타납니다.

![스크린샷 2022-06-09 오후 2.32.39](/Users/jaychoi/Desktop/스크린샷 2022-06-09 오후 2.32.39.png)

기본적인 디버깅 준비가 되었습니다. 다음 단락에서는 assembly에 익숙하지 않은 분들을 위한 설명을 진행하겠습니다.

3.2 assembly 코드 설명

위의 코드(캡션 혹은 소제목 추가)는 다음과 같습니다. 106라인을 기준으로 설명합니다.

  • 106 라인의 -> 은 메모리에 진행중인 코드의 현재 위치입니다. breakpoint가 걸려있는 곳입니다.
  • 0x12cd152bf는 코드가 위치한 메모리 번지입니다.
  • <...> 안의 숫자는 현재 서브루틴(함수로 불러도 무방합니다)인 testColorSucceed의 시작 메모리 번지로부터 현재 코드가 얼마나 떨어져 있는지 나타납니다.
  • jmp는 어셈블리 코드(명령어)입니다.
  • 0x12cd152c1는 jmp명령어로 이동할 메모리 번지입니다.
    • 현재는 바로 다음 라인으로 이동합니다. 크게 의미는 없습니다.
  • 마지막은 자동으로 만들어지는 주석입니다. 109라인의 경우 실제 코드를 유추할 수 있습니다.

3.3 assembly 명령어

디버깅 과정에서는 객체, 여기서는 주로 UIColor의 정보를 알기 위해 직접 변수에 접근할 수 없습니다. 모든 값은 메모리 번지와 CPU의 register를 통해서 접근해야 합니다. 메소드와 함수간의 이동도 단순하지가 않죠. 이 단락에서는 분석 과정을 진행할 때 사용하는 lldb의 콘솔 명령어를 간단히 소개합니다.

  • register read
    • CPU의 각 레지스터에 저장된 값들을 확인할 수 있습니다.
  • po
    • 메모리 번지나 레지스터의 레퍼런스를 이용하여 객체의 description을 출력할 수 있습니다.
  • si - thread step-inst
    • assembly 명령어를 한 스텝씩 이동할 수 있습니다. call(x86_64 명령어)이나 br(arm64 명령어)와 같은 서브루틴(함수)분기 명령어를 만나면 분기안으로 이동합니다.
  • ni - thread next-inst
    • assembly 명령어를 한 스텝씩 이동할 수 있습니다. si와 달리 서브루틴 분기 명령을 만나면 분기로 이동하지 않고 분기 실행 후 다음 assembly 명령어로 이동합니다.

3.4 assembly 분석

이제 assembly 코드를 실행 순서대로 분석하면서 보이지 않은 UIColor간의 구체적인 비교 과정을 알아보겠습니다.

(순서에 맞게 자세히 항목도 추가하면서 설명)

  • 작성중 -

3.5 UIColor 비교 분석

disassembly를 통해 UIColor간의 동일 테스트의 실제 진행 방식을 알아보고, 어떤 내용으로 인해 성공해야할 테스트가 실패하게 되는지를 확인합니다.

  • 작성중 -

4. 이슈분석

이 단락에서는 분석방법 단락을 통해 얻은 디버깅 지식을 이용해서 테스트 실행 과정에서 동일한 색상임에도 틀리다는 결과가 나오는 경우에 실제로 코드의 내부에서는 어떤 상황이 발생하고 있는지 분석해보겠습니다.

  • 작성중 -

5. 해결방안

이제 여러분은 어떤 이유로 인해 특정상황에서 UIColor의 비교테스트가 실패하는지 알게 되었습니다. 이 단락에서는 해당 문제가 발생한 경우 정상적인 테스트를 진행할 수 있게 도와주는 UIColor에 대한 지식과 팁을 안내합니다.

  • 작성중 -
profile
iOS Developer

0개의 댓글