Skip to main content
네이티브
입문네이티브React Native 플랫폼 · 23

React Native 외부 SDK 회귀 분리: 앱 코드와 SDK 변경을 어떻게 나누는가

앱 코드와 외부 SDK가 함께 바뀐 배포에서 같은 실패 기준을 고정하고, 이전·새 앱 코드와 이전·새 SDK 동작을 교차해 어느 축과 상호작용이 회귀를 만드는지 좁힌다.

마지막 검증 재검증 정책: 제품 버전 의존: 새 주요 버전마다 재검증
목차
표준·구현·측정·해석 표시는 무엇인가요?
  • 표준웹 표준이나 언어 명세가 정한 동작
  • 구현특정 기술이나 브라우저가 실제로 구현한 동작
  • 측정명시한 환경에서 직접 실행해 관찰한 결과
  • 해석앞선 근거에서 도출한 설계 판단
  • 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용

30초 요약

앱 코드와 외부 SDK가 같은 배포에서 바뀌면 “업데이트 뒤 깨졌다”는 시간 정보만으로 원인을 정할 수 없다. 먼저 사용자가 본 실패를 한 문장과 숫자로 고정하고, 앱 코드와 SDK 동작을 두 축으로 나눠 네 조합을 같은 환경에서 실행한다.

                         이전 SDK 동작       새 SDK 동작
이전 앱 코드                 A                   B
새 앱 코드                   C                   D
  • A는 마지막 정상 기준선이다.
  • D는 실제 변경을 함께 넣은 후보 상태다.
  • B는 SDK 축만, C는 앱 코드 축만 바꾼다.
  • D만 실패하면 두 변경의 상호작용을 먼저 조사한다.

각 칸은 앱 commit(소스 변경 기록) 이름만 적는 표가 아니다. JavaScript 잠금 파일, Android·iOS에서 실제 선택된 네이티브 SDK 버전, 빌드 구성, 플랫폼과 실패 결과를 함께 기록한다.

반복해서 기억할 문장은 이것이다.

같이 바뀐 두 축을 탓하지 말고, 한 칸씩 되돌려 같은 실패 기준으로 비교한다.

이 글을 관통하는 상황: 새 앱과 새 SDK에서만 화면 전환이 두 번 일어났다

22편의 전면 화면 앱은 SDK가 표시 시작을 알리면 오디오를 멈추고, 닫힘 뒤 다음 화면을 연다. 다음 배포는 앱의 SDK 연결 코드를 정리하면서 외부 SDK도 함께 올렸다. 이후 일부 흐름에서 다음 화면 열기 요청이 두 번 기록됐다.

팀에는 두 가지 추측이 생긴다.

  • “SDK 업데이트가 같은 닫힘 사건을 두 번 보냈다.”
  • “앱 코드 정리가 중복 방지 조건을 없앴다.”

둘 다 아직 가설이다. 실제 배포에는 두 변경이 함께 들어갔기 때문이다. 이 글의 질문은 하나다.

앱 코드와 SDK 변경이 함께 배포됐을 때 어느 축과 상호작용이 회귀를 만드는지 어떻게 좁히는가?

먼저 실패 기준과 비교 축을 고정한다

가상 fixture의 eventId는 원본 사건 순서를 화면에 보여 주는 진단용 이름이다. 실제 교정 조건으로 가정하지 않는다. 이 글의 교정은 현재 시도 식별값, 현재 SDK 객체와 현재 단계가 모두 맞는지를 확인한다. 실제 SDK가 고유 사건 ID를 공식 계약으로 제공할 때만 그 ID를 추가 중복 방지 근거로 쓴다.

회귀는 이전에 지원하던 동작이 변경 뒤 깨지는 일이다. 이 사례의 성공 기준은 “전면 화면 시도 하나마다 다음 화면 열기 요청이 정확히 한 번”이다. 단순히 “화면이 이상함”이라고 적으면 네 조합에서 같은 실패를 판정할 수 없다.

