XCTest 소요시간 단축하기

얼마전까지 XCTest를 유닛 테스트와 통합 테스트로만 구분하고 있었는데, 유닛 테스트 또한 런타임 작동 방식에 따라 application test와 library test로 나눌 수 있다는걸 알게 됐다. 둘의 차이점과 쓰임새를 정리했다.

Application Test vs Library Test

유닛 테스트 타켓의 Host Application 항목에서 앱 executable을 선택하면 application test가 되고 None을 선택하면 library test가 된다. Xcode에서 유닛 테스트 타겟을 만들 때 초기 설정을 변경하지 않았다면 기본값으로 Host Application이 선택된다. 그래서 지금까지는 별 생각없이 대부분의 테스트를 application test로 짜고 있었다. 그러나 목적에 따라 이 둘을 구분해서 사용하면 분명한 이점이 있다.

Application Test

iOS 앱과 관련된 부분(UIViewController, UIWindow, UIView 등)을 검사하는 유닛 테스트는 application test 번들에 포함시켜야 한다. Application test 번들은 테스트를 실행하기 위한 호스트 앱이 필요하고, 호스트 앱을 설치할 iOS 시뮬레이터도 필요하다.

Application test의 단점은 아래와 같다.

  • 앱 라이프사이클이 돌게 되는데 이때 타이밍 이슈가 발생해 테스트 결과가 달라질 수 있다.
  • 시뮬레이터는 동시에 하나의 호스트 앱만 실행할 수 있기 때문에 application test 타겟은 하나의 시뮬레이터에서 병렬(parallel) 테스팅이 작동하지 않는다.

Library Test

Library test는 앱과 상관없는 로직을 테스트하기 위해 사용한다. Library test는 호스트 앱을 시뮬레이터에 설치할 필요가 없어서 테스트가 더 빠르고 이따금씩 동작이 불안정한 시뮬레이터의 영향을 덜 받는다.

반면 library test에서는 작동하지 않는 iOS API가 좀 있다. 그런 경우에는 application test에서 검사해야 한다.

  • 유닛 테스트에서 유저의 버튼 탭을 흉내내기 위해 사용하는 UIControlfunc sendActions(for controlEvents: UIControl.Event)는 동작하지 않는다.
  • 임의의 UIWindow를 keyWindow로 만들 수 없다. 호스트 앱도 없기 때문에 UIWindow 자체가 없다. 그래서 UIView를 렌더링한 다음 검사하는 snapshot test가 불가능하다.
  • Keychain 관련 API

(등등 더 있을수 있다.)

Library Test를 쓰면 좋은 이유

Application test는 호스트 앱의 프로세스에 주입되기 때문에 시뮬레이터에 앱이 설치되고 실행(app launch)까지 된 후에야 테스트 코드가 돌기 시작한다. 반면에 library test는 앱에 의존하지 않아서 앱 설치 없이 iOS 시뮬레이터 내 xctest executable($DEVELOPER_DIR/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest)이나 macOS의 xctest 커맨드라인의 프로세스(/usr/bin/xctest/)에서 실행된다. 따라서 application test를 library test로 전환하면 전환한 타겟 갯수 * 앱 설치 및 실행 소요시간 만큼의 테스트 시간을 줄일 수 있다. 또한 호스트 앱을 기다릴 필요 없어 병렬로 빠르게 테스팅 할 수 있다.

Library Test로 전환하는 방법

먼저 테스트 타겟의 General 탭에 가서 Host Application을 None으로 변경하고 테스트를 실행해본다. 문제가 발생하면 다음과 같이 해결한다.

1. 테스트 번들이 실행되지 않을 때

아래와 같은 런타임 에러를 낸다.

Library not loaded. 
(중략)
Reason: image not found

Application test는 호스트 앱에 주입이 되기 때문에 앱 타겟에 링크된 라이브러리를 사용할 수 있다. 하지만 library test는 독립적인 프로세스에서 실행이 되기 때문에 테스트 타겟에 외부 의존성을 직접 링크해줘야한다. 코코아팟을 사용하고 있다면 Podfile을 열어 앱이 사용하고 있는 외부 의존성을 테스트 타겟에도 추가해준다.

2. 테스트 케이스가 실패할 때

실행은 되지만 테스트케이스가 실패한다면 iOS 앱에 의존성 있는 API를 사용했을 확률이 크다. 따라서 타겟을 분리하고 library test에서 실패하는 테스트는 application test로 옮긴다. 즉 iOS 앱이 필요한 테스트만 application test에서 실행하고 나머지는 library test로 실행하면 테스트 소요시간을 줄일 수 있다

