웹3 세상에서는 개인이 identity와 data에 대한 통제권을 갖습니다.
그래서 웹3 세상을 열기 위해서는 identity와 data 관리에 대한 고민이 필수적입니다.
그런 맥락에서 현실 세계의 대표적인 identity 관리 방식인 OAuth2와 OpenID Connect을 살펴봅니다.

 

OAuth2 표준과 OpenID Connect 표준

  • OAuth2는 Authorization(이후 authZ)에 대한 표준이다.
  • 반면 OpenID Connect(이후 OIDC)는 Authentication(이후 authN)에 대한 표준이다.
  • OIDC 표준은 OAuth2 표준을 기반으로 정의되었다.

 

OIDC AuthZ code flow

그림1. OAuth2 시퀀스 그림2. OIDC 시퀀스
  • 그림1은 OAuth2 authZ code grant type의 동작 시퀀스다.
  • 그림2는 OIDC authZ code flow의 동작 시퀀스다.
  • 두 시퀀스는 사실상 동일하다. 두 시퀀스의 차이점은 id-provider 서버(그림의 'KEYCLOAK')로부터 받아 오는 토큰이다(양쪽 그림의 5번 'Exchange authZ code...' 화살표).
    • 이때 OAuth2 프로토콜은 Access Token을 발급하고, OIDC 프로토콜은 ID Token을 발급한다.

 

OIDC JWT 표준

  • ID Token의 규격은 OIDC 표준이 정한다.
    • ID Token은 JWT(JSON Web Token) 포맷을 따른다.
  • JWT 토큰에는 토큰 발급자(issuer, 이후 iss)의 priv-key를 이용해서 만든 서명이 포함되어 있어서, 누구나 iss의 pub-key를 이용해서 토큰의 무결성을 검증할 수 있다.
  • Access Token 관련해서는 정해진 규격이 없다.
    • OAuth2 표준은 Access Token의 포맷을 규정하지 않는다. 그래서 어떤 포맷을 사용하더라도 표준에 부합한다.
    • Access Token 포맷으로 JWT를 사용하는 경우가 많다.
  • 관련용어
    • JWT: JSON Web Token
    • JWS: JSON Web Signature
    • JWKS: JSON Web Key Set

 

JWT 토큰 검증

https://jwt.io

https://jwt.io 페이지에서 JWT 포맷으로 발급된 토큰을 검증할 수 있다.

  1. 로컬머신에 OIDC id-provider 서버를 띄우고 JWT 포맷의 Access Token을 발급 받는다.
  2. 발급 받은 토큰을 https://jwt.io/ 에 Copy & Paste 한다.
  3. "Signature Verified" 문구를 확인할 수 있다.
토큰샘플 eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtlT0tCZGNNdVNGcnhWU0dTZlhBcFNmLXlYR3BRMEFmQmx1YnU4OVJ1R1kifQ.eyJleHAiOjE2OTk1OTY3NjQsImlhdCI6MTY5OTUxMDM2NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MzgwIiwic3ViIjoidGVzdC1hY2Nlc3MifQ.psH3-wi6cztRaVaXe0Exh9Aoo_qkvB7_UQhcs1Kzlk8zcLpOZrb20-BLJPs8cV4vlr1jkpm2E_q-cHfAq41ged48zRp0O3bwEPekTinE2T1qfKG3t_8WJu80Wn5Q4d64P8xQCPA8Ly_Q-Q8pRzdmjQBeUK9jZxzEmeqa9BGadna1IHM-WBd7LzCq5pCjS9gkphIy9t5J_bW2f6Nhp67T-VY9gneNy8izTgrja_ulXtifPqHZNaAK7IFFpyJxCvM__3RMOeEFuHYmtmGN7_pT2gT9QJ5cYwGRFbox2V1q95dUfLQWn0nQmC7l1VxOKBRMGcSgGtvvMBu8x-nPalY3gw
JWT 검증OK

잠깐, 뭘로 검증했지?

  • JWT 토큰에 포함된 Signature는 iss의 priv-key를 이용해 만들어진 값이다.
  • 이를 검증하려면 iss의 pub-key가 필요하다.
  • 그런데 JWT 토큰 안에는 iss의 pub-key가 존재하지 않는다. 어디서 가져왔을까?

JWKS URI

JWT 토큰을 검증하려면 iss의 pub-key를 확보해야 한다. 그 절차는 다음과 같다.

  1. 토큰에 포함된 iss 속성값으로부터 well-known URL을 유추한다 (iss + '/.well-known/openid-configuration').
  2. (예시로 든 토큰샘플의 iss는 http://localhost:8380 이다. 이 토큰을 검증해서 Signature Verified 결과를 얻으려면 http://localhost:8380/.well-known/openid-configuration 엔드포인트를 호스팅하는 id-provider가 떠 있어야 한다.)
  3. well-known URL에서 JWKS uri를 확보한다.
  4. JWKS uri에서 iss의 pub-key를 얻을 수 있다.

http://localhost:8380/.well-known/openid-configuration 엔드포인트의 응답

{
  "issuer": "http://localhost:8380",
  "authorization_endpoint": "http://localhost:8380/authz",
  "token_endpoint": "http://localhost:8380/token",
  "userinfo_endpoint": "http://localhost:8380/userinfo",
  "jwks_uri": "http://localhost:8380/jwks"
}

 

http://localhost:8380/jwks 엔드포인트의 응답

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "keOKBdc...",
      "use": "sig",
      "alg": "RS256",
      "e": "AQAB",
      "n": "xPyyJCGj..."
    }
  ]
}

 

