[Flutter] Using TDD with flutter widget Test and Provider

배연우·2023년 5월 17일
0

flutter

목록 보기
1/2

배운 점 요약

await tester.pumpWidget(
      MultiProvider(
        providers: [
          ChangeNotifierProvider<StudentQuizProvider>(
              create: (_) => StudentQuizProvider()),
              // main에서 하는 것처럼 필요한 모든 provider 달아놓기
        ],
        child: Builder(
          builder: (BuildContext context) {
            var provider = Provider.of<StudentQuizProvider>(context);
            //... Provider 에 데이터 입력하는 코드
            
            return const StuQuizBackgroundPage(index: 0);
            // 리턴값은 테스트할 위젯
          },
        ),
      ),
    );

현재 2학년 1학기에 기초프로젝트랩을 통해서 flutter 팀 프로젝트를 진행중이다. 현재 UI를 만드는 작업이 매우 시급하였기에, 나는 처음에는 TDD를 시도해보려다가 조급한 마음에 잘 찾지도 못하고 너무 빨리 포기해버렸다. 그렇게 어떻게든 코드를 작성하다가 우연히 TDD와 관련한 책이 내 눈에 들어와 다시 TDD를 시도해보기로 결정하였다.

TDD를 다시 하기로 결정한 이유

  1. 코드를 더 깔끔하게 적고 싶어서
  2. 나중에 앱이 거의 완성되어갈 때의 혼란을 최소화하고 싶어서
  3. TDD를 몸에 체화시켜 더 좋은 프로그래머가 되고 싶어서
  4. 설계 능력을 높여 copilot 과 같은 AI가 직접 코딩하는 세계를 끝내도, 적응하기 위해서

현재 내가 해야 할 일은 UI를 구성하는 일인데, 나는 Mockito를 사용할 줄 몰라서, DummyData 클래스를 만들고, 거기에 static const variable로 dummy를 만들어 Mock을 구현하였다. 이 Data들은 provider를 통해서만 UI로 전달되도록 하였다. 그러면 내 생각에 나중에 Provider와 통신하는 코드만 만들면, UI는 자동으로 따라올 것이라고 생각하였기 때문이다.

결국, 필연적으로 Test를 할려면, Provider를 통해서 데이터를 전달해주는 테스트 코드가 필요했다. 또한, UI를 작업하는 것이기에 그냥 test() 함수보다는 testWidget() 함수가 여러모로 적절하였다.

Provider와 WidgetTest

사실 Provider를 사용하려면 무조건 WidgetTest를 사용해야 한다. 이유는 다음과 같다.

  1. provider를 사용하려면 우선적으로 MultiProvider가 호출되어야만 한다. MultiProvider를 거치지 않으면, Provider.of()와 같은 메서드를 통해서 Provider를 가져올 수 없고, 이로 인해 데이터 읽기, 쓰기 모두 안 되기 때문이다. (조상 어디에도 Provider가 정의되지 않아 에러를 낸다.)

그래서 이 문제를 해결한 코드를 보여주면, 다음과 같다.

await tester.pumpWidget(
      MultiProvider(
        providers: [
          ChangeNotifierProvider<StudentQuizProvider>(
              create: (_) => StudentQuizProvider()),
        ],
        child: ... //여기에 들어갈 건 조금 후에 설명함
    ),
),
  1. provider에 데이터를 넣기 위해선 BuildContext이(가) 필요하다 그런데 이걸 사용하기 위해선 StackOverFlow의 이 방법을 사용하였다.

https://stackoverflow.com/questions/56277477/unit-testing-in-flutter-passing-buildcontext

요약하자면 이렇게 하면 된다.

await tester.pumpWidget( 
	Builder(
		builder: (BuildContext context) {
			var provider = Provider.of<StudentQuizProvider>(context);
			// ...
            
			// 반환하는 애가 우리가 테스트할 위젯이 되어야 함.
	    	return const StuQuizBackgroundPage(index: 0);
		},
    ),
);

첫 번째 테스트 코드

await tester.pumpWidget(
	MultiProvider(
        providers: [
          ChangeNotifierProvider<StudentQuizProvider>(
              create: (_) => StudentQuizProvider()),
        ],
        child: ... //여기에 들어갈 건 조금 후에 설명함
	),
);

await tester.pumpWidget( 
	Builder(
		builder: (BuildContext context) {
			var provider = Provider.of<StudentQuizProvider>(context);
			// ...
            
			// 반환하는 애가 우리가 테스트할 위젯이 되어야 함.
	    	return const StuQuizBackgroundPage(index: 0);
		},
    ),
);
//...(테스트 코드 작성)

이렇게 하면 에러가 난다. 어떤 내용이나면, Provider.of 메소드에서 조상에 Provider가 없다는 에러가 나온다.
왜 이렇게 된걸까? 내가 추측하기로는 위의 Widget과 아래의 Widget이 서로 조상, 후손 관계가 아니기 때문이다. (아마도 형제가 아닐까?)

두 번째 테스트 코드

그래서 다음엔 child에 Builder를 넣어보았다. 이런 식으로 말이다.

await tester.pumpWidget(
      MultiProvider(
        providers: [
          ChangeNotifierProvider<StudentQuizProvider>(
              create: (_) => StudentQuizProvider()),
        ],
        child: Builder(
          builder: (BuildContext context) {
            var provider = Provider.of<StudentQuizProvider>(context);
            //... 입력 코드
            // The builder function must return a widget.
            return const StuQuizBackgroundPage(index: 0);
          },
        ),
      ),
    );

이렇게 하였더니, 테스트가 잘 작동하였다.

Finder 이용하기

그 다음으로는 Finder에서 사용한 몇가지 유용한 메서드를 소개할까 한다.
사실 Finder는 아직 잘 모르겠다.
find.byType(클래스)
이 메서드는 클래스를 바탕으로 찾도록 하는 메서드이다. 이 함수는 find가 CommonFinders 클래스라서 그거의 doc을 찾아갔더니 찾은 메서드이다

(참고)https://api.flutter.dev/flutter/flutter_driver/CommonFinders-class.html

문제점

솔직히 아직은 어떻게 해야 진짜 UI가 잘 나왔는지 확인하는 방법을 모르겠다. 테스트 코드를 짤 때, 모든 UI적 요소를 고려하는 코드를 짜는 것은 매우 힘들고, TDD를 통해 얻어가야 하는 것이 아닌 것처럼 느껴진다. 이 부분은 조금 더 고민해볼 필요가 있다고 생각한다.

profile
주니어 개발자

0개의 댓글