고정 입력        attempt-42 전면 화면 한 번
관찰 원본        dismissed 사건의 진단용 eventId 순서
제품 결과        다음 화면 열기 요청 횟수
성공             1회
실패             0회 또는 2회 이상

이 글의 baseline-app은 현재 시도·SDK 객체·단계를 모두 확인하고 첫 닫힘만 처리한다. changed-app은 코드 정리 중 이 단계 확인을 제거했다. single-dismiss-sdk는 닫힘 사건을 한 번 전달하고, replayed-dismiss-sdk는 같은 닫힘 사건을 두 번 전달한다.

이 이름들은 실제 Google SDK 버전이 아니다. 같은 사건 재전달과 앱 중복 방지의 상호작용을 반복 관찰하는 가상 fixture다. 실제 SDK가 무엇을 보냈는지는 뒤에서 플랫폼 원본 기록으로 따로 증명한다.

두 축을 한 칸씩 바꿔 실행한다

22편에서 사용한 React Native 프로젝트에서 외부 패키지를 설치하지 않고 App.tsx를 다음 코드로 교체한다. FIX_CHANGED_ADAPTERfalse인 첫 실행은 중복 방지 조건이 사라진 새 앱 코드를 재현한다.

실제 연결 층은 callback의 attemptId가 현재 시도인지, callback을 보낸 SDK 객체가 현재 보관한 객체인지, 현재 단계가 presenting인지 함께 확인한다. 첫 닫힘을 받으면 단계를 dismissed로 옮기고 객체 참조를 비운다. 따라서 같은 callback이 다시 와도 더는 허용 조건을 만족하지 않는다.

import React, { useState } from 'react'
import { Button, SafeAreaView, StyleSheet, Text, View } from 'react-native'
 
const FIX_CHANGED_ADAPTER = false
 
type AppRevision = 'baseline-app' | 'changed-app'
type SdkBehavior = 'single-dismiss-sdk' | 'replayed-dismiss-sdk'
 
type DismissedEvent = {
  eventId: string
  attemptId: string
  sdkHandle: FakeSdkHandle
}
 
type FakeSdkHandle = {
  instanceId: string
}
 
type AttemptPhase = 'showRequested' | 'presenting' | 'dismissed'
 
type CaseResult = {
  appRevision: AppRevision
  sdkBehavior: SdkBehavior
  rawEventIds: string[]
  ignoredCount: number
  continuationCount: number
  outcome: 'PASS' | 'FAIL'
}
 
type FakeSdkCallbacks = {
  onPresenting: () => void
  onDismissed: (event: DismissedEvent) => void
}
 
function wait(delayMs: number) {
  return new Promise<void>(resolve => {
    setTimeout(resolve, delayMs)
  })
}
 
async function showFakeSdk(
  sdkBehavior: SdkBehavior,
  sdkHandle: FakeSdkHandle,
  callbacks: FakeSdkCallbacks,
) {
  const event = {
    eventId: 'dismiss-42',
    attemptId: 'attempt-42',
    sdkHandle,
  }
 
  await wait(30)
  callbacks.onPresenting()
 
  await wait(30)
  callbacks.onDismissed(event)
 
  if (sdkBehavior === 'replayed-dismiss-sdk') {
    await wait(30)
    callbacks.onDismissed(event)
  }
}
 
