출시·고도화 중
Dart 안내서 · 5/6
Dart는 예외(exception)로 오류를 알리고, try/catch로 처리합니다. Java와 달리 함수가 던질 수 있는 예외를 선언하지 않으며(검사 예외가 없음), 어떤 예외든 잡지 않으면 위로 전파됩니다. 이 장에서는 예외를 던지고 잡는 방법, assert, 그리고 package:test와 dart test로 테스트를 쓰고 실행하는 방법을 살펴봅니다.
Dart의 핵심 라이브러리는 문제를 두 종류로 나눕니다.
| 종류 | 뜻 | 예 |
|---|---|---|
Exception | 예상할 수 있고 처리해야 하는 실패 | FormatException, IOException, TimeoutException |
Error | 프로그램의 버그, 보통 잡지 않고 고칩니다 | RangeError, TypeError, StateError, ArgumentError |
입력 형식이 틀렸거나 네트워크가 끊긴 상황은 Exception으로 표현하고 호출한 쪽에서 처리합니다. 리스트 범위를 벗어난 접근처럼 코드가 잘못된 경우는 Error이며, 잡아서 숨기기보다 원인을 고쳐야 합니다. throw는 어떤 객체든 던질 수 있지만, 실제 코드에서는 Exception이나 Error를 구현한 타입만 던지는 것이 관례입니다.
class InsufficientFundsException implements Exception {
InsufficientFundsException(this.balance, this.amount);
final int balance;
final int amount;
@override
String toString() => '잔액 부족: 잔액 $balance, 요청 $amount';
}
class Account {
int _balance = 0;
int get balance => _balance;
void deposit(int amount) {
if (amount <= 0) {
throw ArgumentError.value(amount, 'amount', '0보다 커야 합니다');
}
_balance += amount;
}
void withdraw(int amount) {
if (amount > _balance) {
throw InsufficientFundsException(_balance, amount);
}
_balance -= amount;
}
}on 타입으로 특정 예외만 잡고, catch (e, s)로 예외 객체와 스택 추적을 받습니다. finally 블록은 예외가 났든 안 났든 항상 실행되므로 자원 정리에 씁니다. 잡은 예외를 기록만 하고 다시 위로 던지려면 rethrow를 씁니다. rethrow는 원래 스택 추적을 보존합니다.
int parseAge(String input) {
try {
final age = int.parse(input);
if (age < 0) throw FormatException('음수 나이', input);
return age;
} on FormatException catch (e) {
print('형식 오류: ${e.message}');
return 0;
} catch (e, stackTrace) {
print('예상하지 못한 오류: $e');
print(stackTrace);
rethrow;
} finally {
print('parseAge 끝');
}
}
void main() {
print(parseAge('42')); // 42
print(parseAge('abc')); // 형식 오류 출력 후 0
final account = Account()..deposit(100);
try {
account.withdraw(500);
} on InsufficientFundsException catch (e) {
print(e); // 잔액 부족: 잔액 100, 요청 500
}
}async 함수에서도 같은 문법을 씁니다. await한 Future가 오류로 끝나면 그 자리에서 예외가 던져지므로 try/catch로 감싸면 됩니다.
Future<String> loadConfig() async {
try {
return await readRemoteConfig();
} on TimeoutException {
return '{}'; // 시간 초과면 기본 설정 사용
}
}위 코드에서 TimeoutException은 dart:async에 있고, readRemoteConfig는 설정을 읽어 오는 임의의 비동기 함수입니다.
assert(조건, 메시지)는 조건이 거짓이면 AssertionError를 던집니다. 개발 중에만 확인하는 가정을 적을 때 씁니다. assert는 기본적으로 꺼져 있고, dart run --enable-asserts로 실행하거나 dart test로 테스트를 돌릴 때, Flutter 디버그 모드에서 켜집니다. 프로덕션 빌드에서는 평가조차 되지 않으므로, 사용자 입력 검증처럼 항상 필요한 검사에는 쓰면 안 됩니다.
class Percent {
Percent(this.value) : assert(value >= 0 && value <= 100, '0~100 사이여야 합니다');
final int value;
}Dart의 표준 테스트 도구는 package:test입니다. dart create로 만든 프로젝트에는 이미 들어 있고, 없다면 dart pub add --dev test로 추가합니다. 테스트 파일은 test/ 디렉터리에 두고 이름을 _test.dart로 끝냅니다.
// test/account_test.dart
import 'package:test/test.dart';
import 'package:bank/account.dart';
void main() {
group('Account', () {
late Account account;
setUp(() {
account = Account()..deposit(100);
});
test('입금하면 잔액이 늘어난다', () {
account.deposit(50);
expect(account.balance, equals(150));
});
test('잔액보다 많이 출금하면 예외', () {
expect(
() => account.withdraw(500),
throwsA(isA<InsufficientFundsException>()),
);
});
test('0 이하 입금은 ArgumentError', () {
expect(() => account.deposit(0), throwsArgumentError);
});
});
}expect(실제값, 매처)가 기본 형태이고, 매처 자리에 값을 그대로 쓰면 equals로 비교합니다. 자주 쓰는 매처는 equals, isTrue, isNull, contains, hasLength, greaterThan, isA<T>(), throwsA, 그리고 throwsArgumentError나 throwsStateError 같은 이미 만들어진 예외 매처입니다. setUp과 tearDown은 각 테스트 앞뒤에, setUpAll과 tearDownAll은 그룹 전체 앞뒤에 한 번 실행됩니다. 테스트 함수를 async로 쓰면 테스트 실행기가 그 Future가 끝날 때까지 기다립니다.
dart test # test/ 아래 모든 테스트
dart test test/account_test.dart # 파일 하나
dart test --name "입금" # 이름에 맞는 테스트만
dart test --reporter expanded # 테스트마다 한 줄씩 출력
dart test --coverage=coverage # 커버리지 데이터 수집실패한 테스트는 기대값과 실제값, 실패한 위치를 함께 보여 줍니다. CI에서는 dart analyze, dart format --set-exit-if-changed ., dart test를 차례로 돌리면 기본적인 품질 검사가 됩니다. Flutter 프로젝트는 같은 package:test API를 바탕으로 한 flutter test를 씁니다.
Dart는 예상 가능한 실패를 Exception, 버그를 Error로 구분합니다. on으로 특정 예외를 잡고, catch (e, s)로 스택 추적을 받으며, finally로 정리하고, rethrow로 다시 던집니다. assert는 개발 중에만 확인하는 가정에 쓰고, 테스트는 package:test의 test, group, expect로 쓴 뒤 dart test로 실행합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.