본문으로 건너뛰기

[openclaw] Vitest 모듈 로딩 최적화 및 테스트 병렬성 증대를 통한 CI 성능 개선

PR 링크: openclaw/openclaw#59033 상태: Merged | 변경: +None / -None

들어가며

CI(Continuous Integration) 환경에서 테스트는 코드 품질을 보장하는 핵심 요소입니다. 하지만 테스트 스위트가 커질수록 빌드 시간이 길어져 개발 생산성을 저해하는 병목 현상이 발생하곤 합니다. OpenClaw 프로젝트의 이번 PR은 이러한 문제를 해결하기 위해 Vitest 테스트 환경의 모듈 로딩 방식을 최적화하고, 테스트 병렬 처리 능력을 향상시켜 CI 성능을 개선하는 데 초점을 맞추고 있습니다.

주요 목표는 다음과 같습니다:

  1. 불필요한 모듈 재로딩 방지: 각 테스트 케이스 실행 전에 반복적으로 수행되던 브라우저 및 Matrix 관련 모듈의 재로딩을 제거하여 테스트 준비 시간을 단축합니다.
  2. 병렬 처리 능력 향상: checks-fast-extensions 작업의 샤드(shard) 수를 5개에서 6개로 늘려 병렬 테스트 실행 능력을 증대시킵니다.
  3. CI Manifest 일관성 유지: 새로운 샤드 수에 맞춰 CI Manifest가 올바르게 생성되도록 조정합니다.

이 글에서는 코드 변경 사항을 상세히 분석하고, 이러한 변경이 왜 성능 향상으로 이어지는지, 그리고 어떤 일반적인 교훈을 얻을 수 있는지 살펴보겠습니다.

코드 분석

이번 PR의 핵심 변경 사항은 주로 테스트 파일 내에서 Vitest의 모듈 로딩 및 테스트 실행 설정을 최적화하는 데 집중되어 있습니다. 특히 beforeEachbeforeAll 훅의 사용 방식 변경 및 vi.resetModules() 호출 제거가 눈에 띕니다.

1. Vitest 모듈 로딩 최적화 (beforeEach -> beforeAll)

여러 테스트 파일에서 공통적으로 발견되는 패턴은 beforeEach 훅 내에서 vi.resetModules()를 호출하고 모듈을 다시 가져오는(import) 방식입니다. 이는 각 테스트가 독립적인 환경에서 실행되도록 보장하지만, 동일한 모듈을 반복적으로 로딩하게 만들어 상당한 오버헤드를 발생시킵니다.

변경 전 (예시: control-service.plugin-disabled.test.ts)

-import { beforeEach, describe, expect, it, vi } from "vitest";
+import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";

// ... (생략)

describe("startBrowserControlServiceFromConfig", () => {
-  beforeEach(async () => {
-    mocks.ensureBrowserControlAuth.mockClear();
-    mocks.createBrowserRuntimeState.mockClear();
-    mocks.loadConfig.mockClear();
-    vi.resetModules();
-    ({ startBrowserControlServiceFromConfig } = await import("../control-service.js"));
-  });
+
+  beforeAll(async () => {
+    ({ startBrowserControlServiceFromConfig } = await import("../control-service.js"));
+  });
+
+  beforeEach(() => {
+    mocks.ensureBrowserControlAuth.mockClear();
+    mocks.createBrowserRuntimeState.mockClear();
+    mocks.loadConfig.mockClear();
+  });

  it("does not start the default service when the browser plugin is disabled", async () => {
    // ...
  });
});

변경 후 (예시: control-service.plugin-disabled.test.ts)

위 코드에서 볼 수 있듯이, beforeEach에서 수행되던 모듈 임포트(import) 로직이 beforeAll로 이동했습니다. beforeAll은 해당 describe 블록의 모든 테스트가 시작되기 전에 한 번만 실행됩니다. 따라서 startBrowserControlServiceFromConfig와 같은 함수는 테스트 전체에서 단 한 번만 로딩하면 됩니다.

beforeEach 훅은 이제 모듈 로딩 대신, 각 테스트 케이스 실행 전에 필요한 Mock 함수들의 상태만 초기화(mockClear())하는 역할만 수행합니다. 이는 테스트 격리성을 유지하면서도 반복적인 모듈 로딩으로 인한 불필요한 시간 낭비를 제거합니다.

이러한 변경은 extensions/browser/src/browser/control-service.plugin-disabled.test.ts, extensions/browser/src/browser/profiles-service.test.ts, extensions/browser/src/browser/server-context.existing-session.test.ts, extensions/browser/src/browser/server-lifecycle.test.ts, extensions/matrix/src/matrix/client-bootstrap.test.ts, extensions/matrix/src/onboarding.resolve.test.ts 등 여러 테스트 파일에 일관되게 적용되었습니다.