async function runCase(
  appRevision: AppRevision,
  sdkBehavior: SdkBehavior,
): Promise<CaseResult> {
  const currentAttemptId = 'attempt-42'
  const loadedSdkHandle: FakeSdkHandle = { instanceId: 'sdk-42' }
  let currentSdkHandle: FakeSdkHandle | null = loadedSdkHandle
  const rawEventIds: string[] = []
  let ignoredCount = 0
  let continuationCount = 0
  let phase: AttemptPhase = 'showRequested'
 
  const shouldRequireCurrentAttempt =
    appRevision === 'baseline-app' || FIX_CHANGED_ADAPTER
 
  await showFakeSdk(sdkBehavior, loadedSdkHandle, {
    onPresenting() {
      phase = 'presenting'
    },
    onDismissed(event) {
      rawEventIds.push(event.eventId)
 
      const isCurrentDismissal =
        event.attemptId === currentAttemptId &&
        currentSdkHandle !== null &&
        event.sdkHandle === currentSdkHandle &&
        phase === 'presenting'
 
      if (shouldRequireCurrentAttempt && !isCurrentDismissal) {
        ignoredCount += 1
        return
      }
 
      phase = 'dismissed'
      currentSdkHandle = null
      continuationCount += 1
    },
  })
 
  return {
    appRevision,
    sdkBehavior,
    rawEventIds,
    ignoredCount,
    continuationCount,
    outcome: continuationCount === 1 ? 'PASS' : 'FAIL',
  }
}
 
const CASES: Array<[AppRevision, SdkBehavior]> = [
  ['baseline-app', 'single-dismiss-sdk'],
  ['baseline-app', 'replayed-dismiss-sdk'],
  ['changed-app', 'single-dismiss-sdk'],
  ['changed-app', 'replayed-dismiss-sdk'],
]
 
export default function App() {
  const [busy, setBusy] = useState(false)
  const [results, setResults] = useState<CaseResult[]>([])
 
  async function runMatrix() {
    setBusy(true)
    setResults([])
 
    try {
      const nextResults: CaseResult[] = []
 
      for (const [appRevision, sdkBehavior] of CASES) {
        nextResults.push(await runCase(appRevision, sdkBehavior))
      }
 
      setResults(nextResults)
    } finally {
      setBusy(false)
    }
  }
 
  return (
    <SafeAreaView style={styles.screen}>
      <Text style={styles.title}>외부 SDK 회귀 분리 fixture</Text>
      <Text>changed adapter fix: {FIX_CHANGED_ADAPTER ? 'on' : 'off'}</Text>
      <Button
        title={busy ? '네 조합 실행 중' : '네 조합 실행'}
        disabled={busy}
        onPress={() => void runMatrix()}
      />
 
      <View style={styles.results}>
        {results.map(result => (
          <View
            key={`${result.appRevision}:${result.sdkBehavior}`}
            style={styles.row}
          >
            <Text>{result.appRevision} × {result.sdkBehavior}</Text>
            <Text>원본 사건: {result.rawEventIds.join(' → ')}</Text>
            <Text>
              다음 화면 요청: {result.continuationCount} / 무시: {result.ignoredCount}
            </Text>
            <Text>결과: {result.outcome}</Text>
          </View>
        ))}
      </View>
    </SafeAreaView>
  )
}
 
const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, gap: 12 },
  title: { fontSize: 22, fontWeight: '700' },
  results: { gap: 10 },
  row: { borderWidth: 1, borderColor: '#8A8A8A', padding: 10, gap: 4 },
})

실행 전에 예측한다.

  1. 이전 앱 코드와 새 SDK 동작은 같은 닫힘 사건을 두 번 받아도 다음 화면을 몇 번 요청할까?
  2. 새 앱 코드와 이전 SDK 동작만 조합하면 실패할까?
  3. 새 앱 코드와 새 SDK 동작에서만 실패한다면 어느 한쪽의 단독 회귀라고 말할 수 있을까?

개발자 메뉴에서 Reload해 기준선을 새로 만든 뒤 네 조합 실행을 누른다. 약 0.5초 뒤 다음 결과를 확인한다.

baseline-app × single-dismiss-sdk
원본 사건: dismiss-42
다음 화면 요청: 1 / 무시: 0
결과: PASS
 
baseline-app × replayed-dismiss-sdk
원본 사건: dismiss-42 → dismiss-42
다음 화면 요청: 1 / 무시: 1
결과: PASS
 
