Cypress를 통한 안정적인 제품 배포
Fei Han
February 7, 2021
Apache APISIX Dashboard는 사용자가 프론트엔드 인터페이스를 통해 Apache APISIX를 쉽게 운영할 수 있도록 설계되었으며, 프로젝트 시작 이후 552개의 커밋과 10개의 릴리즈가 있었습니다. 이렇게 빠른 제품 반복 속도에서 오픈소스 제품의 품질을 보장하는 것이 중요합니다. 이를 위해 우리는 안정적인 제품 제공을 보장하기 위해 E2E 테스트 모듈을 도입했습니다.
프론트엔드 E2E란 무엇인가?
E2E는 "End to End"의 약자로, "종단 간" 테스트로 번역될 수 있습니다. 이는 사용자 행동을 모방하여 시작점에서 작업이 완료될 때까지 단계별로 동작을 실행합니다. 철저한 테스트는 코드 변경이 원래의 로직을 깨뜨리지 않도록 방지합니다.
왜 Cypress를 선택했는가?
우리는 선택 연구 기간 동안 Taiko, Puppeteer, TestCafe, 그리고 Cypress를 사용하여 라우트 생성 테스트 케이스를 작성했으며, 각 테스트 프레임워크의 특징을 경험하기 위해 각각의 프레임워크로 케이스를 작성했습니다.
Taiko는 스마트 선택자 기능이 특징이며, 텍스트 내용과 위치 관계를 기반으로 사용자가 조작하려는 요소를 지능적으로 찾아낼 수 있고, 시작 비용이 낮아 테스트 케이스를 빠르게 완료할 수 있습니다. 그러나 테스트 케이스를 작성할 때 사용자 친화적이지 않습니다. 사용자가 실수로 터미널을 종료하면 작성된 모든 테스트 케이스가 손실되며, 완전한 테스트 케이스를 실행하려면 다른 테스트 러너와 함께 사용해야 하므로 사용자의 학습 비용이 증가합니다.
Puppeteer는 최고의 성능을 가지고 있습니다. 그러나 테스트는 Puppeteer의 주된 초점이 아닙니다. 이는 웹 크롤링에 널리 사용됩니다. 우리 프로젝트는 ANTD가 공식적으로 추천하는 E2E 테스트 프레임워크인 Puppeteer로 시작했으며, 사용해 본 후 Puppeteer가 비프론트엔드 개발자에게 친숙하지 않아 다른 사용자들이 참여하기 어렵다는 것을 발견했습니다. 사용자가 테스트 케이스를 작성할 때 지능적인 요소 위치 지정 기능이 부족하여 학습 곡선이 매우 높습니다.
TestCafe는 놀라울 정도로 설치가 쉽고, 내장된 대기 메커니즘이 있어 사용자가 페이지 상호작용을 위해 적극적으로 대기할 필요가 없으며, 동시 다중 브라우저 테스트를 지원하여 다중 브라우저 호환성 테스트에 도움이 됩니다. 단점은 디버깅 과정이 그다지 사용자 친화적이지 않으며, 각 테스트 케이스 변경 후 새로운 사용 사례를 실행해야 한다는 것입니다. 개발자들은 기본적인 Javascript 구문을 알아야 합니다. 또한, 다른 프레임워크에 비해 실행 속도가 상대적으로 느리며, 특히 withText()를 사용하여 요소를 찾을 때 더욱 그렇습니다.
종합적인 비교 후, 우리는 최종적으로 Cypress를 프론트엔드 E2E 프레임워크로 선택했으며, 그 이유는 다음과 같습니다:
- 간단한 구문
Cypress 테스트에서 사용되는 구문은 매우 간단하고 읽고 쓰기 쉽습니다. 약간의 연습만으로 테스트 케이스 작성을 마스터할 수 있으며, 이는 오픈소스 프로젝트에 중요합니다. 왜냐하면 E2E 테스트 케이스에 관심이 있는 커뮤니티가 최소한의 학습 비용으로 테스트 케이스 작성에 참여할 수 있기 때문입니다.
- 쉬운 디버깅
테스트 케이스를 디버깅할 때, 우리는 Cypress의 Test Runner를 사용할 수 있으며, 이는 다차원 데이터를 제공하여 문제를 빠르게 파악할 수 있게 합니다.
- 테스트 케이스 실행 상태를 보여줍니다. 성공, 실패, 진행 중인 실행 횟수를 포함합니다.
- 전체 테스트 세트 실행에 소요된 총 시간을 표시합니다.
- 내장된 Selector Playground는 요소를 찾는 데 도움을 줍니다.
- 각 사용 사례의 실행 단계를 보여주고, 완료된 후 각 실행 단계에 대한 정보를 보여주는 스냅샷을 형성합니다.
- 활발한 커뮤니티
Cypress는 큰 사용자 커뮤니티를 가지고 있으며, 커뮤니티 내에서 많은 사람들이 자신의 경험과 아이디어를 공유하고 있습니다.
이는 문제를 만났을 때 도움이 되며, 다른 사람들이 이전에 겪었던 문제를 만날 가능성이 높습니다. 또한, 새로운 기능이 요청될 때, 우리는 커뮤니티에 참여하여 Cypress에 추가하고 싶은 기능을 논의하고 추가할 수 있습니다. 이는 APISIX 커뮤니티에서 하는 것과 같습니다: 커뮤니티의 의견을 듣고 피드백을 제공합니다.
- 명확한 문서
Cypress의 문서 구조는 더 명확하고 포괄적입니다. 사용 초기 단계에서, 우리는 공식 문서 가이드를 기반으로 Cypress를 프로젝트에 빠르게 도입하고 첫 번째 케이스를 작성할 수 있었습니다. 또한, 문서 사이트에는 사용자에게 최선의 실천 방법에 대한 좋은 지침을 제공하는 많은 양의 문서가 있습니다.
Cypress와 APISIX Dashboard
현재 APISIX Dashboard를 위해 49개의 테스트 케이스가 작성되었습니다. 우리는 GitHub Action에서 해당 CI를 구성하여 각 병합 전에 코드가 통과되도록 하여 코드 품질을 보장합니다. 우리는 Cypress의 최선의 실천 방법을 참조하고 우리 프로젝트와 결합하여 APISIX Dashboard에서 Cypress의 사용을 여러분과 공유합니다.
- 일반적으로 사용되는 기능을 명령으로 캡슐화합니다.
로그인을 예로 들면, 로그인은 시스템에 들어가는 데 필수적인 부분이므로 이를 명령으로 캡슐화하여 각 케이스 실행 전에 로그인 명령을 호출할 수 있게 합니다.
Cypress.Commands.add("login", () => {
cy.request(
"POST",
'http://127.0.0.1/apisix/admin/user/login',
{
username: "user",
password: "user",
}
).then((res) => {
expect(res.body.code).to.equal(0);
localStorage.setItem("token", res.body.data.token);
});
});
beforeEach(() => {
// init login
cy.login();
})
- 선택자와 데이터를 공용 변수로 추출합니다.
테스트 코드의 의미를 사용자가 더 직관적으로 이해할 수 있도록, 우리는 선택자와 데이터를 공용 변수로 추출합니다.
const data = {
name: 'hmac-auth',
deleteSuccess: 'Delete Plugin Successfully',
};
const domSelector = {
tableCell: '.ant-table-cell',
empty: '.ant-empty-normal',
refresh: '.anticon-reload',
codemirror: '.CodeMirror',
switch: '#disable',
deleteBtn: '.ant-btn-dangerous',
};
- cy.wait(someTime) 제거
우리는 Cypress 초기에 cy.wait(someTime)을 사용했지만, cy.wait(someTime)이 네트워크 환경과 테스트 머신의 성능에 너무 의존적이라는 것을 발견했습니다. 이는 네트워크 환경이나 머신 성능이 나쁠 때 테스트 케이스가 오류를 보고할 수 있습니다. 권장되는 실천 방법은 cy.intercept()와 함께 사용하여 명시적으로 대기할 네트워크 리소스를 지정하는 것입니다.
cy.intercept("https://apisix.apache.org/").as("fetchURL");
cy.wait("@fetchURL");
요약
현재 APISIX Dashboard는 49개의 테스트 케이스를 작성했습니다. 앞으로 우리는 프론트엔드 E2E 커버리지를 계속 강화하고, 각 새로운 기능이나 버그 수정 제출에 대해 커뮤니티가 테스트 케이스를 작성하는 데 동의하도록 요구하여 제품의 안정성을 보장할 것입니다.
세계적인 게이트웨이 제품을 다듬는 데 함께해 주세요.