Ref

Keycloak Identity and Access Management for Modern Applications (2nd Ed.) 책

Understanding ID Token

How do you choose between OAuth2 and OpenID Connect for web authorization?

JWKs and node-jose

Validate an OpenID Connect JWT using a public key in JWKS

What is OIDC Discovery .well-known end-point?

 

Posted by ingeeC
,

IPFS 2023 컨퍼런스 요약

Dev 2023. 10. 16. 16:27
이름만으로 가슴 뛰게 만드는 프로젝트가 있습니다.
IPFS (InterPlanetary File System) 얘기입니다.
지난 4월 벨기에에서 IPFS 2023 컨퍼런스가 열렸습니다.
관련 내용을 요약합니다.

모든 발표 동영상은 유튜브에 올려져있습니다.
https://www.youtube.com/playlist?list=PLuhRWgmPaHtQ-TO65P62tqfUM85HCIqSj
 

IPFS þing 2023 - IPFS on the Web

 

www.youtube.com

 


IPFS on the Web in 2023 (so far) - Dietrich Ayala (30분)

https://youtu.be/dn8PssXkRbY

  • 행사 개요와 연사들을 소개한다
  • Brave 브라우저가 IPFS를 적극 지원하고 있다 - Brave는 IPFS엔진을 내장하고 있다
  • 모바일 IPFS 플랫폼 DURIN을 개발하고 있다 - 모바일에서 IPFS 컨텐츠를 볼 수 있다
  • IPFS를 인공위성에서 운영하는 실험을 하고 있다 - https://github.com/ipfs-shipyard/space

What Is The Web? - Robin Berjon (30분)

https://youtu.be/s878bm15mrk

  • 웹의 형태를 바꾸자 - 지금은 서버가 기능을 제공, 서버에 권력이 있다
  • 2023-03-28에 "IPFS Principles" 표준을 출간했다 - IPFS가 웹의 미래다

A better web: secure, private, p2p apps with user-owned data and identity - Ian Preston, 30분 동영상

https://youtu.be/mSElk2jcFqY

  • 감시와 통제, 아이덴티티, 데이터 소유권에 관한 문제를 해결하고자 Peergos(회사)가 노력하고 있다
  • 서버 없는 REST API, "Sandbox"를 잠깐 언급한다

WNFS: Versioned and Encrypted Data on IPFS - Philipp Krüger (30분)

https://youtu.be/LBMyRp4Ywew

  • fission(회사)가 데이터를 암호화해서 IPFS에 저장하는 서비스를 제공한다
  • 커뮤니티 프로젝트다, 참여하라 - https://github.com/wnfs-wg/

Content Based Addressing and the Web Security Model - Fabrice Desré (40분)

https://youtu.be/H_1JVGDnctI

  • 처음부터 Security를 고려한 파일시스템 Web Tiles를 소개한다
  • Web Tiles는 IPFS와 닮았지만 다르다

Hello Helia - achingbrain (30분)

https://youtu.be/T_FlhkLSgH8

  • js-ipfs와 js-libp2p 메인테이너가 "브라우저 IPFS 구현체"의 미래/마일스톤을 직접 소개한다
  • js-ipfs를 대치하는 Helia가 릴리즈되어 지금 당장 쓸 수 있다 - https://github.com/ipfs/helia

JavaScript performance - how to wring the most out of your Helia deployment - achingbrain (20분)

https://youtu.be/zPeLYosZ3Ak

  • 새로운 "브라우저 IPFS 구현체" Helia의 성능 측정 자료를 공개한다

Connecting everything, everywhere, all at once with libp2p - Prithvi Shahi (20분)

https://youtu.be/4v-iIB0C9_8

  • libp2p 프로토콜로 모든 소프트웨어와 모든 네트워크를 한번에 연결하자
  • libp2p가 QUIC, WebRTC, WebTransport를 어떻게 이용하는지 소개한다
  • 더 알고 싶다면 github를 참조하라 - github.com/libp2p/universal-connectivity

The Incredible Benefits of libp2p + HTTP - Marten Seemann & Marco Munizaga (15분)

https://youtu.be/Ixyo1G2tJZE

The Name Name Service - Blaine Cook (30분)

https://youtu.be/CHiCEd36KtI

  • NNS (Name Name Service)는 탈중앙화된 DNS를 목표로 한다
  • NNS는 DID를 이용한다 - NNS의 Name은 DID가 아니지만, 적어도 1개의 DID를 갖는다
  • Decentralized, Human Readable, Secure 사이의 상충을 보여주는 Zooko's Triangle을 소개한다

Building decentralized websites on IPFS - Ryan Shahine (30분)

https://youtu.be/TeFAHmzvIdg?si=2vGIcNfwYDBi58VV

  • giliam.eth 같은 ENS에 웹사이트를 생성/배포하는 도구를 개발했다 - Portrait
  • Brave 브라우저는 giliam.eth 같은 ENS를 해석/처리할 수 있다