changed-app × single-dismiss-sdk
원본 사건: dismiss-42
다음 화면 요청: 1 / 무시: 0
결과: PASS
 
changed-app × replayed-dismiss-sdk
원본 사건: dismiss-42 → dismiss-42
다음 화면 요청: 2 / 무시: 0
결과: FAIL

네 칸의 결과로 원인 범위를 좁힌다

결과를 “새 SDK에서 실패”라고 요약하면 중요한 두 칸을 잃는다. 이전 앱 코드는 새 SDK 동작에서도 통과했고, 새 앱 코드도 이전 SDK 동작에서는 통과했다. 두 변경이 만나는 D에서만 실패했다.

                         single-dismiss-sdk   replayed-dismiss-sdk
baseline-app                  PASS                   PASS
changed-app                   PASS                   FAIL

이 결과가 직접 증명하는 것은 다음 범위다.

  • 앱 코드 변경만으로는 고정 입력에서 실패하지 않았다.
  • SDK 사건 재전달만으로도 이전 앱에서는 실패하지 않았다.
  • 중복 방지 조건이 없는 새 앱 코드와 사건 재전달이 함께 있을 때 2회 요청이 생겼다.

따라서 이 fixture의 원인은 상호작용 회귀다. 실제 제품에서 SDK가 정말 같은 사건을 재전달했는지, 그 재전달이 문서화된 계약인지 SDK 결함인지는 아직 증명하지 않았다.

다른 결과 모양은 조사 방향을 다르게 만든다.

실패 모양먼저 좁힐 범위
C와 D가 모두 실패앱 코드 축
B와 D가 모두 실패SDK·네이티브 연결 축
D만 실패두 변경의 상호작용
A도 실패정상 기준선·입력·환경부터 다시 고정

표는 원인 문장을 자동으로 만들어 주지 않는다. 각 칸의 원본 사건, 앱 상태 이동과 사용자 결과가 같은 실패 기준으로 측정됐을 때만 이 해석을 쓴다. 실제 조합이 의존성 해석·컴파일·링크 단계에서 멈추면 그 칸은 FAIL이 아니라 NOT_RUN(제품 기준 미실행)으로 기록한다. 네 칸 모두 같은 제품 기준까지 실행된 경우에만 위 표의 축·상호작용 해석을 적용한다. NOT_RUN 자체는 해당 조합의 호환성 문제를 보여 주지만 D만 실패한 상호작용 증거는 아니다.

교정도 같은 네 칸으로 확인한다

이 제품은 같은 전면 화면 시도에서 다음 화면 열기를 한 번만 허용한다. 현재 시도·SDK 객체·단계가 모두 맞을 때만 첫 닫힘을 받아들이는 조건을 새 앱 코드에 복원한다. 상수를 바꾼다.

const FIX_CHANGED_ADAPTER = true

Reload 뒤 다시 네 조합 실행을 누른다. 네 칸 모두 다음 화면 요청: 1, PASS가 된다. 새 앱 코드와 재전달 동작을 함께 둔 칸은 두 번째 dismiss-42를 진단 기록에 남기되 이미 단계가 dismissed이므로 제품 행동에서는 무시한다.

이 교정이 모든 실제 SDK에 대한 보편 답은 아니다. SDK가 사건을 정확히 한 번만 보낸다고 보장하고 실제 구현이 이를 어겼다면 SDK 버전 고정과 공급자 오류 보고가 우선일 수 있다. 반대로 결제 확정·화면 전환처럼 중복 효과가 위험한 제품 행동은 SDK와 별개로 멱등하게 만드는 편이 안전하다. 멱등은 같은 입력이 다시 와도 최종 효과가 한 번만 남는 성질이다.

공식 변경 기록과 가상 재현을 섞지 않는다

