본문 바로가기

Flutter/Skills

Unit Test

테스트 3종류

  1. Unit 테스트
  2. UI Widget 테스트
  3. Intergation 테스트 (통합 테스트)

Unit Test

메소드나 클래스처럼 작은 단위를 테스트할 때 쓰입니다.(내부테스트)

Http 통신 IO , DB 접근하여 데이터 가져오는 행위를 테스트(외부테스트) : Mockito 테스트 프레임워크 사용

 

Mockito 테스트 프레임워크

https://flutter-ko.dev/docs/cookbook/testing/unit/mocking

 

Mockito를 사용하여 의존성들에 대해 mock 객체 생성하기

어떠한 경우에는 단위 테스트가 웹 서비스나 데이터베이스로부터 데이터를 가져오는 역할을수행하는 특정 클래스에 의존하는 경우가 있습니다. 이럴 때는 다음과 같은 이유로 인해 테스트가

flutter-ko.dev

 

 

기본적인 설정, 테스트 방법

pubspec.yaml

dev_dependencies:
  flutter_test:
    sdk: flutter

⇒ flutter_test 는 test 패키지 와 호환 됩니다.

 

 

test/simple_test.dart

파일 생성 위치

import 'package:flutter_test/flutter_test.dart';

void main() {
	// 어떤 테스트를 할지 설명하고,안에 있는 테스트를 실행합니다. 
  test('should be lowercase', () { 
    String hello = "Hello World"; 

		// 테스트를 실행했을 때의 기대값과 실제값을 비교합니다.
    expect(hello.toLowerCase(), "hello world"); 
  });
}

테스트 코드는 main()에 적어주셔야합니다.

  • test()는 테스트를 실행할 때 쓰이는 함수고,
  • expect()는 테스트 실행값과 기대값을 비교하는 함수입니다.

이 테스트는 대문자 ⇒ 소문자로 잘 바뀌는지 확인해보는 테스트입니다.

flutter test test/simple_test.dart

or 클릭 혹은 단축키(ctrl + shift + R)

 

ctrl + shift + R

 

통과됨.

00:01 +1: All tests passed!

 


 

그럼 테스트할 때 쓰이는 기본적인 함수들을 알아보겠습니다.

다트의 테스트 함수

  • test
    • 테스트에 대한 설명과 실제 테스트 코드를 적습니다.
    • 시간 제한(timeout) 이나 테스트 환경 (브라우저, OS) 등도 적어줄 수 있습니다.
  • expect
    • expect(실제값, 기대값)
    • 테스트의 기대값과 실제값을 비교합니다.
    • 다른 언어의 assert 와 동일하다고 보시면 됩니다.
  • setup
    • 테스트를 시작하기 전에 설정을 해줍니다.
    • 테스트 단위 하나 하나마다 실행됨. ( test() 함수 1개 = 테스트 단위 1개. 한 파일에 여러 test() 가 있으면 여러번 실행됨 )
  • setupAll
    • 테스트 시작하기 전에 설정을 해줍니다.
    • 파일 하나에 한번만 실행됩니다. (데이터 베이스 설정할 때 쓰기 좋겠죠)
  • teardown
    • 테스트를 마치고 할 작업을 정해줍니다.
    • 테스트 단위 하나마다 실행됩니다 ( setup() 함수랑 동일합니다 )
  • teardownAll()
    • 테스트를 마치고 할 작업을 정해줍니다.
    • 파일 하나에 한번만 실행됩니다. ( setupAll() 함수랑 동일합니다 )

이 뿐만 아니라 테스트 시간 제한, 비동기 테스트 등 할 수 있는게 정말 많습니다.

