일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Unity
- c언어
- github
- jupyter lab
- C++
- C# delegate
- Flutter
- 도커
- gitlab
- c# winform
- git
- 깃
- 다트 언어
- docker
- 포인터
- Data Structure
- Algorithm
- 플러터
- vim
- HTML
- c#
- C언어 포인터
- 유니티
- Houdini
- c# 윈폼
- c# 추상 클래스
- 구조체
- Python
- jupyter
- dart 언어
- Today
- Total
nomad-programmer
[Dart] Unit Test (유닛 테스트) 본문
플러터에서 쓰이는 테스트 방식은 크게 3가지이다.
- 유닛 테스트
- 위젯 테스트
- 통합 테스트
유닛 테스트는 메소드나 클래스처럼 작은 단위를 테스트할 때 쓰인다. 그리고 외부에 의존하지 않는 테스트를 말한다.
보통 IO처리, 데이터베이스 접근하는 것을 외부에 의존한다고 하는데, 외부에 의존하는 경우는 Mockito 같은 테스트 프레임워크를 사용해 테스트한다.
다트 기본 테스트 프레임워크
test 라이브러리를 추가한다.
// pubspec.yaml
dev_dependencies:
test: any
test 디렉토리에다 테스트 파일(simple_test.dart)을 생성한다. 파일 이름은 항상 "test"로 끝나야 한다.
// test/simple_test.dart
import 'package:test/test.dart';
void main() {
// 어떤 테스트를 할지 설명하고, 이 안에 있는 테스트를 실행한다.
test('should be lowercase', () {
String hello = 'Hello World';
// 테스트를 실행했을 때의 기대값과 실제값을 비교한다.
expect(hello.toLowerCase(), equals('hello world'));
});
}
테스트 코드는 main()에 선언해야한다. 테스트 코드는 크게 test() 함수와 expect() 함수로 구성되어 있다.
- test() 함수 : 테스트를 실행할 때 사용하는 함수
- expect() 함수 : 테스트 실행값과 기대값을 비교하는 함수
테스트 실행 방법은 다음과 같다.
// 소문자 변경 테스트
flutter test test/simple_test.dart
/* 결과
00:03 +1: All tests passed!
*/
Dart의 테스트 함수
- test
- 테스트에 대한 설명과 실제 테스트 코드를 적는다.
- 시간 제한(timeout) 이나 테스트 환경(browser, OS) 등도 명시할 수 있다.
- expect
- expect(실제값, 기대값)
- 테스트의 기대값과 실제값을 비교
- 다른 언어의 assert 와 동일하다고 보면 된다.
- setup
- 테스트를 시작하기 전에 설정을 해준다.
- 테스트 단위 하나마다 실행된다. (test() 함수 하나가 테스트 단위 하나이다. 한 파일에 여러 test() 함수가 있으면 여러번 실행된다)
- setupAll
- 테스트를 시작하기 전에 설정을 해준다.
- 파일 하나에 한번만 실행된다. (데이터베이스 설정할 때 사용하기 용이하다)
- teardown
- 테스트를 마치고 할 작업을 정해준다.
- 테스트 단위 하나마다 실행된다. (setup() 함수와 동일)
- teardownAll()
- 테스트를 마치고 할 작업을 정해준다.
- 파일 하나에 한번만 실행된다. (setupAll() 함수와 동일)
이것뿐만 아니라 테스트 시간 제한, 비동기 테스트 등 많은 것들을 테스트할 수 있다.
테스트 실패
// test/simple_test.dart
import 'package:test/test.dart';
void main() {
// 어떤 페스트를 할지 설명하고, 이 안에 있는 테스트를 실행한다.
test('should be lowercase', () {
String hello = 'Hello World';
// 테스트를 실행했을 때의 기대값과 실제값을 비교한다.
expect(hello.toLowerCase(), equals('hello world'));
});
test('should contain name', () {
String hello = 'Hello World, ethan';
expect(hello.contains('john'), equals(true));
});
}
// 실행
flutter test test/simple_test.dart
/* 결과
00:04 +1 -1: should contain name [E]
Expected: <true> // 기대값
Actual: <false> // 실제값
package:test_api expect
test\simple_test.dart 15:5 main.<fn>
00:04 +1 -1: Some tests failed.
*/
테스트 실패시, 기대값과 실제값이 어떻게 다른지 보여준다.
그룹(Group) 테스트
덧셈, 뺄셈, 스퀘어 함수를 생성하여 테스트를 해보자. group() 함수를 이용하면 여러 테스트를 묶어서 테스트할 수 있다.
// lib/calculator.dart
class Calculator {
int add(int x, int y) => x + y;
int minus(int x, int y) => x - y;
int square(int x) => x * x;
}
// test/calculator_test.dart
import 'package:flutter_unit_test/calculator.dart';
import 'package:test/test.dart';
void main() {
group('calculator', () {
Calculator calc = Calculator();
test('add should be equal to a + b', () {
expect(calc.add(5, 8), equals(13));
});
test('minus should be equal to a - b', () {
expect(calc.minus(10, 5), equals(5));
});
test('square should be equal to a * a', () {
expect(calc.square(5), equals(25));
});
});
}
// 실행
flutter test test/calculator_test.dart
/* 결과
00:02 +3: All tests passed!
*/
비동기(Asynchronous) 테스트
플러터 개발할 때, 비동기 데이터를 확인할 일이 많다. Future 객체를 어떻게 테스트하는지 알아보자.
// test/asynchronous_test.dart
import 'package:test/test.dart';
void main() {
test('new Future.value() returns the value', () {
Future<int> value = Future<int>.value(10);
expect(value, equals(10));
});
}
// 실행
flutter teset test/asynchronous_test.dart
/* 결과
00:03 +0 -1: new Future.value() returns the value [E]
Expected: <10>
Actual: <Instance of 'Future<int>'>
package:test_api expect
test\asynchronous_test.dart 7:5 main.<fn>
00:03 +0 -1: Some tests failed.
*/
테스트를 실패하였다. 기대값은 <10> 인데, 실제값은 <Future<int>> 객체라고한다. 그렇다면 기대값을 Future객체로 바꿔주고 다시 테스트를 해보자.
// test/asynchronous_test.dart
import 'package:test/test.dart';
void main() {
test('new Future.value() returns the value', () {
Future<int> value = Future<int>.value(10);
expect(value, equals(Future<int>.value(10)));
});
}
// 실행
flutter test test/asynchronous_test.dart
/* 결과
00:03 +0 -1: new Future.value() returns the value [E]
Expected: <Instance of 'Future<int>'>
Actual: <Instance of 'Future<int>'>
package:test_api expect
test\asynchronous_test.dart 7:5 main.<fn>
00:03 +0 -1: Some tests failed.
*/
이번에도 테스트를 실패하였다. 기대값과 실제값 모두 Future의 인스턴스지만 같은 인스턴스가 아니라서 실패했다.
Future를 테스트할 때는, expect(실제값, 기대값) 중 기대값을 "completion" 으로 해줘야 한다.
completion은 Future가 완료될 때까지 테스트를 종료하지 않도록 한다.
// test/asynchronous_test.dart
import 'package:test/test.dart';
void main() {
test('new Future.value() returns the value', () {
Future<int> value = Future<int>.value(10);
expect(value, equals(completion(10)));
});
}
// 실행
flutter test test/asynchronous_test.dart
/* 결과
00:03 +1: All tests passed!
*/
테스트가 성공적으로 완료된것을 확인할 수 있다.
이메일과 주민등록번호 테스트 예제
주민등록번호는 앞에 생년월일, 뒤에는 성별 및 주소로 되어있다. 이를 확인하는 정규식을 만들어 해당 정규식이 올바르게 작동하는지 테스트해보자.
// lib/field_validator.dart
class FieldValidator {
static bool validateSocialSecurityNumber(String input) {
if (input.isEmpty) {
return false;
}
Pattern pattern =
r'^[0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|[3][01])-([1-4][0-9]{6})';
RegExp exp = RegExp(pattern);
if (exp.hasMatch(input)) {
return true;
}
return false;
}
}
입력 필드를 확인하는 클래스이고 그 안에 주민등록번호를 확인하는 정적 메소드가 존재한다. 이 메소드가 잘 작동되는지 다음과 같이 테스트를 진행해본다.
// test/field_validator_test.dart
import 'package:flutter_unit_test/field_validator.dart';
import 'package:test/test.dart';
void main() {
group('field validator test', () {
test('validate social security number', () {
final String socialNumber1 = '901213-2110332';
expect(FieldValidator.validateSocialSecurityNumber(socialNumber1), true);
final String socialNumber2 = '950728-2235385';
expect(FieldValidator.validateSocialSecurityNumber(socialNumber2), true);
final String socialNumber3 = '983907-2139853';
expect(FieldValidator.validateSocialSecurityNumber(socialNumber3), false);
});
});
}
// 실행
flutter test test/field_validator_test.dart
/* 결과
00:04 +1: All tests passed!
*/
해당 메소드가 잘 작동하는 것을 볼 수 있다. 이번에는 이메일 유효성 체크 메소드를 테스트하는 예제를 보도록하자.
이메일은 @를 중심으로 문자와 숫자 _ 등이 포진되어있다.
// lib/field_validator.dart
class FieldValidator {
static bool validateEmail(String email) {
if (email.isEmpty) {
return false;
}
Pattern pattern = r'^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$';
RegExp exp = RegExp(pattern);
if (exp.hasMatch(email)) {
return true;
}
return false;
}
}
이메일 유효성 정규식 클래스를 작성하였으며 이것을 다음과 같이 테스트해보자.
// test/field_validator_test.dart
import 'package:flutter_unit_test/field_validator.dart';
import 'package:test/test.dart';
void main() {
group('field validator test', () {
test('validateEmail', () {
const String email1 = 'yuna@gmail.com';
expect(FieldValidator.validateEmail(email1), true);
const String email2 = 'hana^@gmail.com';
expect(FieldValidator.validateEmail(email2), true,
reason: '^ is a not valid character');
});
});
}
// 실행
flutter test test/field_validator_test.dart
/* 결과
00:03 +0 -1: field validator test validateEmail [E]
Expected: <true>
Actual: <false>
^ is a not valid character
package:test_api expect
test\field_validator_test.dart 13:7 main.<fn>.<fn>
00:03 +0 -1: Some tests failed.
*/
expect() 함수의 reason 파라미터를 이용해 테스트가 실패했을 때, 그 원인을 적어줄 수 있다.
'Programming > Flutter' 카테고리의 다른 글
[Programming/Flutter] isolate : 백그라운드에서의 JSON 파싱 (0) | 2020.10.23 |
---|---|
[Programming/Flutter] CustomPainter를 이용한 차트(그래프) (0) | 2020.10.22 |
[Flutter] StatefulWidget 클래스 사용 시점 (0) | 2020.10.18 |
[Flutter] Reactive Programming (0) | 2020.10.17 |
[Flutter] 성능 개선을 위한 작은 실천 (0) | 2020.10.15 |