이 글의 초안을 읽어준 류성두 님에게 고마움을 전합니다.

Tags: xctest, application test, library test, ios testing  

Reducing Time Spent for an XCTest

The XC test was only divided into unit tests and integration tests until recently, and I found out that unit tests can also be divided into application tests and library tests according to how they operate runtime. I summarized the differences and the usage of the two.

Application Test vs Library Test

If you select the app executable from the Host Application item from the unit test target, it becomes an application test, and if you select None, it becomes a library test. If you did not change the initial settings when creating a unit test target, the Host Application will be selected as default. So until now, I made most of the tests as application tests without giving them much thought. But if you use them according to each purpose there is a clear advantage.

Application Test

A unit test that tests parts related to the iOS app(UIViewController, UIWindow, UIView, etc) should be included in the application test bundle. The application test bundle requires a host app to run the test, as well as an iOS simulator that would install the host app.

The disadvantages of an Application test are as follows.

  • There is an app lifecycle, and a timing issue might occur, given different test results.
  • Since the simulator can only run one host app, the application target cannot be tested using parallel testing on one simulator.

Library Test

A library test is used to test logic unrelated to the app. Library tests do not require the host apps to be installed on the simulator, so they are less affected by simulators with unstable behavior.

However, there are some iOS APIs that do not work in library tests. In that case, it should be checked in the application tests.

  • UIControl’s func sendActions(for controlEvents: UIControl.Event), which is used to imitate the button taps of a user in a unit test does not work.
  • You cannot make a random UIWindow into a keyWindow. There is no UIWindow because there is no host app. Therefore, snapshot testing which tests after rendering a UIView is impossible.
  • Keychain-related API

(and there could be more.)

Why It’s Good to Use the Library Test

Because the Application test is injected into the host app process, the test code only begins to spin after the app is installed and launched within the simulator. On the other hand, the library test is not dependent on the app, so it runs on xctest executable($DEVELOPER_DIR/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest) within the iOS simulator without installing the app, or within the xctest commandline process(/usr/bin/xctest/). Therefore, switching the application test to a library test can reduce the time spent in testing by the number of targets switched * time spent for installing and running the app. Also, testing can be done in parallel since it doesn’t have to wait for the host app.

How to Switch to a Library Test

First, go to the General tab of the test target, change the Host Application to None and then try running the test.
If a problem occurs, solve it as follows.

1. 테스트 번들이 실행되지 않을 때

아래와 같은 런타임 에러를 낸다.

Library not loaded. 
(중략)
Reason: image not found

Application test는 호스트 앱에 주입이 되기 때문에 앱 타겟에 링크된 라이브러리를 사용할 수 있다. 하지만 library test는 독립적인 프로세스에서 실행이 되기 때문에 테스트 타겟에 외부 의존성을 직접 링크해줘야한다. 코코아팟을 사용하고 있다면 Podfile을 열어 앱이 사용하고 있는 외부 의존성을 테스트 타겟에도 추가해준다.

2. 테스트 케이스가 실패할 때

실행은 되지만 테스트케이스가 실패한다면 iOS 앱에 의존성 있는 API를 사용했을 확률이 크다. 따라서 타겟을 분리하고 library test에서 실패하는 테스트는 application test로 옮긴다. 즉 iOS 앱이 필요한 테스트만 application test에서 실행하고 나머지는 library test로 실행하면 테스트 소요시간을 줄일 수 있다

1. When the test bundle isn’t running

It makes a runtime error as seen below.

Library not loaded. 
(...)
Reason: image not found

Because application tests are injected into the host app, they can use libraries that are linked to the app target. But because library tests run on an independent process, we need to directly link an external dependency on the test target. If you’re using CocoaPods, open Podfile and add the external dependency that the app is using into the test target as well.

2. When the test case fails

If the test runs but the test case fails, it’s highly likely that a dependent API is used on the iOS app. Therefore, the target is separated and the test that fails in the library test is moved to the application test. In other words, running tests that need iOS apps in the application test and running the rest as library test can reduce time spent in testing.

이 글의 초안을 읽어준 류성두 님에게 고마움을 전합니다.

Tags: xctest, application test, library test, ios testing  

재택근무 한달

한국의 코로나바이러스 전파 상황이 악화될 무렵 싱가폴에 들어온 바람에 입사하자마자 의무로 2주간 재택 근무를 해야했고 현지 상황을 고려해 현재는 자발적으로 재택 근무를 하고 있다. 그 사이 스프린트를 하나 끝마쳤다. 새로운 나라에서 새로운 팀에 합류하자마자 원격 근무를 하게 돼서 어려운 상황이 될 수 있었지만 나름 성공적으로 새 회사에 적응하고 업무를 익힐 수 있었다. 화상회의 덕분이다.

