그냥저냥

Dart/Flutter 테스트 | Counter 본문

개발기/Dart,Flutter

Dart/Flutter 테스트 | Counter

sync86 2025. 3. 4. 02:40
728x90
반응형

Dart 언어로 테스트 코드를 작성해보았다. 간단히 작성할 수 있는 Counter 예제이다. 나름대로 Counter 객체의 스펙을 아래와 같이 작성하였고 테스트 코드를 작성하였다.

 

Counter Specification

  • 최소, 최고값을 가지며, 이 값은 변경할 수 있어야 한다. 단, 객체를 생성할 때만 변경할 수 있다.
    • 최소의 값은 0이다.
    • 최고의 값은 255이다.
  • 증가, 감소를 할 수 있어야 한다.
  • 증가, 감소할 때 최소, 최고값의 범위를 벗어날 때 세부 사항은 아래와 같다.
    • 증가는 최고값을 가져야 한다.
    • 감소는 최소값을 가져야 한다.
  • 현재 상태의 값을 검증할 수 있어야 한다.

Test Driven으로 코드를 작성한 것은 아니다. Counter 객체 코드를 일부 작성하고 테스트 코드를 작성하여 테스트를 진행하며 필요한 부분을 추가로 구현하였다.

const int maxValue = 255;
const int minValue = 0;

// Counter 스펙!
// - 최소, 최고값을 가지며, 이 값은 변경할 수 있어야 한다. 단, 객체를 생성할 때만 변경할 수 있다.
//   > 최소의 값은 {  0}이다.
//   > 최고의 값은 {255}이다.
// - 증가, 감소를 할 수 있어야 한다.
// - 증가, 감소할 때 최소, 최고값의 범위를 벗어날 때 세부 사항은 아래와 같다.
//   > 증가는 최고값을 가져야 한다.
//   > 감소는 최소값을 가져야 한다.
// - 카운터 값을 검증할 수 있어야 한다.

class Counter {
  final int _min;
  final int _max;

  int _value = minValue;

  Counter({int min = minValue, int max = maxValue})
    : _min = min, _max = max;

  void increment() => (_value >= _min && _value < _max) ? (_value++) : (_max);

  void decrement() => (_value >  _min && _value < _max) ? (_value--) : (_min);

  bool isA(int value) => _value == value;

  bool isNotA(int value) => !isA(value);
}

간단하게 구현한 Counter 객체이지만 increment(), decrement() 메소드를 구현하면서 아래의 고민거리가 있었다. 우선은 이중 각각 MAX, MIN 값으로 대체한다는 선택을 하였다.

  • increment() 메소드 - 값의 범위가 올바르지 않을 경우 (_value 값의 범위가 MIN 값 이하, 또는 MAX 값 초과했을 경우)
    • MAX 값으로 대체
    • 예외발생
  • decrement() 메소드 - 값의 범위가 올바르지 않을 경우 (_value 값의 범위가 MIN 값 미만 또는 MAX 값 초과했을 경우)
    • MIN 값으로 대체
    • 예외 발생

다만 카운터가 범위가 넘어갔을 때 이후 상황을 진행하지 못하도록 제한하고 싶다면 상황에 따라서는 예외를 발생한다는 선택지도 고려할 수 있을 것 같다.

 

아래는 테스트 코드이다.

import 'package:signin/counter.dart';
import 'package:test/test.dart';

void main() {
  group('Counter', () {
    test('Counter 초기화한 직후의 값은 0으로 예상된다.', () async {
      final counter = Counter();
      expect(counter.isA(0), isTrue);
    });

    test('Counter 초기화하고, 한번 증가했을 때, 값은 1(으)로 예상된다.', () async {
      final counter = Counter();
      counter.increment();
      expect(counter.isA(1), isTrue);
    });

    test('Counter 초기화하고, 한번 감소했을 때, 값은 0(으)로 예상된다.', () async {
      final counter = Counter();
      counter.decrement();
      expect(counter.isA(0), isTrue);
    });

    test('Counter 초기화하고, 두번 감소했을 때, 값은 0(으)로 예상된다.', () async {
      final counter = Counter();
      counter.decrement();
      counter.decrement();
      expect(counter.isA(0), isTrue);
    });

    test('Counter 초기화하고, 한번 증가 한번 감소했을 때, 값은 0(으)로 예상된다.', () async {
      final counter = Counter();
      counter.increment();
      counter.decrement();
      expect(counter.isA(0), isTrue);
    });

    test('Counter 초기화하고, 두번 증가 한번 감소했을 때, 값은 1(으)로 예상된다.', () async {
      final counter = Counter();
      counter.increment();
      counter.increment();
      counter.decrement();
      expect(counter.isA(1), isTrue);
    });

    test('Counter 초기화하고, 최대까지 증가한다면, 값은 $maxValue(으)로 예상된다.', () async {
      final counter = Counter();
      for (int i = 0; i < maxValue; i++) {
        counter.increment();
      }
      expect(counter.isA(maxValue), isTrue);
    });
  });
}

 

