모바일 앱의 경우 성능은 사용자 경험에 매우 중요하다. 사용자는 앱이 "jank"(쟁크. 화면 버벅거림)로 알려진 끊김이나 건너뛰는 프레임 없이 부드러운 스크롤과 의미 있는 애니메이션을 갖기를 기대한다. 다양한 장치에서 앱에 버벅거림이 없는지 확인하는 방법은 무엇일까?
두 가지 옵션이 있다. 먼저 다른 기기에서 앱을 수동으로 테스트한다. 이 접근 방식은 보다 작은 앱에 적합할 수 반면에 앱 크기가 커지면 더 번거로워진다. 또는 특정 작업을 수행하고 성능 타임라인을 기록하는 통합 테스트를 실행한다. 그런 다음 결과를 검토하여 앱의 특정 섹션을 개선해야 하는지 여부를 결정한다.
이 레시피에서는 특정 작업을 수행하는 동안 성능 타임라인을 기록하고 결과 요약을 로컬 파일에 저장하는 테스트를 작성하는 방법을 알아보자.
이 레시피는 다음 단계를 사용한다.
1. 항목 목록을 스크롤하는 테스트 작성
2. 앱의 성능 기록
3. 결과를 디스크에 저장
4. 테스트 실행
5. 결과 검토
이 레시피에서는 항목 목록을 스크롤할 때 앱의 성능을 기록한다. 성능 프로파일링에 집중하기 위해 이 레시피는 위젯 테스트의 스크롤 레시피를 기반으로 한다.
해당 레시피의 지침에 따라 앱을 만들고 테스트를 작성하여 모든 것이 예상대로 작동하는지 확인한디.
다음으로 목록을 스크롤하면서 앱의 성능을 기록한다. IntegrationTestWidgetsFlutterBinding 클래스에서 제공하는 traceAction() 메소드를 사용하여 이 작업을 수행한다.
이 메서드는 제공된 기능을 실행하고 앱의 성능에 대한 자세한 정보와 함께 Timeline을 기록한다. 이 예제는 특정 항목이 표시되도록 항목 목록을 스크롤하는 기능을 제공한다. 함수가 완료되면 traceAction()는 Timeline을 포함한 보고 데이터 Map을 생성한다.
하나 이상의 traceAction을 실행할 때 reportKey를 지정해라. 기본적으로 모든 Timelines는 주요 Timeline을 저장와 함께 저장되며 예시에서 reportKey가 scrolling_timeline으로 변경된다.
await binding.traceAction(
() async {
// Scroll until the item to be found appears.
await tester.scrollUntilVisible(
itemFinder,
500.0,
scrollable: listFinder,
);
},
reportKey: 'scrolling_timeline',
);
이제 성능 타임라인을 캡처했으므로 이를 검토할 방법이 필요하다. Timeline 객체는 발생한 모든 이벤트에 대한 자세한 정보를 제공하지만 결과를 검토하는 편리한 방법은 제공하지 않는다.
따라서 를 Timeline를 TimelineSummary로 변환해라. TimelineSummary는 결과를 보다 쉽게 검토할 수 있는 두 가지 작업을 수행할 수 있다.
1. Timeline이 포함된 데이터를 요약하는 json 문서를 디스크에 작성한다. 요약에는 건너뛴 프레임 수, 가장 느린 빌드 시간 등에 대한 정보가 포함된다.
2. Timeline을 디스크에 json 파일로 저장한다. 이 파일은 chrome://tracing에 있는 Chrome 브라우저의 추적 도구로 열 수 있다.
결과를 캡처하려면 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']);
// Convert the Timeline into a TimelineSummary that's easier to
// read and understand.
final summary = driver.TimelineSummary.summarize(timeline);
// Then, write the entire timeline to disk in a json format.
// This file can be opened in the Chrome browser's tracing tools
// found by navigating to chrome://tracing.
// Optionally, save the summary to disk by setting includeSummary
// to true
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}
이 integrationDriver기능에는 사용자 정의할 수 있는 responseDataCallback이 있다. 기본적으로 결과를 integration_response_data.json 파일에 기록 하지만 이 예제와 같이 요약을 생성하도록 사용자 정의할 수 있다.
성능 Timeline을 캡처 하고 결과 요약을 디스크에 저장하도록 테스트를 구성한 후, 다음 명령을 사용하여 테스트를 실행한다.
flutter drive \ --driver=test_driver/perf_driver.dart \ --target=integration_test/scrolling_test.dart \ --profile
이 --profile옵션은 "디버그 모드"가 아닌 "프로필 모드"용으로 앱을 컴파일하여 벤치마크 결과가 최종 사용자가 경험하게 되는 것과 더 가깝다는 것을 의미한다.
참고) 모바일 장치 또는 에뮬레이터에서 실행할 때 --no-dds 명령어를 실행하자. 이 옵션은 컴퓨터에서 액세스할 수 없는 DDS(Dart Development Service)를 비활성화한다.
테스트가 성공적으로 완료되면 프로젝트 루트의 build 디렉터리에 두 개의 파일이 포함된다.
{
"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
]
}
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:scrolling/main.dart';
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Counter increments smoke test', (tester) async {
// Build our app and trigger a frame.
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 {
// Scroll until the item to be found appears.
await tester.scrollUntilVisible(
itemFinder,
500.0,
scrollable: listFinder,
);
},
reportKey: 'scrolling_timeline',
);
});
}
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']);
// Convert the Timeline into a TimelineSummary that's easier to
// read and understand.
final summary = driver.TimelineSummary.summarize(timeline);
// Then, write the entire timeline to disk in a json format.
// This file can be opened in the Chrome browser's tracing tools
// found by navigating to chrome://tracing.
// Optionally, save the summary to disk by setting includeSummary
// to true
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}