Android 1.3.1의 공개 기록은 제한 접근 기능의 오류 수정(bug fix)과 성능 개선이다. 이것을 보고 “닫힘 사건 중복이 고쳐졌다”거나 “중복이 새로 생겼다”고 추론하지 않는다. 관찰한 플랫폼 원본 사건과 SDK 지원 답변처럼 직접 연결할 증거가 필요하다.

공개 변경 기록에서 가져올 것은 다음 조사 입력이다.

  • 정확한 SDK 버전과 공개 날짜
  • 제거·추가·동작 변경 API
  • 알려진 오류와 수정 범위
  • 최소 플랫폼·도구 버전
  • 마이그레이션 문서와 공식 예제 변화

가상 fixture는 앱 상태 규칙을 빠르게 재현하고, 공식 기록은 실제 후보 변경을 열거한다. 둘을 합쳐 실제 SDK 회귀라고 부르기 전에는 플랫폼 통합 실행이 필요하다.

선언이 아니라 실제로 선택된 버전을 고정한다

앱 소스만 바꾸면서 로컬에 남은 의존성이나 빌드 산출물을 재사용하면 네 칸의 축이 섞인다. 각 조합은 별도 작업 디렉터리나 독립된 지속적 통합(Continuous Integration, CI) 작업에서 다음 항목을 기록한다.

JavaScript SDK 연결 패키지는 네이티브 SDK를 JavaScript에서 호출하도록 감싼 패키지다. 해시값은 파일 내용이 같은지 비교하는 짧은 지문 값이다.

app commit / 변경 집합
React Native 정확한 버전
package-lock.json 해시값과 JavaScript SDK 연결 패키지 실제 버전
Android 선택 SDK 버전·선택 경로
iOS Podfile.lock의 SDK 버전
Debug 또는 Release 빌드 구성
플랫폼·운영체제·기기 모델
fixture 또는 공식 테스트 광고 식별값
원본 SDK 사건 → 공통 앱 사건 → 제품 효과

npm ci는 기존 잠금 파일과 package.json이 맞지 않으면 실패하고, 잠금 파일을 바꾸지 않는 고정 설치다. 기존 node_modules를 제거하므로 제품 작업 디렉터리가 아니라 각 조합의 소스를 따로 꺼내 둔 버릴 수 있는 작업 디렉터리나 CI에서 실행한다.

Android 조합은 앱 루트에서 다음처럼 실제 선택 버전을 확인할 수 있다.

cd android
./gradlew :app:dependencyInsight \
  --dependency ads-mobile-sdk \
  --configuration debugRuntimeClasspath
cd ..

iOS 조합은 앱 루트에서 잠금 파일을 존중해 설치한 뒤 선택 버전을 읽는다.

cd ios
bundle exec pod install
rg "Google-Mobile-Ads-SDK" Podfile.lock
cd ..

pod install은 이미 Podfile.lock에 있는 Pod를 그 고정 버전으로 설치한다. 특정 Pod를 의도적으로 새 버전으로 올릴 때만 pod update 대상 이름을 지정하고 잠금 파일 diff를 검토한다. 이름 없는 pod update로 모든 Pod를 함께 움직이면 SDK 축 하나를 시험한다는 전제가 깨진다.

플랫폼 원본 사건부터 제품 효과까지 한 줄로 연결한다

가상 fixture를 통과한 뒤 실제 SDK 통합에서는 네 층을 같은 시도 식별값으로 잇는다.

플랫폼 원본
  Android onAdShowed... / iOS adWillPresent...

React Native 연결 층
  attemptId + SDK 객체 정체성 + 원본 사건 이름

앱 상태
  showRequested / presenting / dismissed / showFailed

제품 효과
  오디오 정지 / 다음 화면 요청 / 재시도 화면

22편처럼 Android의 “표시됨”과 iOS의 “표시 예정”은 같은 원본 뜻이 아니다. 공통 presenting을 쓰더라도 원본 사건 이름을 함께 남긴다. 그래야 Android에서만 실패한 경우 iOS 로그와 억지로 같은 사건으로 합치지 않는다.

