[flutter] 플러터에서 bloc 패턴을 사용하여 테스트 가능한 앱 만들기

플러터(Flutter)는 Google에서 개발한 사용하기 쉬운 UI 프레임워크로, 모바일 앱을 빌드하기 위한 다양한 기능을 제공합니다. 이번에는 bloc 패턴을 사용하여 플러터 앱을 만들고, 이를 테스트할 수 있는 방법에 대해 알아보겠습니다.

1. bloc 패턴 소개

bloc은 Business Logic Component의 약자로, UI와 비즈니스 로직을 분리하여 코드의 가독성과 유지보수성을 높이는 디자인 패턴입니다. bloc 패턴은 특히 데이터의 상태 변화에 따라 UI를 업데이트해야 하는 경우에 유용합니다.

2. bloc 라이브러리 설치

플러터 앱에서 bloc 패턴을 사용하기 위해서는 blocflutter_bloc 라이브러리를 설치해야 합니다. pubspec.yaml 파일에 다음과 같이 추가합니다.

dependencies:
  flutter_bloc: ^7.0.0
  equatable: ^2.0.2

그리고 터미널에서 flutter pub get 명령어를 실행하여 라이브러리를 가져옵니다.

3. bloc 구현

간단한 카운터 앱을 만들어보겠습니다. 먼저, CounterBloc 클래스를 생성하고 flutter_bloc 라이브러리의 Bloc 클래스를 상속받습니다.

import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// 상태 클래스
class CounterState extends Equatable {
  final int count;
  
  CounterState(this.count);

  @override
  List<Object> get props => [count];
}

// bloc 클래스
class CounterBloc extends Bloc<dynamic, CounterState> {
  CounterBloc() : super(CounterState(0));

  @override
  Stream<CounterState> mapEventToState(event) async* {
    if (event == 'increment') {
      yield CounterState(state.count + 1);
    } else if (event == 'decrement') {
      yield CounterState(state.count - 1);
    }
  }
}

4. UI 구현

이제 bloc을 사용하여 UI를 구현해보겠습니다.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(),
      child: Scaffold(
        appBar: AppBar(title: Text('Counter App')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              BlocBuilder<CounterBloc, CounterState>(
                builder: (context, state) {
                  return Text('Count: ${state.count}');
                },
              ),
              SizedBox(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      BlocProvider.of<CounterBloc>(context).add('increment');
                    },
                    child: Text('Increment'),
                  ),
                  SizedBox(width: 20),
                  ElevatedButton(
                    onPressed: () {
                      BlocProvider.of<CounterBloc>(context).add('decrement');
                    },
                    child: Text('Decrement'),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

5. 테스트

이제 이 카운터 앱의 bloc 부분을 테스트할 수 있습니다. flutter_test 라이브러리를 사용하여 테스트 코드를 작성할 수 있습니다.

import 'package:flutter_test/flutter_test.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:mockito/mockito.dart';
import 'package:counter_app/counter_bloc.dart';

class MockCounterBloc extends MockBloc<CounterEvent, CounterState> implements CounterBloc {}

void main() {
  group('CounterBloc', () {
    CounterBloc counterBloc;

    setUp(() {
      counterBloc = MockCounterBloc();
    });

    test('initial state is correct', () {
      expect(counterBloc.initialState, CounterState(0));
    });

    blocTest<CounterBloc, CounterState>(
      'emits [CounterState(count + 1)] when increment event is added',
      build: () => counterBloc,
      act: (bloc) => bloc.add('increment'),
      expect: () => [CounterState(1)],
    );

    blocTest<CounterBloc, CounterState>(
      'emits [CounterState(count - 1)] when decrement event is added',
      build: () => counterBloc,
      act: (bloc) => bloc.add('decrement'),
      expect: () => [CounterState(-1)],
    );
  });
}

결론

이제 플러터에서 bloc 패턴을 사용하여 테스트 가능한 앱을 만들었습니다. 이렇게 구조화된 코드는 유지보수가 쉽고, 테스트도 용이하여 효율적인 앱 개발을 할 수 있습니다.

참고 자료: https://bloclibrary.dev/#/gettingstarted