자세한 건 다트 펍을 참고해주세요 (https://pub.dartlang.org/packages/test)

 


테스트 예제 1 - 테스트 실패 해보기

이번엔 테스트를 일부러 실패해 보겠습니다.

test/simple_test.dart

void main() {
  // 어떤 테스트를 할지 설명하고,안에 있는 테스트를 실행합니다.
  test('should be lowercase', () {
    String hello = "Hello World";

    // 테스트를 실행했을 때의 기대값과 실제값을 비교합니다.
    expect(hello.toLowerCase(), "hellKKKKK world");
  });
}

테스트 실패시 기대값과 실제값이 어떻게 다른지 보여줍니다.

 


테스트 예제 2 - 덧셈, 뺄쎔 함수 테스트 해보기

덧셈 뺄쎔을 하는 함수를 만들어보고 테스트를 해보겠습니다.

group() 함수로 여러 테스트를 묶어서 테스트 해보겠습니다.

 

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:test/test.dart';

void main() {
  
  group('calculator', () {
    Calculator cal = Calculator();

    test('add should be equal to a + b', () {
      expect(cal.add(20, 30), 50);
    });

    test('minus should be equal to a - b', () {
      expect(cal.minus(30, 20), 10);
    });

    test('square should be equal to a * a', () {
      expect(cal.square(10), 10 * 10);
    });
  });
  
}

3 케이스 모두 통과

 


테스트 예제 3 - 비동기 테스트 해보기

플러터 개발을 하다보면 비동기 데이터를 확인할 일이 많습니다.

퓨처를 어떻게 테스트 하는지 알아보겠습니다.

 

asynchronous_test.dart

void main() {

  test('new Future.value() returns the value', () {
    var value = Future.value(10);
    expect(value, 10);
  });
}

이대로 테스트를 실행하면 어떻게 될까요?

Expected: <10>
  Actual: <Instance of 'Future<int>'>

실패하네요.

expect()의 기대값을 바꿔주면 테스트를 통과할까요?

void main() {

  test('new Future.value() returns the value', () {
    var value = Future.value(10);
    expect(value, Future.value(10));

  });
}

또 실패합니다.

Expected: <Instance of 'Future<int>'>
  Actual: <Instance of 'Future<int>'>

둘 다 퓨처의 인스턴스지만 같은 인스턴스는 아니라서 실패했습니다.

 

퓨처를 테스트할때는 expect(실제값, 기대값) 중 기대값을 completion으로 해줘야합니다.

completion퓨처가 완료될 때까지 테스트를 종료하지 않도록 하죠.

테스트를 실행해볼께요.

void main() {
  test('new Future.value() returns the value with completion', () {
    var value = Future.value(10);
    expect(value, completion(10));
  });
}

테스트를 무사히 통과합니다.

여태까지 간단한 테스트를 해보았는데요.

이젠 좀 더 실용적인 예제를 테스트 해볼께요.

맞는 주민 번호와 이메일인지 확인하는 함수를 작성하고

테스트를 해보겠습니다.

 


2.이메일과 주민번호 테스트하기

2.1 테스트 예제 - 주민번호 확인하기 ( social security number validator )

주민 번호는 앞에는 생년월일, 뒤에는 성별 및 주소로 되어 있죠.

이를 확인하는 정규표현식을 작성해 보겠습니다.

 

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; // 유효한 주민번호면 true 리턴

    return false;
  }
  
}

정규표현식과 매칭되면 true를 반환하고 아니면 false를 반환합니다..

이제 함수를 테스트 해볼게요.

3가지 케이스를 테스트합니다.

  • 911222-2110332
  • 761031-1518312
  • 983015-1910312

911222-2110332 , 761031-1518312 은 true를 반환하고

983015-1910312 은 983015니 (30월이니) false를 반환해야겠죠.

정말 그런지 테스트 해볼께요.

 

test/field_validator_test.dart

void main() {
  group('field validator test', () {

    test('validate Social Security Number', () {

      final String socialNumber = "911222-2110332";
      expect(FieldValidator.validateSocialSecurityNumber(socialNumber), true);

      final String socialNumber2 = "761031-1518312";
      expect(FieldValidator.validateSocialSecurityNumber(socialNumber2), true);

      final String socialNumber3 = "983015-1910312";
      expect(FieldValidator.validateSocialSecurityNumber(socialNumber3), false); 
    });
  });
}

예상대로 테스트를 통과하네요.

이해를 위해 테스트를 좀 더 해볼께요.

이메일을 확인하는 함수를 작성하고 테스트 해보겠습니다.

 


2.2 테스트 예제 - 이메일 확인하기 ( email validator )

아래 코드를 봐주세요.

validateEmail()는 이메일 형식에 맞으면 true를 반환하고, 아니면 false를 반환하는 함수입니다.

 

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; // 유효한 이메일이면 true 리턴

    return false;
  }
  
}

테스트 케이스는 2개를 작성했습니다.

1. messi2010@gmail.com

2. ronaldo2022#@gmail.com (#이 들어있어서 비 유효)

 

 

test/field_validator_test.dart

import 'package:test/test.dart';
import '../src/field_validator.dart';

void main() {
  group('field validator test', () {

    test("validateEmail", (){
      const String email1 = "messi2010@gmail.com";

      expect(FieldValidator.validateEmail(email1), true); // 통과할거로 예상하고 통과

      const  String email2 = "ronaldo2022#@gmail.com";

      expect(FieldValidator.validateEmail(email2), true , reason: '# is a not valid character'); // 통과할 거로 예상했으나 통과하지 못하기에 test fail
    });
  });
}

 

테스트 실행결과

Expected: <true>
  Actual: <false>
# is a not valid character

예상대로 이메일 ronaldo2022#@gmail.com는 테스트를 통과하지 못하네요.

이번엔 expect() 에다 reason 을 적어줬습니다. reason을 적으면 테스트가 실패한 원인을 알려줍니다.

 

 

 

'Flutter > Skills' 카테고리의 다른 글

제네릭 <T> 를 사용할때, 착각하기 쉬운 경우  (0) 2024.11.26
Pagination  (0) 2024.11.05
gskinner  (0) 2022.11.22
Dio + Retrofit + JsonSerializable 통한 서버 통신  (0) 2022.11.18
Flutter Favorite program  (0) 2022.11.14