ODD.js, a technical overview - icidasset (10분)

https://youtu.be/ByQbY3lNAck?si=gcAHRhoPqRDysdoy

  • ODD.js는 백엔드가 없는 어플리케이션을 위한 프레임워크다
  • ODD.js는 DID, WNFS, UCAN을 이용한다

IPFS native frontend development using Importmaps - Dilip Shukla (20분)

https://youtu.be/4HY_7DxScMo?si=5dp82haOtmpm3XHk

  • 웹페이지를 위한 package.json 역할을 하는 importmap을 소개한다
  • importmap을 지원하는 jspm 도구를 개발했다 - npm을 대체할 수 있다

Explorations into Decentralized Publishing - David Justice (10분)

https://youtu.be/fn5QNvRXMIo?si=eojMK9sD7jlbuwOb

  • IPFS와 FVM을 이용해서 팀블로그를 만들었다 - GH: meandavejustice/blog-builder
  • IPFS에 호스팅된 SPA 형태의 Viewer와 FVM에서 동작하는 스마트컨트랙트를 이용했다
  • 짧은 발표와 긴 Q&A가 인상적

 


기타 번외로 참조한 IPFS 동영상들을 나열합니다.

How IPFS deals with files - IPFS Camp Workshop

https://youtu.be/Z5zNPwMDYGg

  • 2019-09-18, 1시간 동영상
  • IPFS가 데이터를 다루는 방식을 자세히 소개한다
  • CID와 CID 탐색기를 소개한다 - cid.ipfs.tech
  • DAG와 DAG 빌더를 소개한다 - dag.ipfs.tech

libp2p NAT Hole Punching Success Rate - @dennis-tra - Measuring IPFS

https://youtu.be/fyhZWlDbcyM

  • 2022-08-10, 30분 동영상
  • 방화벽과 NAT 장벽을 넘어 p2p 연결을 달성하는 Hole Punching에 대해 소개한다
  • kubo v0.11 부터 kubo가 릴레이 역할을 함께 수행한다 (kubo는 go-ipfs의 새 이름)

Intro to libp2p: helping with real world application problems - Max Inden

https://www.youtube.com/watch?v=J7ZWbpo2AZk

  • 2022-11-01, 15분 동영상
  • libp2p 프로토콜은 IFPS, Ethereum2, Poladot 등에 적용되어 있다
  • 현재 libp2p 팀은 브라우저에서의 연결성 개선을 위해 노력하고 있다

Browser connectivity state of union and demo

https://youtu.be/qG_1bYVtGO4

  • 2022-11-01, 20분 동영상
  • 브라우저의 문제는 TCP 프로토콜과 QUIC 프로토콜을 쓸 수 없다는 점이다
  • libp2p의 브라우저 연결성 개선에 관하여 WebTransport 프로토콜에 기대를 걸고 있다

libp2p NAT Hole Punching

https://youtu.be/bzL7Y1wYth8

  • 2022-11-01, 30분 동영상
  • Hole Punching 개념과 DCUtR 프로토콜의 개요를 설명한다

WebRTC signalling data over QR codes

https://youtu.be/EQFUPZK9CwI

  • 2022-11-02, 10분 동영상
  • 일련의 QR코드 스트림(동영상)을 이용하여 기기와 기기가 WebRTC 통신을 수행하는 데모를 시연한다 - 재밌음

Why WebRTC

https://youtu.be/ZBIFFuakFHQ

  • 2022-11-02, 10분 동영상
  • libp2p 브라우저 연결성 개선을 위해 시도한 노력의 내용과 현황을 소개한다
  • 아직 완벽하지 않다

이상입니다.

 

Posted by ingeeC
,

Web3 세상에서는 중앙 서버가 해체되어 존재하지 않게 됩니다 (Decentralized).

Web3 세상은 중앙 서버가 아니라 P2P 프로토콜에 의해 지탱됩니다.

Web3 세상에서는 웹 브라우저가 어플리케이션 실행 플랫폼 역할을 수행할 것이라고 생각합니다.

그래서 웹 프론트엔드 기술이 중요해질 것이라고 생각합니다.

그런 맥락에서 Web3 프론트엔드 어플리케이션의 e2e 테스트에 대해 소개합니다.

Cypress 와 Synpress, 두 단어만 기억하시면 성공입니다.

 

 

e2e 테스트가 필요한 순간

  • TDD (Test Driven Development) 방식으로 Web3 어플리케이션을 개발하고 있었음
  • 터미널 한쪽 구석에 Unit Test를 실행시켜 놓고 소스 파일을 수정할 때마다 모든 Unit Test가 성공하는지 확인하며 개발했음
  • 하지만, 모든 Unit Test를 통과했음에도 버그가 발생하는 상황을 겪음
  • 서버, 네트워크, 브라우저-애드온 같은 모든 컴포넌트들이 참여하는 상황에서만 확인 가능한 버그가 있었음

 

 

e2e 테스트 도구, Cypress

  • 현재 많이 쓰이는 e2e 테스트 도구는 Cypress Selenium
  • Cypress는 브라우저 안에서 일어나는 거의 모든 일을 JavaScript로 확인 가능한 개발자 친화적 도구
  • 반면 Selenium은 QA 엔지니어 친화적 도구

 

 

