티스토리 뷰

반응형

모바일 앱에서 성능은 사용자 경험에 매우 중요합니다. 사용자는 앱이 끊김이나 프레임 누락 없이 부드러운 스크롤과 의미 있는 애니메이션을 제공하길 기대합니다. 다양한 기기에서 앱이 끊김(jank) 없이 잘 동작하는지 어떻게 확인할 수 있을까요?

두 가지 방법이 있습니다. 첫 번째는 서로 다른 기기에서 앱을 수동으로 테스트하는 방법입니다. 이 방법은 앱이 작을 경우에는 효과적일 수 있지만, 앱이 커질수록 번거로워집니다. 두 번째는 특정 작업을 수행하면서 성능 타임라인을 기록하는 통합 테스트를 실행하는 것입니다. 그런 다음 결과를 검사하여 앱의 특정 섹션을 개선해야 하는지 확인할 수 있습니다.

 

이 레시피에서는 특정 작업을 수행하면서 성능 타임라인을 기록하고, 그 결과 요약을 로컬 파일에 저장하는 테스트를 작성하는 방법을 배웁니다.

💡 참고 : 웹에서는 성능 타임라인 기록이 지원되지 않습니다. 웹에서 성능을 프로파일링하려면 웹 앱의 성능 디버깅 페이지를 참조하세요.

 

이 레시피는 다음 단계를 사용합니다:

1. 항목 목록을 스크롤하는 테스트 작성

2. 앱의 성능 기록

3. 결과를 디스크에 저장

4. 테스트 실행

5. 결과 검토


 

1. 항목 목록을 스크롤하는 테스트 작성하기

이 레시피에서는 항목 목록을 스크롤하는 동안 앱의 성능을 기록합니다. 성능 프로파일링에 집중하기 위해, 이 레시피는 위젯 테스트의 스크롤 테스트 레시피를 기반으로 합니다.

 

해당 레시피의 지침에 따라 앱을 만들고, 모든 기능이 예상대로 동작하는지 확인하는 테스트를 작성하세요.


2. 앱의 성능 기록하기

다음으로, 목록을 스크롤하는 동안 앱의 성능을 기록합니다. 이 작업은 IntegrationTestWidgetsFlutterBinding 클래스에서 제공하는 traceAction() 메서드를 사용하여 수행합니다.

 

이 메서드는 제공된 함수를 실행하고 앱의 성능에 대한 자세한 정보를 포함하는 Timeline을 기록합니다. 이 예제에서는 특정 항목이 화면에 나타날 때까지 목록을 스크롤하는 함수를 제공합니다. 함수가 완료되면, traceAction()은 Timeline이 포함된 보고서 데이터를 생성합니다.

 

여러 개의 traceAction을 실행하는 경우, 각 타임라인에 고유한 reportKey를 지정하세요. 기본적으로 모든 타임라인은 timeline이라는 키로 저장되지만, 이 예제에서는 scrolling_timeline이라는 키를 사용합니다.

await binding.traceAction(() async {
  // 항목이 나타날 때까지 스크롤합니다.
  await tester.scrollUntilVisible(
    itemFinder,
    500.0,
    scrollable: listFinder,
  );
}, reportKey: 'scrolling_timeline');

 

3. 결과를 디스크에 저장하기

이제 성능 타임라인을 캡처했으므로, 이를 검토할 방법이 필요합니다. Timeline 객체는 발생한 모든 이벤트에 대한 자세한 정보를 제공하지만, 결과를 편리하게 검토할 수 있는 방법은 제공하지 않습니다.

따라서 Timeline을 TimelineSummary로 변환합니다. TimelineSummary는 결과를 더 쉽게 검토할 수 있도록 다음 두 가지 작업을 수행할 수 있습니다:

  • Timeline에 포함된 데이터를 요약한 JSON 문서를 디스크에 작성합니다. 이 요약본에는 누락된 프레임 수, 가장 느린 빌드 시간 등의 정보가 포함됩니다.
  • 전체 Timeline을 JSON 파일로 디스크에 저장합니다. 이 파일은 Chrome 브라우저의 트레이싱 도구인 chrome://tracing에서 열 수 있습니다.

결과를 캡처하려면, test_driver 폴더에 perf_driver.dart라는 파일을 만들고 다음 코드를 추가하세요:

import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';