코로나 때문에 시작된 강제 재택근무

Grab 입사 첫날에 반나절 정도 오리엔테이션을 받고 집에 돌아갔다. 다음날 아침에 출근 준비를 하는데 HR에서 연락이 왔다. ‘혹시 싱가폴 들어오기 전에 어느 나라에서 왔냐.’ ‘미국에 쭉 있다가 한국에서 하룻밤 자고 들어왔다.’ ‘알았다, 잠깐만 기다려라.’

(10분 뒤)

‘방금 회의에서 정해진 내용인데 한국에서 입국한 직원들은 사무실 출근하지 말고 2주 동안 재택근무하기로 했다. 오늘부터 절대 사무실 오지 마라.’

아니, 나 아직 매니저 얼굴도 못봤고 팀원들도 못만나봤는데 2주나 재택근무를 하라고? 인사는 고사하고 업무를 제대로 할수나 있을지 걱정이 됐다. 원격으로 제대로 근무를 해본 적이 없었기 때문에 의사소통이 제대로 될지도 의구심이 들었고 아직 회사 업무 방식이나 코드도 잘 모르는데 원격으로 답답하게 어떻게 헤쳐나가야하나 싶었다.

모든 미팅은 줌Zoom으로 동시 진행된다

첫 주는 사실상 팀이 프로젝트를 시작하기도 전이라 공식 업무가 없었다. 개발에 필요한 거 설치하고 코드 실행해보고 RIBs 튜토리얼 보고 ‘그랩버디’한테 이것저것 물어보면서 보냈다. 그리고 다음주 월요일이 되어 정식으로 킥오프를 했다. 미팅은 줌으로 진행돼서 나도 집에서 참석할 수 있었다. 이어진 스프린트 계획 미팅도 줌으로 참여했다. 그런데 나만 원격으로 미팅에 참여한건 아니었다. 사무실에 있는 사람들은 한 회의실에 모여 컴퓨터 한 대를 대표로 연결하고, 출장 가있거나 재택 근무를 하는 사람들은 각자 위치에서 접속했다. 이렇게 본격적인 화상회의는 처음이었는데 은근히 매끄럽게 진행돼서 놀랐다. 미팅을 주도하는 사람이 화면 공유 기능으로 자신의 컴퓨터 화면을 공유하고 이걸 다같이 보면서 돌아가며 발언을 했다.

Grab은 줌이 사내 시스템과 연동이 되어있어서 회의 일정을 잡으면 회의실만 예약되는게 아니라 줌 링크도 자동으로 생성이 되어 참석자들에게 공유된다. 그리고 모든 회의실에는 마이크가 설치돼있고 회의를 시작할때는 항상 줌 미팅을 연결한다. 심지어 15분짜리 데일리 스탠드업도 무조건 줌 미팅을 생성한다. 평균적으로 2 ~ 3명 정도는 원격으로 참석하는 느낌이다. 어느날은 매니저가 느즈막히 출근하면서 오는 길에 폰으로 줌에 접속해서 데일리 스탠드업에 참여하기도 했다.

원격 근무를 가능하게 해주는 일상화된 화상회의

모든 회의를 화상으로 병행하니 회사에 안가도 필요한 회의에 참석할 수 있어서 업무에 지장이 없었다. 코드 구조나 사내 개발 프로세스, 툴 사용하는 방법 등도 동료가 줌으로 친절히 설명해주었다. 화상회의가 생활화되니 다른 나라 오피스에서 일하는 동료들과 협업하는 것도 어색하지 않다. 다같이 비대면 회의에 익숙해지니 가능한 것 같다. 무엇보다, 언제든 개인의 필요에 따라 원격 근무를 할 수 있는 여건이 마련되어 있는 것이 좋다. 그래서 사람들은 3 ~ 4주 자리를 비우면서 모국으로 휴가를 가고 그 곳에서 일주일 이상 원격 근무를 하기도 한다.

강제 재택근무 기간이 끝나고 이틀 출근했었는데 다음날 회사 직원이 코로나 확진을 받아서 그 이후로는 팀이 자발적 재택 근무까지 하고 있는 중이다. 화상회의가 일상화되어 있으니 팀 전체가 재택 근무를 하더라도 큰 불편없이 평소처럼 일할 수 있는 것 같다.

#줌_주식사러_갑니다

Tags: work from home, zoom