[Tool] cypress와 간단한 사용 방법
Cypress
Frontend Test
테스트의 종류
- e2e
- integration
- unit
- static
End to End (e2e)
실제 유저가 앱 내 행동(클릭 등)과 같이 동작하게 하고
그 일련의 function들이 정확하게 돌아가는지 verify하는 테스트
ex) cypress, testcafe, nightwatch
Integration
여러 unit들이 잘 상호 작용하여 동작하는지 확인하는 테스트
- store에 연결된 component 테스트 혹은 생성자/리듀서 테스트 등..
Unit
하나의 unit (function)이 각각 독립적으로 잘 동작하는지 확인하는 테스트
ex) jest, chai, mocha, jasmine, karma … (js, ts 환경에서 테스트 가능)
ex) DOM: react-testing-library, Enzyme .. (가상돔 제공으로 렌더된 컴포넌트 테스트 가능)
- 하나의 function이 정상적으로 동작했는지는 알 수 있지만, 그 function이 왜 동작했는지 언제 동작했는지 등 정상적인 플로우인지는 확인할 수 없다.
Static
코드 작성 시 타입 혹은 타이포 에러를 확인하는 테스트
ex) eslint, typescript ..
static → e2e로 갈수록
- speed : 느려짐.
- e2e로 갈수록 실행하는 코드가 늘어나기 때문
- cost : 비용 많아짐.
- CI 환경에서 더 느려질 수도 있다. (테스트를 언제 진행하느냐에 따라 다르지만)
- 엔지니어가 테스트 코드를 작성하고 유지 관리하는 비용도 늘어난다.
사실 e2e보다 신뢰성, 정확성이 높은 것이 실제 사용자가 사용해보도록 하고 버그 리포팅을 받는 것
⇒ 너무 비용이 비싸고 오래 걸린다.
하지만,
사용 방식과 유사하게 테스트들이 resemble되게 코드를 작성하면 신뢰성을 가져갈 수 있다.
Why 테스트?
테스트의 장점이 무엇 이길래 테스트, 테스트할까.
테스트는 장점만 있을까?
테스트 장점
- 자신감, 확신, 신용, 신뢰성 보장
개발자에게 확신을 줘서 개발 혹은 리팩토링 간 기존 로직 혹은 베이스 플로우 등을 망치지 않을 것이라는 확신을 줌. - 빠른 피드백
테스트할 부분을 타겟팅하여 테스팅하기 때문에 고쳐야 할 코드를 바로 알 수 있음. - 다큐멘테이션
좋은 테스트 코드는 테스트 코드만으로도 맥락을 이해할 수 있고, 예제로도 이용 가능.
이 플로우 혹은 함수가 어떤 동작을 하는지를 테스트 코드만 보고 알 수 있다.
심지어 전체적인 흐름은 사용자 행동 기반으로 보여주는 테스트 코드가 더 보기 쉬울 수도.
테스트 단점
- 개발 공수 많이 듦.
- 개발 간 생산성 저하로 이어질 수도
- 테스트 코드 또한 거대한 관리 포인트가 됨.
개인적으로는 환경 구축 및 설정이 어려운 포인트였음
(yaml 파일 및 스크립트 작성, 테스트 시점 등등)
- 러닝 커브 필요.
- 처음 셋업도 어려울 수 있고,
- 처음 테스트 코드 작성은 결코 쉽지 않다.
- 결국 개발자가 생각하는 엣지 케이스에 지나지 않음.
- 결국 QA 엔지니어가 있지 않는 이상은 개발자가 생각해낸 케이스로 커버.
Cypress
cypress에서 e2e는
url에 랜딩해서 그 페이지에서 일어나는 컴포넌트, 일, 이벤트들을 테스팅할 수 있음.
강력한 테스팅 도구
- e2e test
- component test
- integration test
- unit test
모두 가능.
*component 테스트 같은 경우는 페이지에 상관없이 테스트하려는 컴포넌트에 초점을 맞추어 테스트하는 것.
작성법
주의 및 고려 사항
⚠️ E2E Test 작성 시
e2e 테스트를 짜려면 먼저 high quality의 test scenario가 있어야 한다.
그렇지 않으면
- 신뢰성이 떨어지고, 관리가 어려워져 유지보수가 힘들어지고, 결국 깨지고 삭제해버릴 수도 있기 때문이다.
🤷 언제 동작하게 할 것인지?
e2e 테스트는 느리기 때문에 commit마다 하거나 on save 마다하는 등 자주 하게 되면 생산성이 떨어진다.
PR 단계에서 돌아가게 할 것인지, build 과정에서 돌아가게 할 것인지는 상황에 맞게 설정해야 할 것이다.
👮 도입한 순간 항상 명심
이 테스트 코드 또한 사실 거대한 관리 포인트가 하나 생긴 것이라고 볼 수 있다.
하지만 e2e는 바로 피드백을 받을 수 없는 테스트이기 때문에 만약 hotfix 이슈가 생기거나 플로우에 변화, 작은 변화라도 생기면 e2e 테스트까지 별도로 관리를 해주어야지 아니면 데드 코드에 불과해진다. (항상 개발 워크 플로우 포함 시켜야 함)
📍어디 어디 적용할 것 인지
e2e 테스트는 느리고 시간이 오래 걸리기에 모든 도메인에 다 추가하는 것이 좋은 것은 아닐 수 있다.
→ 클릭 마다 랜딩이 주기적으로 바뀌는 페이지이면 e2e를 도입하면 개발 공수가 많이 들고 자주 수정해주어야 한다.
실제 작성
setup 자체를 다루지는 않을 예정
home 페이지 랜딩 테스트
작성 코드
describe("home-landing-test", () => {
// describe를 login 된 상태와 아닌 상태로 나눔.
describe("landed home page with logged in", () => {
// before: 작성된 테스트들이 모두 실행되기 전에 한 번 실행
// beforeEach: 작성된 테스트들이 각각 실행되기 전마다 한 번씩 실행
beforeEach(() => {
// NOTE: set jwt token in cookie
cy.setCookie("jwt", "test-token");
// NOTE: fake response intercept
cy.intercept("GET", `/auth/validate`, {
statusCode: 200,
body: {
email: "e2e.test@test.kr",
uuid: "test-uuid",
},
});
// 홈 화면에 뿌려지는 데이터들 필요한 것이 있으면 intercept 해서 fake response 설정해주기.
cy.intercept("GET", "api/user/profile", {
statusCode: 200,
body: {
createdAt: "2021-11-29 19:27:06",
description: null,
email: "e2e.test@test.kr",
name: "test te",
gender: "male",
userID: "test-uuid",
},
});
// NOTE: go to home page
cy.visit(`${process.env.BASE_URL}/`);
});
// h1 태그에 "안녕하세요"라는 text가 있어야 한다.
it("should display greeting in korean", () => {
cy.get("h1").contains("안녕하세요");
});
it("should display Sub Title", () => {
cy.get("h2").contains("Sub Title");
});
});
// 로그인 안되어 있는 경우
describe("landed home page without being logged in", () => {
it("should redirect to login page", () => {
cy.visit(`${process.env.BASE_URL}/`);
cy.url().should("include", "/signin");
});
});
});
-
로그인 상태 테스트
- 모든 테스트 시작 전 세팅
- set token
token은 실제로 받아오기 어려우므로 테스트로 cookie를 set 할 수 있다.(
cy.setCookie
) - response 실제 데이터 요청을 하기 때문에 실제 token이 없는 이 테스트에서는 validate api, user info api 등의 데이터도 백엔드에서 실제 값을 가져올 수 없다. 실제 테스트 화면에 나타난 데이터와 원하는 결과 데이터가 다를 수 있어 에러 발생 가능성 그 때 intercept 혹은 route를 사용하여 요청을 가로채 fake response를 설정하고 반환해줄 수 있다.
- set token
token은 실제로 받아오기 어려우므로 테스트로 cookie를 set 할 수 있다.(
-
h1 데이터 테스트
h1 태그에 어떤 값이 들어가는지 확인 (
cy.get("h1")
)이후 chaning으로 사용할 수 있는 matcher는 매우 많아 docs 참고해가며 작성.
.element
/#element
이렇게 jQuery 처럼 요소를 꺼내올 수 있음.
대신
cy.get(”.element”)
이건 안됨. (cy.get은 동기적으로 요소를 반환해주지 않음.)$(”.element”)
이렇게 써야 한다.
- 모든 테스트 시작 전 세팅
-
검사
- cypress open: cypress app을 이용해 연다
- cypress run: cypress app 없이 console로 확인 가능.
-
기타
- 사실 기능은 매우매우 다양하고 많다.
- cross browsing (cypress app에서 chrome, firefox .. 지원)
- snapshot
- user interaction
- 비동기
- 등등
- 그러나 모두 다루기에는 양이 많고 문서를 참조하는 게 더 좋을 것 같아서 전체적인 부분만 정리해두었다.
- 사실 기능은 매우매우 다양하고 많다.
컨벤션
⇒ 코드 작성되면 거기에 따라서 컨벤션 작성
But,
Cypress Docs
- Set up the application state.
- Take an action.
- Make an assertion about the resulting application state.
- Set up the application state.
앱의 현재 특정 상태를 지정cy.visit("http://localhost:3000");
- Take an action.
유저가 행하게 될 동작을 테스트로 일으킴cy.get("button").click();
- Make an assertion about the resulting application state.
결과가 어떻게 되어야 하는지 설정cy.url().should("include", "/signin");
⇒ 이런 패턴은 정형화된 것이 아니기에 컨벤션은 상황에 따라서 정하면 좋을 것 같다.
예를 들어,
-
given과 userevent는 따로
const $buttonToHome = $(".loginButton"); $buttonToHome.click();
- intercept 등은 어떻게 정리할 것인지
- describe는 어떻게 나눌 건지
- 시나리오에서 스텝을 나눠서 스텝은 describe로 할 건지
- 성격을 하나로 묶어서 step은 it으로 할 건지 등등
개인적인 결론
테스트 코드가 현재 서비스의 코드를 커버하고 있는 정도를 나타내주는 coverage
수치를 무조건 100%로 맞추거나 몇 이상 지향하는 것보다는
현재 주어져 있는 상황에 맞게 최대한 커버하는 것 만으로도 좋지 않을까 한다.
주객전도가 되면 안되고,
분명히 테스트 코드를 작성하는 이유는
사용자가 프로덕트를 사용할 때 혹은 개발자가 프로덕트를 빌드할 때 신뢰성을 높이기 위함이다.
물론 커버리지가 높아지면 신뢰성이 더 좋다는 얘기지만,
개발 생산성도 고려해야 하고, 오히려 자주 변경되는 구간은 flexiblity를 놔두어도 되는 상황이 있지 않을까 생각해본다.
상황에 맞게 개발자들이 잘 논의해서 도입하면 좋을 것 같다.
우리 회사는 QA 엔지니어가 없기에 지점 등록 같은 메인 플로우 피쳐에도 적용하면 좋을 것 같긴 하다.
개발 공수와 관리 포인트는 무조건 많아지긴 할 것 같다.
기능에 초점을 맞춘다면 확실히 e2e만 도입하고, unit이나 ui 테스트는 보류하는 것도..
댓글남기기