분석 기록에는 다음을 넣는다.

  • 앱 버전·배포 build 번호·commit
  • React Native와 외부 SDK의 실제 선택 버전
  • 플랫폼·운영체제와 기기 종류
  • attemptId, SDK 객체 진단 ID와 원본 사건 이름
  • 사건 순서와 제품 효과 횟수

광고 내용, 사용자 식별값과 타기팅 값은 기록하지 않는다. 실패를 좁히는 데 필요한 버전·순서·식별값만 남긴다.

실제 SDK 회귀는 공식 테스트 광고와 플랫폼 빌드로 닫는다

증거Android emulatoriOS Simulator지원 실기기
네 조합의 앱 계산가상 fixture가상 fixture회귀 재확인
실제 선택 SDK 버전Gradle 보고서Podfile.lock같은 commit·잠금 파일·선택 의존성에서 실기기 대상 산출물을 별도로 빌드해 확인
공식 테스트 광고 load·표시·닫힘확인확인테스트 기기 설정으로 확인
화면 소유자·background 복귀통제된 전환통제된 전환제품 지원 조건 최종 확인
네트워크·중개 광고 구성공식 테스트 설정공식 테스트 설정필요한 실제 조합 확인

React Native testing 안내의 종단간 테스트는 release 구성 앱을 device·simulator·emulator에서 사용자 관점으로 확인한다. JavaScript fixture의 PASS는 빌드에 실제 포함된 네이티브 SDK 코드, 화면, 네트워크와 기기 생명주기를 실행하지 않았으므로 이 증거를 대신하지 않는다.

운영 광고를 눌러 회귀를 확인하지 않는다. 공식 테스트 광고 단위나 등록한 테스트 기기를 사용한다. 실기기 검증도 개인 기기 접근 권한과 테스트 계정·데이터 경계를 먼저 갖춘 뒤 수행한다.

커밋 범위가 남으면 그때 이진 탐색한다

SDK 버전과 플랫폼 환경을 한 값으로 고정했는데도 여러 앱 commit 사이에서 정상·실패 경계가 남는다면 그때 이진 탐색을 쓴다. 자동 판정은 이 글의 “다음 화면 요청 1회”처럼 종료 코드로 구분할 수 있어야 한다.

다음 조건에서는 먼저 이진 탐색을 멈춘다.

  • 중간 commit이 빌드되지 않아 같은 실패를 실행할 수 없다.
  • commit마다 잠금 파일이나 SDK 실제 선택 버전이 함께 바뀐다.
  • 네트워크 응답·테스트 계정·기기 상태가 실행마다 달라진다.
  • 정상과 실패를 사람이 느낌으로만 판정한다.

이 경우 테스트 가능한 commit을 표시하거나 SDK 축을 먼저 고정한다. 이진 탐색은 원인 후보 commit을 찾는 도구이지 외부 SDK와 앱 코드의 상호작용을 자동 설명하는 도구가 아니다.

24편에서는 SDK 회귀와 달리 앱이 종료된 crash와 일정 시간 응답하지 않는 Android Application Not Responding(ANR, 앱 무응답)을 어떤 증거로 나눠 조사하는지 다룬다.

pull request에서 달라져야 할 행동

앱 코드와 외부 SDK를 함께 바꾸는 pull request에는 다음 표가 있어야 한다.