다만, 위의 스펙에서도 언급하였듯이 Counter 객체에서 현재 상태의 값을 검증하는 것을 isA(...), isNotA(...)와 같이 별도의 메소드로 구현하였다. 

import 'package:signin/counter.dart';
import 'package:test/test.dart';

void main() {
  group('Counter', () {
    test('...생략...', () async {
      expect(counter.value, 0);
    });
  });
}

 

이렇게 별도의 메소드를 구현한 이유는 _value 값을 공개하여 테스트에서 아래와 같이 코드를 작성하여 검증할 수 있었으나 아래의 이유로 별도의 메소드를 구현하여 분리하였다. (Dart 언어에서 객체안에 _value로 _로 시작하면 비공개 변수라는 것을 의미한다.)

  • _value는 Counter 객체의 소유이다.
    • 엄밀히 말자자면 Counter 클래스로 생성된 인스턴스 counter 객체의 소유이다.
      • 만약 otherCounter 이라는 새로운 객체를 생성했다면, otherCounter도 별도의 _value에 대한 소유권을 가진다.
      • counter의 _value와 otherCounter의 _value는 다른 것이다.
    • 내부 상태를 변경은 Counter 객체만이 할 수 있다.
    • 단, Counter 객체(Counter 클래스로 생성된 인스턴스)는 아래와 같은 서비스를 제공한다.
      • 내부 상태 즉, _value의 값을 증가, 감소할 수 있다.
      • 내부 상태 즉, _value의 값을 검증 또는 확인할 수 있다.
  • _value라는 이름는 기분에 따라 바뀔 수 있다.
    • 적어도 객체지향프로그래밍 언어이면 isA(...), isNotA(...)와 같은 메소드는 인터페이스 또는 추상 클래스로 구현을 강제할 수 있는 수단이 있다.

테스트의 결과는 아래와 같다.

$ dart test
00:00 +0: test/counter_test.dart: Counter 초기화한 후 카운터의 값은 0으로 예상된다.
00:00 +1: test/counter_test.dart: Counter 초기화한 후 카운터의 값은 0으로 예상된다.
00:00 +1: test/counter_test.dart: Counter 초기화하고, 한번 증가했을 때, 카운터의 값은 1(으)로 예상된다.
00:00 +2: test/counter_test.dart: Counter 초기화하고, 한번 증가했을 때, 카운터의 값은 1(으)로 예상된다.
00:00 +2: test/counter_test.dart: Counter 초기화하고, 한번 감소했을 때, 카운터의 값은 0(으)로 예상된다.
00:00 +3: test/counter_test.dart: Counter 초기화하고, 한번 감소했을 때, 카운터의 값은 0(으)로 예상된다.
00:00 +3: test/counter_test.dart: Counter 초기화하고, 두번 감소했을 때, 카운터의 값은 0(으)로 예상된다.
00:00 +4: test/counter_test.dart: Counter 초기화하고, 두번 감소했을 때, 카운터의 값은 0(으)로 예상된다.
00:00 +4: test/counter_test.dart: Counter 초기화하고, 한번 증가 한번 감소했을 때, 카운터의 값은 0(으)로 예상된다.
00:00 +5: test/counter_test.dart: Counter 초기화하고, 한번 증가 한번 감소했을 때, 카운터의 값은 0(으)로 예상된다.
00:00 +5: test/counter_test.dart: Counter 초기화하고, 두번 증가 한번 감소했을 때, 카운터의 값은 1(으)로 예상된다.
00:00 +6: test/counter_test.dart: Counter 초기화하고, 두번 증가 한번 감소했을 때, 카운터의 값은 1(으)로 예상된다.
00:00 +6: test/counter_test.dart: Counter 초기화하고, 최대까지 증가한다면, 카운터의 값은 255(으)로 예상된다.
00:00 +7: test/counter_test.dart: Counter 초기화하고, 최대까지 증가한다면, 카운터의 값은 255(으)로 예상된다.
00:00 +7: All tests passed!
테스트 코드나 결과에서 보면 Counter 객체에 isNotA() 메소드도 구현되어 있는데  isNotA() 메소드에 대해서는 테스트를 하지 못했다. 누락된 것 같다. 간단한 예제인데 왜 이렇게 복잡하게 할 필요가 있을까?라는 생각을 할 수 있지만 테스트를 연습하기 위해 나름대로 좀 더 디테일하게 생각한 것을 기록하였다.
그런데 같은 테스트 내용이 2번씩 보여지는 것은 이유를 알 수 없다. 이렇게 출력되는 것이 정상인 것인지 아니면 옵션을 추가해야 하는지 검색을 해보았으나 아직 찾을 수 없었다. 좀 더 검색해봐야겠다. ㅠ.ㅠ 
728x90
반응형

'개발기 > Dart,Flutter' 카테고리의 다른 글

Cubit과 BLoC가 공존하는 이유!  (0) 2024.10.17
요즘 BLoC를 훑어보게 된 이유?  (0) 2024.10.16
BLoC Concepts - 훑어보기 (3)  (0) 2024.10.15
BLoC Concepts - 훑어보기 (2)  (0) 2024.10.14
BLoC Concepts - 훑어보기 (1)  (0) 2024.10.13