데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      도전! 프론트엔드 TDD

      ingee.kim 23.08.08
      1,955 15 0
      DEVOTEE 요약
      브라우저가 웹3 세상의 핵심 플랫폼이며, 프론트엔드 개발이 중요하다는 주장입니다. 이에 따라 작은 프로젝트를 통해 프론트엔드 TDD(Test Driven Development)를 경험한 결과를 설명하고, Vue3 및 Pinia를 사용한 테스트 코드 작성 예를 제공합니다. 이를 통해 Vue3 및 Pinia 프로젝트 개발에 도움이 되길 바랍니다.

      웹3 세상의 핵심 플랫폼은 브라우저일 거라고 생각합니다. 그래서 프론트엔드 개발이 중요하다고 생각합니다.

      작은 프로젝트를 진행하면서 프론트엔드 TDD를 경험했습니다. TDD는 테스트 코드와 구현 코드를 함께 개발해 나가는 방식입니다.

      간단한 Todo 앱을 예로 들어 제가 경험한 TDD를 설명하려 합니다.

      프론트엔드 TDD 개념과 Vue3 & Pinia 테스트 코드 작성 사례를 전달하는 것이 목표입니다. Vue3 & Pinia 프로젝트 개발에 도움이 되면 좋겠습니다.

      관련 코드는 GitHub https://github.com/ingee/todo 소스 레포에 있습니다.


      웹앱 구상

      • 웹앱을 스케치합니다. todo를 등록하고 삭제하는 기능이 전부입니다.

        화면스케치


      스캐폴딩

      • npm init vue@latest 명령으로 Vue3와 Pinia를 이용하는 웹앱의 뼈대를 만듭니다.

      $ node -v
      v16.20.0
      
      $ npm init vue@latest
      
      Vue.js - The Progressive JavaScript Framework
      
      ✔ Project name: … todo
      ✔ Add TypeScript? … No
      ✔ Add JSX Support? … No
      ✔ Add Vue Router for Single Page Application development? … No
      ✔ Add Pinia for state management? … Yes
      ✔ Add Vitest for Unit Testing? … Yes
      ✔ Add an End-to-End Testing Solution? › No
      ✔ Add ESLint for code quality? … No
      
      Scaffolding project in /home/ingee/work/todo/todo...
      
      Done. Now run:
      
        cd todo
        npm install
        npm run dev


      1. Pinia 스토어에 있는 todo 목록을 화면에 표시하자

      목표

      • Pinia 스토어에 todos라는 이름으로 todo 목록을 저장할 계획입니다.

      • Pinia 스토어에 저장되어 있는 todo 목록을 화면에 표시하는 기능부터 구현하기로 합니다.

      1.1 Unit Test를 작성합니다

      src/ tests/ App.spec.js

      import { describe, it, expect, vi } from 'vitest'
      import { shallowMount, flushPromises } from '@vue/test-utils'
      import { mount } from '@vue/test-utils'
      import { createTestingPinia } from '@pinia/testing'
      import merge from 'lodash.merge'
      import App from '@/App.vue'
      
      describe('App.vue', () => {
        function createWrapper (option) {
          const pinia = createTestingPinia({
            initialState: option
              ? {
                  todo: option.initialState,
                }
              : {},
            createSpy: vi.fn,
          })
          const defaultOpt = {
            global: {
              plugins: [pinia],
            },
          }
          return shallowMount(App, merge(defaultOpt, option))
        }
      
        it('shows todos in store', () => {
          const todos = [
            'wake up',
            'wash up',
            'go to work',
          ]
          const initialState = { todos }
          const wrapper = createWrapper({ initialState })
          for (const todo of todos) {
            expect(wrapper.text()).toContain(todo)
          }
        })
      })
      • 테스트 wrapper 객체를 생성하기 위해 createWrapper() 함수를 작성합니다.

      • Pinia 스토어의 초기 상태를 지정하기 위해 createWrapper() 함수에 전달하는 option 객체를 이용합니다 (어디선가 솜씨 좋은 개발자로부터 배운 방식입니다).

      • 기능이 정상 동작하는지 검증하기 위해 "It shows todos in store" 유닛 테스트를 작성합니다.

      1.2 실패 확인

      • 콘솔창에서 npm run test:unit 명령을 실행하고, 유닛 테스트가 실패함을 확인합니다 (아직 구현하지 않았으니 실패가 당연합니다).

      • 적절한 사유와 함께 실패함을 확인합니다.

        • Pinia의 초기 상태로 지정한 todo 목록이 화면에 표시되지 않는다고 합니다. 적절합니다.

       ❯ src/__tests__/App.spec.js (1)
         ❯ App.vue (1)
           × shows todos in store
      
      ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
      
       FAIL  src/__tests__/App.spec.js > App.vue > shows todos in store
      AssertionError: expected 'Todo Lorem Ipsum is simply dummy text…' to include 'wake up'
       ❯ src/__tests__/App.spec.js:35:30
           33|     const wrapper = createWrapper({ initialState })
           34|     for (const todo of todos) {
           35|       expect(wrapper.text()).toContain(todo)
             |                              ^
           36|     }
           37|   })
      
      ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
      
       Test Files  1 failed (1)
            Tests  1 failed (1)
         Start at  13:08:02
         Duration  90ms
      
      
       FAIL  Tests failed. Watching for file changes...
             press h to show help, press q to quit

      1.3 기능 구현 & 성공 확인

       ✓ src/__tests__/App.spec.js (1)
      
       Test Files  1 passed (1)
            Tests  1 passed (1)
         Start at  13:11:57
         Duration  108ms
      
      
       PASS  Waiting for file changes...
             press h to show help, press q to quit


      2. 입력창에 입력한 todo를 Pinia 스토어에 저장하자

      목표

      • 화면에 입력한 todo를 Pinia 스토어에 저장할 계획입니다.

      • todo를 입력하고 Add 버튼을 클릭하면, 해당 항목이 Pinia 스토어에 저장되게 하기로 합니다.

      2.1 Unit Test를 추가합니다

      src/ tests/ App.spec.js

        it('adds todo-item when add-btn is clicked', async () => {
          const wrapper = createWrapper()
      
          const todo = 'have lunch'
          const todoInput = wrapper.find('[data-test="todo-input"]')
          await todoInput.setValue(todo)
      
          const addBtn = wrapper.find('[data-test="add-btn"]')
          await addBtn.trigger('click')
      
          const store = useTodoStore()
          expect(store.addTodo).toBeCalledWith(todo)
        })
      • "It adds todo-item when add-btn is clicked" 유닛 테스트를 추가합니다.

      • 테스트 wrapper의 setValue() 함수로 화면 입력 값을 지정합니다.

      • Add 버튼을 클릭하면 store의 addTodo() 함수가 적절한 인자와 함께 호출되는지 확인합니다.

      2.2 실패 확인

      • 유닛 테스트가 적절한 사유와 함께 실패함을 확인합니다.

        • todo 입력 필드가 없다고 합니다. 적절합니다 (아직 구현하지 않았으니 당연합니다).

       ❯ src/__tests__/App.spec.js (2)
         ❯ App.vue (2)
           ✓ shows todos in store
           × adds todo-item when add-btn is clicked
      
      ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
      
       FAIL  src/__tests__/App.spec.js > App.vue > adds todo-item when add-btn is clicked
      Error: Cannot call setValue on an empty DOMWrapper.
       ❯ Object.get node_modules/@vue/test-utils/dist/vue-test-utils.esm-bundler.mjs:1517:27
       ❯ src/__tests__/App.spec.js:44:21
           42|     const todo = 'have lunch'
           43|     const todoInput = wrapper.find('[data-test="todo-input"]')
           44|     await todoInput.setValue(todo)
             |                     ^
           45|
           46|     const addBtn = wrapper.find('[data-test="add-btn"]')
      
      ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
      
       Test Files  1 failed (1)
            Tests  1 failed | 1 passed (2)
         Start at  13:35:46
         Duration  121ms

      2.3 기능 구현 & 성공 확인

       ✓ src/__tests__/App.spec.js (2)
      
       Test Files  1 passed (1)
            Tests  2 passed (2)
         Start at  13:45:47
         Duration  160ms
      
      
       PASS  Waiting for file changes...
             press h to show help, press q to quit


      3. 화면에 표시된 todo 항목을 제거하자

      목표

      • todo 항목마다 옆에 X 버튼을 표시할 계획입니다.

      • X 버튼을 클릭하면 해당 항목이 삭제되게 하기로 합니다.

      3.1 Unit Test를 추가합니다

      src/ tests/ App.spec.js

        async function setStore (state) {
          const store = useTodoStore()
          store.$patch(state)
          await flushPromises()
          return store
        }
        ...
        
        it('removes todo-item when rm-btn is clicked', async () => {
          const todo = 'something to do'
          const wrapper = createWrapper({
            initialState: {
              todos: [ todo ],
            },
          })
          let rmBtns = wrapper.findAll('[data-test="rm-btn"]')
          expect(rmBtns.length).toBe(1)
          await rmBtns.at(0).trigger('click')
          let store = useTodoStore()
          expect(store.rmTodo).toBeCalledWith(0)
      
          const todos = [
            'something #1',
            'something #2',
            'something #3',
          ]
          store = await setStore({ todos })
          rmBtns = wrapper.findAll('[data-test="rm-btn"]')
          expect(rmBtns.length).toBe(todos.length)
          await rmBtns.at(1).trigger('click')
          expect(store.rmTodo).toBeCalledWith(1)
        })
      • "It removes todo-item when rm-btn is clicked" 유닛 테스트를 추가합니다.

      • createWrapper() 함수를 호출할 때 option.initialState 인자를 전달해서 Pinia 스토어의 초기 상태를 지정합니다.

      • 유닛 테스트 도중 Pinia 스토어의 상태를 변경하기 위해 setStore() 함수를 작성합니다.

      3.2 실패 확인

      • 유닛 테스트가 적절한 사유와 함께 실패함을 확인합니다.

        • X 버튼 (rm-btn)이 없다고 합니다. 적절합니다.

       ❯ src/__tests__/App.spec.js (3)
         ❯ App.vue (3)
           ✓ shows todos in store
           ✓ adds todo-item when add-btn is clicked
           × removes todo-item when rm-btn is clicked
      
      ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
      
       FAIL  src/__tests__/App.spec.js > App.vue > removes todo-item when rm-btn is clicked
      AssertionError: expected +0 to be 1 // Object.is equality
       ❯ src/__tests__/App.spec.js:68:27
           66|     })
           67|     let rmBtns = wrapper.findAll('[data-test="rm-btn"]')
           68|     expect(rmBtns.length).toBe(1)
             |                           ^
           69|     await rmBtns.at(0).trigger('click')
           70|     let store = useTodoStore()
      
        - Expected   "1"
        + Received   "0"
      
      ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
      
       Test Files  1 failed (1)
            Tests  1 failed | 2 passed (3)
         Start at  13:49:05
         Duration  106ms

      3.3 기능 구현 & 성공 확인

      마무리

      지금까지의 내용을 요약합니다.


      TDD는,

      • 테스트 코드를 먼저 만들고,

      • 그 테스트 코드가 적절한 사유와 함께 실패함을 확인하고,

      • 그 테스트 코드가 성공하도록 구현하고, 이 과정을 반복하는 개발 방식입니다.

      Vue3 & Pinia 웹앱의 유닛 테스트 코드를 작성함에 있어,

      • Pinia 스토어의 초기 상태를 지정하는 방법을 알아 보았습니다.

      • 웹 화면의 입력창에 사용자 입력값을 지정하는 방법을 알아 보았습니다.

      • 테스트 도중 Pinia 스토어의 상태를 변경하는 방법을 알아 보았습니다.

      테스트 코드는 쉽게 작성해서 자주 써먹어야 합니다. 하지만 처음 시작하자면 넘어야 할 장벽이 있습니다. 이 글이 그 장벽을 넘는 데 도움이 되기를 희망합니다.

      관련 코드는 GitHub https://github.com/ingee/todo 소스 레포에 있습니다.


      Ref.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      ingee.kim 님의 최신 블로그

      더보기
      동영상 기고하기