실패 기준       같은 입력에서 무엇을 숫자·상태로 판정하는가
정상 기준선     마지막으로 통과한 앱 commit·SDK·플랫폼 조합은 무엇인가
앱 코드 축      이전·새 변경 집합을 독립적으로 바꿀 수 있는가
SDK 축          선언이 아닌 실제 선택 버전을 독립적으로 바꿀 수 있는가
네 조합 결과    A·B·C·D의 원본 사건과 제품 효과는 무엇인가
공식 기록       공개 변경 기록의 어느 항목이 후보이며 무엇은 적혀 있지 않은가
플랫폼 증거     Android·iOS 원본 사건을 각각 보존했는가
교정 재검증     실패 칸과 나머지 세 칸을 모두 다시 실행했는가
실제 환경       공식 테스트 입력·release 빌드·지원 기기 경계를 구분했는가
관측 정보       앱·React Native·SDK 버전, attempt ID와 사건 순서를 복원할 수 있는가

검토자는 다음을 확인한다.

  • 업데이트 뒤 시작됐다는 이유만으로 SDK 또는 앱 코드 한쪽을 원인으로 단정하지 않는가?
  • 실패를 “화면 이상”이 아니라 반복 판정 가능한 결과로 고정했는가?
  • 앱 코드와 SDK 축 외의 React Native 버전·빌드 구성·입력·플랫폼을 같은 값으로 유지했는가?
  • package.json 선언만 보고 실제 Android·iOS SDK 버전을 생략하지 않는가?
  • 새 앱×새 SDK만 실패하는 상호작용을 한쪽 단독 회귀로 축약하지 않는가?
  • 공개 변경 기록에 없는 동작을 해당 SDK 버전의 공식 변화처럼 쓰지 않는가?
  • 교정 뒤 실패하던 칸만 아니라 네 칸 전체를 다시 확인했는가?
  • 가상 fixture 통과를 실제 SDK·화면·기기 통과로 확대하지 않는가?
  • 이진 탐색 전 SDK 버전과 실패 판정을 고정했는가?

한 장으로 다시 보기

문제       앱 연결 코드와 SDK를 함께 바꾼 뒤 다음 화면 요청이 2회 발생
오판       외부 화면 문제이므로 새 SDK만 원인이라고 결론
기준       attempt 하나마다 다음 화면 요청 정확히 1회
두 축      이전/새 앱 코드 × 이전/새 SDK 동작
관찰       새 앱×새 SDK에서만 2회, 나머지 세 칸은 1회
진단       한쪽 단독이 아니라 중복 방지 제거와 사건 재전달의 상호작용
교정       현재 시도·SDK 객체·단계를 확인해 제품 효과를 한 번만 적용
버전       잠금 파일·Gradle 선택 보고서·Podfile.lock으로 실제 값 기록
경계       공개 변경 기록·가상 fixture·실제 테스트 광고 증거를 분리
확장       SDK 축 고정 뒤 앱 commit 범위가 남을 때만 이진 탐색

외부 SDK 회귀를 좁히는 핵심은 많은 빌드를 무작정 만드는 것이 아니다. 같은 실패 기준을 유지한 채 앱 코드와 SDK 축을 한 칸씩 바꾸고, 각 칸이 정말 의도한 버전과 플랫폼 산출물을 사용했음을 증명하는 것이다.

스스로 확인할 질문

  1. 앱 코드와 SDK를 함께 바꾼 배포가 실패해도 한쪽 원인이 바로 확정되지 않는 이유는 무엇인가?
  2. 이전 앱·새 SDK와 새 앱·이전 SDK가 필요한 이유는 무엇인가?
  3. 새 앱·새 SDK 조합만 실패하면 어떤 상호작용을 먼저 조사해야 하는가?
  4. package.json 외에 Android와 iOS에서 실제 SDK 버전을 무엇으로 확인하는가?
  5. 공개 변경 기록, 가상 fixture와 실제 테스트 광고는 각각 무엇을 증명하는가?
  6. SDK 축을 고정하기 전에 git bisect를 시작하면 어떤 문제가 생기는가?

Active recall

기억에서 꺼내 보기