Future<void> main() {
  return integrationDriver(
    responseDataCallback: (data) async {
      if (data != null) {
        final timeline = driver.Timeline.fromJson(
          data['scrolling_timeline'] as Map<String, dynamic>,
        );

        // Timeline을 요약 가능한 TimelineSummary로 변환
        final summary = driver.TimelineSummary.summarize(timeline);

        // 전체 타임라인을 JSON 형식으로 디스크에 저장
        // 이 파일은 Chrome 브라우저에서 chrome://tracing 으로 열 수 있음
        // includeSummary를 true로 설정하면 요약본도 저장됨
        await summary.writeTimelineToFile(
          'scrolling_timeline',
          pretty: true,
          includeSummary: true,
        );
      }
    },
  );
}


integrationDriver 함수에는 responseDataCallback이라는 콜백을 커스터마이징할 수 있습니다. 기본적으로 이 함수는 결과를 integration_response_data.json 파일에 저장하지만, 이 예제처럼 요약을 생성하도록 변경할 수 있습니다.


 

4. 테스트 실행하기

성능 타임라인을 캡처하고 결과 요약본을 디스크에 저장하도록 테스트를 설정한 후, 다음 명령어로 테스트를 실행하세요:

flutter drive \
  --driver=test_driver/perf_driver.dart \
  --target=integration_test/scrolling_test.dart \
  --profile

 

--profile 옵션은 앱을 “디버그 모드”가 아닌 “프로파일 모드”로 컴파일하라는 의미입니다. 이렇게 하면 벤치마크 결과가 실제 최종 사용자가 경험하게 될 성능에 더 가깝습니다.

 

💡 참고 : 모바일 기기나 에뮬레이터에서 실행할 경우 --no-dds 옵션을 함께 사용하세요. 이 옵션은 Dart 개발 서비스(DDS)를 비활성화하는데, 이 서비스는 컴퓨터에서 접근할 수 없습니다.

 

5. 결과 검토하기

테스트가 성공적으로 완료되면, 프로젝트 루트의 build 디렉터리에 두 개의 파일이 생성됩니다:

1. scrolling_summary.timeline_summary.json: 요약 정보를 담고 있습니다. 텍스트 편집기로 열어서 포함된 정보를 확인할 수 있습니다. 좀 더 고급 설정에서는 테스트 실행 시마다 이 요약본을 저장하고, 결과를 그래프로 시각화할 수도 있습니다.

2. scrolling_timeline.timeline.json: 전체 타임라인 데이터를 포함하고 있습니다. 이 파일은 Chrome 브라우저의 트레이싱 도구(chrome://tracing)를 이용해 열 수 있습니다. 트레이싱 도구는 타임라인 데이터를 편리하게 확인하고 성능 문제의 원인을 파악할 수 있는 인터페이스를 제공합니다.

{
  "average_frame_build_time_millis": 4.2592592592592595,
  "worst_frame_build_time_millis": 21.0,
  "missed_frame_build_budget_count": 2,
  "average_frame_rasterizer_time_millis": 5.518518518518518,
  "worst_frame_rasterizer_time_millis": 51.0,
  "missed_frame_rasterizer_budget_count": 10,
  "frame_count": 54,
  "frame_build_times": [
    6874,
    5019,
    3638
  ],
  "frame_rasterizer_times": [
    51955,
    8468,
    3129
  ]
}

 

요약 예시

integration_test/scrolling_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:your_package/main.dart';

void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('Counter increments smoke test', (tester) async {
    // 앱을 빌드하고 첫 프레임을 트리거합니다.
    await tester.pumpWidget(
      MyApp(items: List<String>.generate(10000, (i) => 'Item $i')),
    );

    final listFinder = find.byType(Scrollable);
    final itemFinder = find.byKey(const ValueKey('item_50_text'));

    await binding.traceAction(() async {
      // 찾고자 하는 아이템이 나타날 때까지 스크롤합니다.
      await tester.scrollUntilVisible(
        itemFinder,
        500.0,
        scrollable: listFinder,
      );
    }, reportKey: 'scrolling_timeline');
  });
}


test_driver/perf_driver.dart

import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';

Future<void> main() {
  return integrationDriver(
    responseDataCallback: (data) async {
      if (data != null) {
        final timeline = driver.Timeline.fromJson(
          data['scrolling_timeline'] as Map<String, dynamic>,
        );

        // Timeline을 읽기 쉽고 이해하기 쉬운 TimelineSummary로 변환합니다.
        final summary = driver.TimelineSummary.summarize(timeline);

        // 전체 타임라인을 JSON 형식으로 디스크에 저장합니다.
        // 이 파일은 Chrome 브라우저에서 chrome://tracing 으로 열 수 있습니다.
        // 옵션으로 includeSummary를 true로 설정하면 요약도 함께 저장됩니다.
        await summary.writeTimelineToFile(
          'scrolling_timeline',
          pretty: true,
          includeSummary: true,
        );
      }
    },
  );
}

 

반응형
댓글
공지사항