Web3 어플리케이션 테스트를 위한 Cypress 플러그인, Synpress

  • Web3 어플리케이션은 MetaMask 브라우저 애드온이 있어야 동작
  • MetaMask 브라우저 애드온이 개입하는 사용자 시나리오를 테스트하기 위해 Synpress가 필요 (MetaMask 활성화 시나리오, MetaMask wallet-connect 승인 시나리오, MetaMask TX 승인 시나리오, ...)

 

 

Cypress & Synpress 테스트 팁

  • Cypress 테스트 구문은 query-assert 또는 query-command 형태
    • Cypress 구문은 assert나 command로 마무리 짓는 것이 Good Practice (assert나 command 이후에 query를 덧붙이지 않는다)
  • 이더리움 메인넷을 타겟으로 하는 테스트는 Synpress의 task setupMetamask 단계에서 실패함
    • Synpress가 MetaMask 애드온을 설정한 다음, 성공 여부를 판정하기 위해 mainnet 문구를 찾지만, 한글 브라우저에서 메인넷이라고 표시되기 때문에 문자열 불일치로 인한 assertion fail이 발생한다
    • 메인넷 대신 테스트넷을 타겟으로 테스트 시나리오를 작성하는 편이 좋다 (한글 브라우저에서도 Goerli는 고얼리라고 표시하지 않고 Goerli라고 표시한다)

 

 

그래서 e2e 테스트는?

  • 모든 컴포넌트가 참여하는 상황에서만 확인할 수 있는 버그가 있음. 이를 잡아내는 것이 e2e 테스트
  • Unit Test 수행 시간은 msec 단위, e2e 테스트 수행시간은 min 단위 (테스트 비용이 크다)
  • 프론트엔드 테스트 피라미드가 제시하는 바대로 e2e 테스트는 아껴 써야 함

출처: "Testing Vue.js Applications"/ Edd Yerburgh 지음/ Manning Publications 펴냄

 

 

Ref.

별 4개급 참조 문서

별 3개급 참조 문서

 

Posted by ingeeC
,

도전! 프론트엔드 TDD

Dev 2023. 5. 16. 13:05

웹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.

Posted by ingeeC
,

JS Proxy란? JS Object란?

Dev 2023. 2. 19. 16:19

배경

Vue3를 Composition API 스타일로 쓰는 연습 중이다. Composition API에서 새로 도입된 타입이 있다. ref() 함수로 정의하는 리액티브 타입이다. ref() 함수로 리액티브 변수를 정의해서 console.log()를 찍어보면 Proxy 라는 낯선 이름이 보인다. Proxy란 무엇일까?

 

Vue3 소스코드: ref() 함수로 변수를 선언하고 console에 출력

import { ref } from 'vue'

const hellos = ref([ 'hello', 'world', ])
console.log('hellos.value=', hellos.value)

 

console에 출력된 내용

Proxy { <target>: (2) […], <handler>: {…} }

 

console 출력 스크린 캡쳐

 

결론

JS Proxy가 무엇인지 제대로 소개하는 좋은 글이 있어 링크한다. JS Object가 무엇인지 질문을 던지며 시작한다.

 

ES6 In Depth: 프락시 (Proxy)

http://hacks.mozilla.or.kr/2016/03/es6-in-depth-proxies-and-reflect/

 

ES6 In Depth: 프락시 (Proxy) ★ Mozilla 웹 기술 블로그