2. 테스트 병렬성 증대 (maxShards 변경)

scripts/test-planner/planner.mjs 파일에서는 CI 환경에서 테스트를 병렬로 실행하는 방식을 제어하는 로직이 수정되었습니다.

변경 전:

-    maxShards: 5,
+    maxShards: 6,

변경 후:

buildCIExecutionManifest 함수 내에서 checks-fast-extensions 작업에 대한 maxShards 값이 5에서 6으로 증가했습니다. 이는 해당 작업을 최대 6개의 샤드로 분할하여 병렬로 실행할 수 있음을 의미합니다. 더 많은 샤드는 더 많은 테스트를 동시에 실행할 수 있게 하여 전체 테스트 실행 시간을 단축시키는 효과를 가져옵니다.

이 변경은 test/scripts/test-planner.test.ts 파일의 관련 테스트에서도 검증되었습니다.

변경 전:

-    expect(manifest.shardCounts.extensionFast).toBeLessThanOrEqual(5);
+    expect(manifest.shardCounts.extensionFast).toBeLessThanOrEqual(6);

변경 후:

extensionFast 샤드의 최대 개수가 6으로 증가했음을 확인하는 테스트가 추가되었습니다. 이는 planner.mjs의 변경 사항이 의도한 대로 작동함을 보장합니다.

왜 이게 좋은가?

이 PR은 다음과 같은 이유로 좋은 최적화 및 개선이라고 할 수 있습니다:

  1. 실질적인 CI 시간 단축: Vitest의 vi.resetModules() 호출을 beforeAll로 옮김으로써, 각 테스트 파일마다 반복되던 모듈 로딩 오버헤드가 제거되었습니다. 이는 특히 테스트 파일 수가 많을 때 상당한 시간을 절약해 줍니다. 또한, checks-fast-extensions 작업의 샤드 수를 늘림으로써 병렬 처리 능력을 향상시켜 전체 테스트 실행 시간을 더욱 단축시킬 수 있습니다. 실제 성능 수치는 PR 설명에 명시적으로 포함되지 않았지만, 이러한 변경은 일반적으로 CI 빌드 시간을 수십 초에서 수 분까지 줄일 수 있는 잠재력을 가집니다.

  2. 테스트 유지보수성 향상: 모듈 로딩 로직을 beforeAll로 집중시키고 beforeEach는 상태 초기화에만 집중하게 함으로써 테스트 코드의 가독성과 유지보수성이 향상되었습니다. 각 테스트의 독립성은 vi.resetModules()를 제거하더라도 beforeEach에서 Mock 상태를 초기화함으로써 충분히 보장됩니다.

  3. 확장성 고려: maxShards 값을 증가시킨 것은 향후 테스트 스위트가 더 커지거나 병렬 처리 인프라가 확장될 때 더 효과적으로 대응할 수 있는 기반을 마련합니다. 이는 장기적인 관점에서 CI 시스템의 성능과 확장성을 고려한 설계입니다.

일반적인 교훈

  • 테스트 실행기(Test Runner)의 라이프사이클 이해: Vitest와 같은 테스트 실행기의 beforeAll, beforeEach, afterAll, afterEach 등의 훅을 언제 사용해야 하는지 정확히 이해하는 것이 중요합니다. 모듈 로딩이나 설정과 같이 한 번만 수행되어도 되는 작업은 beforeAll에, 각 테스트마다 독립적인 상태를 보장해야 하는 작업은 beforeEach에 배치해야 합니다. vi.resetModules()는 강력하지만 남용하면 성능 저하의 원인이 될 수 있습니다.
  • 병렬 처리 능력 극대화: CI 환경에서는 가능한 한 많은 테스트를 병렬로 실행하여 전체 빌드 시간을 단축하는 것이 중요합니다. 테스트 플래너나 빌드 시스템에서 샤드(shard) 수를 동적으로 조절하거나 최적화하는 로직은 성능 향상에 크게 기여할 수 있습니다.
  • 측정하고 검증하라: PR 설명에 실제 성능 향상 수치가 포함되었다면 더욱 좋았겠지만, 이러한 종류의 최적화는 일반적으로 CI 파이프라인의 실행 시간을 측정하여 그 효과를 검증하는 것이 좋습니다. 변경 전후의 빌드 시간을 비교하여 개선 정도를 정량적으로 파악하는 것이 중요합니다.

결론

이번 PR은 Vitest 테스트 환경의 미묘하지만 중요한 최적화를 통해 CI 성능을 개선했습니다. 불필요한 모듈 재로딩을 제거하고 병렬 처리 능력을 향상시킴으로써 개발팀은 더 빠르고 효율적인 피드백 루프를 얻을 수 있게 되었습니다. 이는 코드 품질을 유지하면서도 개발 생산성을 높이는 데 크게 기여할 것입니다.

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글