답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.

  1. 01앱 코드와 SDK 버전을 함께 바꾼 배포에서 실패가 시작됐다면 SDK가 원인이라고 결론내려도 될까?

    정답

    아니다. 이전·새 앱 코드와 이전·새 SDK 동작을 교차해 한 축만 바뀐 조합과 두 축이 함께 바뀐 조합을 같은 기준으로 비교한다.

    왜 그런가

    시간상 함께 들어온 변경은 원인 후보이지 단독 원인 증거가 아니다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  2. 02이전 앱·새 SDK와 새 앱·이전 SDK는 통과하지만 새 앱·새 SDK만 실패하면 무엇을 알 수 있을까?

    정답

    두 변경이 각각 단독으로는 실패를 만들지 않고 함께 있을 때 실패하는 상호작용 회귀라는 증거다.

    왜 그런가

    한 행이나 열 전체가 아니라 한 교차점만 실패한다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  3. 03package.json에 목표 JavaScript SDK 연결 패키지 버전이 적혀 있으면 Android와 iOS에서 실제 연결된 네이티브 SDK 버전도 확정됐을까?

    정답

    아니다. JavaScript 잠금 파일, Android의 선택된 의존성 보고서와 iOS Podfile.lock을 각 조합에서 함께 기록한다.

    왜 그런가

    React Native 패키지는 JavaScript와 양쪽 네이티브 의존성 선택을 모두 거칠 수 있다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  4. 04새 SDK 공개 변경 기록에 오류 수정이 적혀 있으면 관찰한 닫힘 사건 중복도 그 버전 변화 때문이라고 보아도 될까?

    정답

    아니다. 공개 변경 기록은 조사 입력이다. 같은 버전·플랫폼에서 원본 사건 순서를 직접 기록하고 재현해야 해당 회귀와 연결할 수 있다.

    왜 그런가

    공개 변경 기록과 제품 앱의 실제 실패는 서로 다른 증거다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  5. 05실패 배포와 정상 배포를 찾았으면 바로 전체 저장소에서 Git 이진 탐색을 시작하는 것이 첫 행동일까?

    정답

    먼저 실패 기준과 SDK·빌드 환경 한 축을 고정한다. 그 뒤 앱 소스 변경 기록 범위가 남았을 때 같은 자동 판정으로 이진 탐색한다.

    왜 그런가

    각 소스 변경 기록에서 SDK나 환경이 달라지면 이진 탐색이 서로 다른 속성을 판정한다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

출처와 검증 범위

아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.

  1. Upgrading to New VersionsReact Native · 공식 문서 · 확인 2026-08-09
  2. package-lock.jsonnpm Docs · 공식 문서 · 확인 2026-08-09
  3. npm-cinpm Docs · 공식 문서 · 확인 2026-08-09
  4. Viewing DependenciesGradle Documentation · 공식 문서 · 확인 2026-08-09
  5. pod install vs. pod updateCocoaPods Guides · 공식 문서 · 확인 2026-08-09
  6. GMA Next-Gen SDK for Android release notesGoogle for Developers · 공식 문서 · 확인 2026-08-09
  7. Set up GMA Next-Gen SDKGoogle for Developers · 공식 문서 · 확인 2026-08-09
  8. Google Mobile Ads SDK for iOS release notesGoogle for Developers · 공식 문서 · 확인 2026-08-09
  9. Set up Google Mobile Ads SDKGoogle for Developers · 공식 문서 · 확인 2026-08-09
  10. Full factorial designsNIST/SEMATECH e-Handbook of Statistical Methods · 공식 문서 · 확인 2026-08-09
  11. A Glossary of DOE TerminologyNIST/SEMATECH e-Handbook of Statistical Methods · 공식 문서 · 확인 2026-08-09
  12. git-bisect DocumentationGit · 공식 문서 · 확인 2026-08-09
  13. TestingReact Native · 공식 문서 · 확인 2026-08-09
  14. Enable test adsGoogle for Developers · 공식 문서 · 확인 2026-08-09
  15. Enable test adsGoogle for Developers · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.