ES6 In Depth 시리즈는 ECMAScript 표준 6번째 에디션(줄여서 ES6)을 통해 JavaScript 에 새로 추가된 요소들을 살펴보는 시리즈입니다. 오늘 우리가 살펴보려는 것은 다음 코드입니다. var obj = new Proxy({}, { g

hacks.mozilla.or.kr

 

누가 번역했는지 참 자연스럽게 잘했다. 2016년에 내가 번역한 글이다 (죄송 😊).

번역보다 중요한 건 원글이다. 질문을 던지고 생각하게 만드는 솜씨가 훌륭하다. 작가의 피가 흐르는 엔지니어다. 읽어보시길.

Posted by ingeeC
,

Vue 관점에서 프론트엔드 프레임워크 현황을 요약함

2022-11-07. 개념검증 차원의 간단한 웹3 어플리케이션을 개발하게 됨. 작업에 앞서 웹 프론트엔드 프레임워크의 현황을 조사해서 요약함. Vue 에 포커스를 두고 조사함.
2023-02-12. 약 3달간 간단한 Vue3 프로젝트를 진행한 소감을 덧붙여 요약함

 

프론트엔드 프레임워크 - Vue 괜찮나?

  • 2021년 개발자 설문 결과 아직 건재
    • 사용중인 f/w Top3 (React, Angular, Vue.js ...), 공부하고 싶은 f/w Top3 (Svelte, Solid, Vue.js, ...)
    • 언젠가 Svelte/스벨트를 배워야 할 듯
  • 2022-11-07 선택: Vue를 쓰자 - 친숙하다

현재 쓰고 있는 프레임워크 랭킹 

현재 쓰고 있는 프레임워크 랭킹

공부하고 싶은 프레임워크 랭킹 

  • 2023-02-12 회고: Vue는 당분간 괜찮은 위치를 유지할 것 같다. 2022년 연말에 새로 발표된 설문 결과에서도 관심있는 프레임워크 3위, 많이 쓰는 프레임워크 3위 자리를 유지했다. 무엇보다 커뮤니티 활동이 활발하고 합리적인 것이 마음에 든다. 정체되어 있지 않고 꾸준히 발전하는 느낌이다. 

 

Vue2 vs Vue3 뭘 선택해야 하나?

  • Vue 히스토리 : 2014년 첫 출시, 2020년 Vue3 출시
  • Vue3의 목표는...
    • 도구의 처리 속도를 빠르게 하는 것
    • 코딩 방식을 단순하고 직관적으로 만드는 것
  • Vue3에서 달라진 점
    • Vite/빗 기반 툴체인 혁신
    • Composition API 스타일 도입 (복잡한 코드일수록 더 간결하게 관리)
    • Teleport API 제공 (컴포넌트를 더 유연하게 재활용)
    • 앱 성능 개선 (메모리 사용량 133% 개선, 번들 크기 41% 절약)
  • 2022-11-07 선택: Vue3를 쓰자 - 지금 안 배우면 언제 배울까?
  • 2023-02-12 회고: 새로 시작하는 프로젝트여서 익숙한 Vue2 대신 Vue3를 선택했는데, 잘한 선택이었다. Vue3에 새로 도입된 <script setup> 태그와 Composition API 스타일의 조합이 좋았다. 프레임워크가 강제하는 것이 아무것도 없는 느낌이었다.

 

스캐폴딩 & 빌드 도구, Vite/빗

  • Vite/빗이 기존 웹팩 기반의 Vue CLI를 대치
  • Vue 저자인 Evan You가 만듬
  • Go랭으로 만들었으며 Vue CLI 대비 처리 속도가 압도적
  • 2022-11-07 선택: Vite/빗 을 쓰자 - Vue CLI를 고집할 이유가 없다
  • 2023-02-12 회고: 일단 Vue3를 선택했다면 Vite/빗을 쓰지 않을 이유가 없다. 웹팩을 쓸 때도 속도가 아쉬웠던 적은 없었지만, 확실히 Vite/빗이 더 빨랐다.

 

테스팅 도구, Vitest/빗테스트

  • Vitest/빗테스트가 기존 Jest를 대치
  • Vitest/빗테스트는...
    • 처음부터 빌드도구 Vite/빗를 염두에 두고 개발
    • Jest와 유사, 기존 Jest 테스트 코드를 쉽게 마이그레이션 할 수 있음
  • Vue 코어팀이 만듬
  • 테스트 코드 실행 속도가 빠름
  • 2022-11-07 선택: Vitest/빗테스트를 쓰자 - 빌드도구로 Vite/빗을 쓸 거라면 당연한 선택
  • 2023-02-12 회고: Vitest/빗테스트도 만족스러웠다. 사용법이 Jest와 거의 동일했다. 유닛테스트 실행 속도는 체감할 수 있을 정도로 빨랐다.

 

상태 관리 플러그인, Pinia/피냐

  • Pinia/피냐가 기존 Vuex를 대치
    • Vuex가 없어지는 것은 아님
    • Piania는 Vuex와 같은 혈통 (Vuex5에서 Vuex가 Pinia와 합쳐질 수도 있다)
    • Vue 저자 Evan You가 Vuex5와 Pinia/피냐는 사실상 같은 프로젝트라고 언급
  • Pinia/피냐의 특징
    • 타입스크립트를 더 잘 지원
    • mutation을 제거, vuex 보다 간결해진 문법
    • Composition API 스타일을 지원
  • 2022-11-07 선택: Pinia/피냐를 쓰자 - 지금 안 배우면 언제 배울까?
  • 2023-02-12 회고: Vue 개발팀의 공식 프로젝트 다운 신뢰감을 느꼈다. Pinia를 Composition API 스타일로 사용했다. Vuex와 사용법이 달라 학습이 필요했는데, 대부분 Composition API 스타일에서 기인한 것이었다. Pinia를 Options API 스타일로 사용했다면 아무런 차이점도 느끼지 못했을 것 같다. Pinia가 Vuex를 계승하고, 차후 Vuex를 통합할 것이라는 얘기가 자연스럽게 이해됐다.

 

UI 라이브러리, Vuetify

  • Vue를 위한 UI 라이브러리로 Quasar와 Vuetify가 많이 쓰임
    • Quasar는 다양한 장치를 지원해야 할 때 좋은 선택
    • Vuetify는 UI 디자인에 투입할 자원이 없을 때 좋은 선택
  • 2022-11-07 선택: Vuetify를 쓰자 - 버텨줘서 고맙다
  • 2023-02-12 회고: 3달 전 Vue3 프로젝트를 시작하고 나서야 Vue3를 지원하는 Vuetify가 개발 중인 것을 알았다. 나는 방황하고 좌절했지만 이젠 Vuetify3를 선택해서 Vue3 프로젝트를 진행해도 좋을 것 같다 (얼마 전 Vuetify3가 릴리즈됐다).

 


Ref.

 

Posted by ingeeC
,

작업 동기

트위터를 즐겨 쓴다. 나름 헤비 유저다. 트위터는 옛날부터 키보드 사용자를 배려해왔다. 트위터 화면에서 J,K 키를 입력하면 화면을 아래,위로 스크롤할 수 있다. VIM 사용자 입장에서 아주 자연스럽고 쾌적하다. 그런데 가끔 딱 시야를 방해하는 그 자리에 "새 트윗 보기" 팝업이 뜬다. 일단 팝업이 뜨면 가독성이 현격히 떨어진다. 쾌적하지 못하다.

 

그림 : 문제 현상

 

해결책을 찾기 위해 노력했다. 브라우저 개발자 도구를 열고 범인을 찾았다. 그리고 해당 html 요소를 투명하게 만들면 해결 가능함을 확인했다. 하지만 트위터를 켤 때마다 개발자 도구를 열고 콘솔창에서 "화면 청소 코드"를 실행시키는 것은 사람이 할 짓이 아니었다. 이런 건 분명 기계에게 시켜야 옳은 일이다. 그리고 나만 불편할까? 이 기능을 브라우저 애드온으로 만들어 공개한다면 널리 세상을 이롭게 하는, 단군 할아버지께서 기뻐하실 만한 일이 되지 않을까?

 

그림 : 범인

 

해결책 : 트위터 화면 청소 코드

obj = document.getElementsByClassName('r-dkhcqf'); for (o of obj) { o.style.opacity = 0.1 }

 

참조 코드를 찾자

모방은 창조의 어머니. 가장 먼저 할 일은 참조 코드를 찾는 일이다. 파이어폭스를 주력 브라우저로 쓰는 입장에서 아주 그럴듯한 참조 코드를 찾았다. webextensions-examples. 이름처럼 다양한 애드온 샘플로 구성된 프로젝트다. 제시된 애드온 샘플 중에서 borderify 샘플이 내게 잘 맞는다고 생각했다. borderify 애드온은 *.mozilla.org 페이지를 표시할 때마다 붉은색 테두리를 덧붙여 표시하는 애드온이다. 특정 url에 반응한다는 점(즉 트위터 url에 반응하는 애드온을 만들 수 있다는 점)과 브라우저 컨텐트를 대상으로 스크립트를 실행한다는 점(즉 트위터 화면 청소 코드를 실행시키는 애드온을 만들 수 있다는 점)이 내가 원하던 바였다.

 

몇 차례 시행착오 끝에 애드온 소스 코드를 완성했다.

애드온 GitHub 소스 레포

 

파이어폭스 주소창에서 about:debugging을 입력하고, "임시 부가 기능 로드..." 버튼을 선택해서 manifest.json 파일을 선택하면, 브라우저에서 애드온을 실행시켜 볼 수 있다. 그렇게 애드온이 정상 동작함을 확인했다.

 

그림 : 애드온 개발 설정

 

이제 개발한 애드온을 정식 등록할 차례다. 그러면 브라우저를 실행시킬 때마다 "임시 부가 기능 로드..." 버튼을 클릭하지 않아도 된다. 그리고 집에 있는 컴퓨터 뿐 아니라 회사에 있는 컴퓨터에서도 이 애드온을 사용할 수 있게 된다. 절차는 간단했다. AMO 개발자 허브에 등록하면 된다. 소스 코드를 zip 파일로 압축해서 클릭 몇 번만 하면 등록이 완료된다. 그러면 24 시간 이내에 처리 결과를 알려주겠다는 친절한 메일이 온다. 그리고 정말 그 다음날 애드온이 등록됐다.

 

그림 : AMO에 정식 등록된 애드온

 

그림 : 파이어폭스에 정식 설치된 애드온

 

브라우저 애드온은 나름 표준 웹기술이어서 파이어폭스 애드온을 조금만 다듬으면 크롬 브라우저에서도 사용 가능하다. 크롬 브라우저를 위한 나머지 일은 다른 훌륭한 사람에게 미룬다. 브라우저로 할 수 있는 재미난 일이 더욱 많아지기를 희망한다. 웹3 세상의 주력 플랫폼은 브라우저일 것이다.

 

 

Posted by ingeeC
,

성능테스트 도구 탐색

  • JavaScript로 테스트 시나리오를 짤 수 있는 도구를 탐색
  • 몇 년 전 사용했던 Artillery와 새로 알게 된 k6를 놓고 고민
  • 미미하게 구글링 건수가 많고 (37,000건 vs 1,800건), grafana 대시보드가 멋진 k6를 선택
  • k6 프로젝트의 오너가 grafana-labs라서 grafana와의 연계가 좋을 수 밖에 없음

이런 멋진 대시보드를 실시간으로 볼 수 있음

 

사용법

  • k6를 실행하려면 influxDB와 grafana가 있어야 함
  • k6 + influxDB + grafana를 컨테이너로 실행하는 것이 가장 간편
  • k6 소스레포에서 docker-compose.yml 파일을 제공함  
git clone https://github.com/grafana/k6 && cd k6 
git submodule update --init 
docker-compose up -d influxdb grafana
docker-compose run -v $PWD:/scripts k6 run /scripts/myscript.js

 

테스트 스크립트 작성

  • k6 자체는 go 언어로 작성됨
  • k6에서 실행되는 테스트 스크립트는 JavaScript로 작성함
  • 테스트 스크립트는 반드시 default function을 export 해야 함
  • 해당 function을 k6가 생성하는 VU(virtual user)들이 실행함
import http from "k6/http";

export default function() {
    let response = http.get("https://test-api.k6.io");
};

 

실행 팁

  • grafana 커뮤니티에서 만든 대시보드들 중 4411 추천
  • k6 성능테스트 도중 "The flush operation took higher than the expected set push interval" 에러를 만남
    ==> influxDB에 테스트 지표를 write 하는 시간이 지연되어 발생하는 에러
    ==> 테스트 결과 분석에 필요한 지표만 SystemTags로 지정해서 influxDB의 지표 수집 부담을 줄여준다
export let options = { 
   vus: 4, 
   stages: [ 
     { duration: "30s", target: 32 }, 
     { duration: "60s", target: 32 }, 
     { duration: "30s", target: 4 }, 
   ], 
   systemTags: ['status', 'http_req_duration'], // <== 이거!
};

 

Ref

https://k6.io/blog/k6-loves-grafana/

  • k6 실행 방법과 대시보드 설치 방법을 간결하게 설명

https://github.com/grafana/k6

  • k6 소스레포 (README 내용 좋음)

https://k6.io/docs/

  • k6 공식 문서

 

Posted by ingeeC
,

웹 프론트엔드 TDD?

Dev 2022. 3. 14. 09:06

2020.10.15.

어찌어찌 프로젝트가 살아남았고 간단한 Admin 포털을 또 만들 기회가 있었다.

지난번에 이어 이번에도 개발 소감을 요약해본다 (2020.10.15. 기준).

 

개발환경: win10 좋다
- OS: win10 + wsl + windows terminal
- tool: vim, tmux, nodejs, vue-cli
- framework/lib: vue, vue-router, vuetify, vuex, axios
- runtime: firefox + vue dev-tools

 

프론트엔드 프레임워크: Vue 좋다

- 컴포넌트
  . 통상 웹UI 컴포넌트 하나를 구현하려면 html, css, js 3개 파일이 필요
  . Vue는 UI 컴포넌트 하나를 하나의 파일로 정의 (SFC: Single File Component)
  . 개발/관리 측면에서 좋았음
- 급격하지 않은 변화
  . 개발 진행중에 Vuetify 문서가 버전업됨
  . 하지만 변화 폭이 크지 않아서 쉽게 적응할 수 있었음
  . Vue 프레임워크 자체도 곧 3.0으로 버전업될 예정. 하지만 Vue 2.0과 크게 다르지 않을 예정
  . "변화 폭이 크지 않고, 변화 방향이 합리적"인 것이 프레임워크의 중요한 장점이라 생각함

 

디버깅툴: Firefox + Vue Dev-tools 좋다

- Vue dev-tools의 vuex 히스토리 기능이 큰 도움 됨
  어플리케이션의 상태 정보를 확인해서 해결되는 문제가 (생각보다) 많았음
- vuex의 mutation은 존재 의미를 불신하던 기능 (필요 없다 생각했음)
  Vue dev-tools 활용만으로도 vuex mutation의 가치를 느낌

 

프론트엔드 TDD: 정말 유용한가?

- 백엔드 tool을 개발하면서 TDD의 유용성을 체감함
- 그래서 프론트엔드 개발에도 TDD를 적용하는 것이 도리일 것이라고 생각,
  이번 작업에서 프론트엔드 TDD를 시도해봄
- 구현 코드보다 테스트 코드를 먼저 작성하면서 개발 요구사항을 정리해봄
  생각을 정리한다는 측면에서 나름 의미는 있었지만,
- 프론트엔드 테스트 코드는 구현 코드와 밀결합될 수 밖에 없음을 느낌
  그리고 프론트엔드의 문제점은 테스트 코드보다 눈과 손으로 먼저 체크하게 됨을 느낌
- 테스트 코드를 작성하는 번거로움 대비 실익을 느낄 수 없었음
- 단기간(3달) 동안 거의 혼자 하는 개발이어서 그랬을지도 모름. 베스트프랙틱스에 대한 조언을 구함

 

(이상)

 

 


 

2021-04-25.

HTML 태그의 data- 속성을 이용해서 실제 구현 코드와 테스트 코드의 의존성을 분리할 수 있다는 조언을 들었다.

예를 들어 화면에 표시되는 메시지 문자열을 검증하는 테스트 코드를 만들 경우, 특별한 data- 속성 (예를 들어 data-test-id="message")을 가진 HTML 요소를 찾아 그 문자열을 검증하면 된다. 이렇게 하면 개발자가 해당 문자열을 표현하기 위해 어떤 HTML 태그를 쓰는지, 어떤 id를 쓰는지 같은 세세한 구현 디테일과 분리된 테스트 코드를 작성할 수 있다. 그러면 차후 HTML 구조가 바뀌거나 엘리먼트 id가 바뀌어도 테스트 코드는 변경 없이 유지할 수 있다.

 


 

2022-03-14.

프론트엔드 TDD에 관한 좋은 책을 발견해서 많이 배웠다.

 

Testing Vue.js Applications

-- Edd Yerburgh 지음

-- Manning 펴냄

-- https://www.manning.com/books/testing-vue-js-applications

 

Testing Vue.js Applications

Testing Vue.js Applications</i> is a comprehensive guide to testing Vue components, methods, events, and output. Author Edd Yerburgh, creator of the Vue testing utility, explains the best testing practices in Vue along with an evergreen methodology that ap

www.manning.com

 

책 구석구석에서 Vue.js와 TDD에 대한 저자의 풍부한 경험을 배울 수 있었다.

무엇보다 TDD에 관해 "하면 안되는 것들" 을 분명히 일러주는 것이 좋았다.

  • 무엇을 테스트할지 결정하는 것이 핵심이다 - 가능한 적게 테스트하라
    • 개발환경/실행환경 설정에 관한 유닛 테스트를 하지 마라
    • 화면 스타일에 관한 유닛 테스트를 하지 마라
  • 유닛 테스트, 스냅샷 테스트, e2e 테스트가 모두 필요하다
  • 유닛 테스트를 많이, 스냅샷 테스트는 조금만, e2e 테스트는 더 조금만 하라

프론트엔드 테스트 피라미드 (The frontend testing pyramid)

 

TDD를 시작하면서 잘 해보려는 의욕 때문에 테스트 케이스를 가능한 많이 작성하려 했다. 특히 HTML 요소들이 화면에 제대로 표시되는지까지 유닛 테스트로 확인하려 했다. 이 책 덕분에 그러면 안된다는 것을 알게 됐다 (테스트 케이스는 가능한 적게 작성해야 하고, 화면 출력에 관한 테스트는 스냅샷 테스트로 커버해야 한다는 것을 알게 됐다).

 

Posted by ingeeC
,

Golang 다형성 메모

Dev 2021. 3. 14. 17:25

컴파일되는 JavaScript

개인적으로는 golang(고랭, 고언어)을 컴파일되는 JavaScript라고 생각한다.
golang에는 드러나지 않는 곳에서 많은 것을 처리하는 golang 런타임(JavaScript 인터프리터에서 파생된 무엇?)이 숨어있다. 그리고 golang 컴파일러는 golang 런타임을 뜯어서 큰 힘 들이지 않고 쉽게 만든 것 같다 (이건 칭찬이다). golang은 아름다움보다는 실용성을 추구한 언어다. 그게 golang의 매력이다.

 

같은듯 다른 '객체 (Object)'

c++ 같은 OOP 언어에서 객체는 변수와 메소드의 덩어리다. 그리고 type이다. c++로 객체지향 프로그래밍을 한다는 것은 달리 말하면 type을 정의하는 일이다.
반면, JavaScript에서 객체는 key-value 테이블이다. 그리고 instance다. JS Object를 딱히 type이라고 부르기 어려운 것이 JavaScript에서는 JS Object 아닌 것이 없기 때문이다. int도 JS Object이고, string도 JS Object이고, 심지어 function도 JS Object다.

 

다형성을 책임지는 interface 타입

golang은 type을 철저하게 체크한다. 하지만 JavaScript보다 조금 더 엄격한 정도다.
golang 코드의 interface 타입은 컴파일 시점에 결정되지 않는다. 실행 시점에 golang 런타임이 interface 타입을 체크한다 (즉, interface 타입 변수가 interface로서 갖춰야할 method 들을 구비하고 있는지 체크한다).

 

"Essential Go" 책의 설명을 참조하자.
https://essential-go.programming-books.io/reflection-c7fea6b176b74c54ab35f2d8fdd56f13

 

Reflection

Reflection

essential-go.programming-books.io

Go는 정적 타입 랭귀지다. 대부분의 경우 변수의 타입은 컴파일 시점에 알 수 있다. 하지만 interface 타입은 예외다. interface 타입 뒤에 있는 값이 실제 무슨 타입인지 컴파일 시점에는 알 수 없다.

 

요약하면 다음과 같다.

  • interface 타입으로 어떤 method가 필요한지 규격을 정의할 수 있다.
  • struct 타입으로 필요한 method를 구현할 수 있다.
  • 어떤 struct가 어떤 interface 규격을 만족시키는지 언어적으로는 명시하지 않는다 (명시할 수 없다). golang 런타임이 실행 시점에 체크한다.

 

샘플코드 - interface 타입을 이용한 다형성 구현

상이한 타입의 변수들을 단일한 interface로 일관성있게 다루는 것이 다형성의 매력이다.

package main

import (  
  "fmt"  
)

type writer interface {  
  write()  
}

type koreanWriter struct{}

func (k koreanWriter) write() {  
  fmt.Println("안녕하세요")  
}

type englishWriter struct{}

func (e englishWriter) write() {  
  fmt.Println("Hello")  
}

func main() {  
  kw := koreanWriter{}
  ew := englishWriter{}
  wa := []writer{kw, ew} 
  
  for _, iw := range wa {  
    iw.write()  
    //  
    // 상이한 타입의 변수들을 단일한 interface로  
    // 일관성있게 다루는 것이 다형성의 매력 
    //  
  }  
}

Posted by ingeeC
,