본문으로 건너뛰기

모범 사례

테스트 구성, 로그인, 상태 제어

안티 패턴 - 페이지를 공유합니다. UI를 사용하여 로그인하고 지름길을 사용하지 않습니다.

✔️ 모범 사례 - 개별적으로 스펙을 테스트하고, 프로그래밍 방식으로 앱에 로그인하고, 앱 상태를 제어합니다.

로그인 레시피에서 몇 가지 예시를 확인하세요.

요소 선택하기

안티 패턴 - 변경될 수 있는 불안정한 선택기를 사용합니다.

✔️ 모범 사례 - data-* 속성을 사용하여 선택기에 컨텍스트를 제공하고 CSS나 JS의 변경으로부터 독립시킵니다.

작성하는 모든 테스트에는 요소에 대한 선택기가 포함됩니다. 골칫거리를 줄이려면 변화에 탄력적인 선택기를 작성해야 합니다.

종종 다음과 같은 이유로 사용자가 요소를 지정하는 데 문제가 발생합니다.

  • 앱이 변경되는 동적 클래스나 ID를 사용함
  • 개발 중 CSS 스타일이나 JS 작동의 변경으로 선택기가 작동하지 않음

운 좋게도 이 두 가지 문제를 모두 피할 수 있는 방법이 있습니다.

  1. CSS 속성을 기반으로 요소를 지정하지 않음 (id, class, tag)
  2. textContent가 변경될 수 있는 요소를 지정하지 않음
  3. 요소를 더 쉽게 지정할 수 있도록 data-* 속성을 추가

작동 원리

다음 버튼과 상호 작용하고 싶다고 가정해 보겠습니다.

html
<button
id="main"
class="btn btn-large"
name="submission"
role="button"
data-cy="submit"
>
Submit
</button>
html
<button
id="main"
class="btn btn-large"
name="submission"
role="button"
data-cy="submit"
>
Submit
</button>

요소를 지정하는 방법은 다음과 같습니다.

선택기추천설명
cy.get('button').click()❌ 비추천최악. 너무 일반적이고 컨텍스트가 없음
cy.get('.btn.btn-large').click()❌ 비추천나쁨. 스타일링과 결합됨. 변경될 가능성이 높음
cy.get('#main').click()⚠ 가끔더 나음. 그러나 여전히 스타일링이나 JS 이벤트 수신기와 연결됨
cy.get('[name="submission"]').click()⚠ 가끔HTML 시맨틱이 있는 name 속성과 연결됨
cy.contains('Submit').click()✔️ 상황에 따라훨씬 나음. 그러나 여전히 변경될 수 있는 텍스트 콘텐츠와 연결됨
cy.get('[data-cy="submit"]').click()✔️ 언제나최고. 모든 변경에서 독립됨

요소를 tag, class, id로 찾는 것은 매우 불안정하고 변경되기 쉽습니다. 요소를 교체하거나, CSS를 리팩터링하고 ID를 갱신하거나, 요소의 스타일에 영향을 주는 클래스를 추가/제거할 수 있기 때문입니다.

대신 요소에 data-cy 속성을 추가하면 테스트 전용 선택기가 생깁니다.

data-cy 속성은 CSS 스타일이나 JS 작동 변경의 영향을 받지 않습니다. 즉, 요소의 작동이나 스타일과 연결되지 않습니다.

또한 이 요소가 테스트 코드에서 사용된다는 것을 모든 사람에게 분명히 할 수 있습니다.

텍스트 콘텐츠

위의 규칙을 읽고 나면 다음과 같은 궁금증이 생길 수 있습니다.

항상 데이터 속성을 사용해야 한다면 cy.contains()는 언제 사용할까요?

경험에 따르면 다음 질문에 답해보는 것입니다.

요소의 내용이 변경되면 테스트가 실패하기를 원하나요?

  • 예 - cy.contains()를 사용
  • 아니오 - 데이터 속성을 사용

위의 버튼 예시를 다시 살펴보겠습니다.

html
<button id="main" class="btn btn-large" data-cy="submit">Submit</button>
html
<button id="main" class="btn btn-large" data-cy="submit">Submit</button>

문제는 테스트에서 Submit의 텍스트 콘텐츠가 얼마나 중요한가입니다.

텍스트가 Submit에서 Save로 바뀌면 테스트가 실패하기를 원하나요?

Submit이라는 단어가 중요하며, 변경되어서는 안 되면 cy.contains()를 이용해 요소를 지정합니다.

텍스트가 변경되도 된다면 cy.get()과 데이터 속성을 사용합니다. 텍스트를 Save로 변경해도 테스트가 실패하지 않습니다.

afterafterEach 훅 사용하기

안티 패턴 - 상태를 정리하기 위해 afterafterEach 훅을 사용합니다.

✔️ 모범 사례 - 테스트 실행 before에 상태를 정리합니다.

많은 개발자가 테스트에서 생성된 상태를 정리하기 위해 afterafterEach 훅에 코드를 추가하곤 합니다.

예시:

js
describe('logged in user', () => {
beforeEach(() => {
cy.login()
})
afterEach(() => {
cy.logout()
})
it('tests', ...)
it('more', ...)
it('things', ...)
})
js
describe('logged in user', () => {
beforeEach(() => {
cy.login()
})
afterEach(() => {
cy.logout()
})
it('tests', ...)
it('more', ...)
it('things', ...)
})

남아 있는 상태는 좋은 친구

사이프러스의 가장 좋은 부분 중 하나는 디버깅 가능성입니다. 다른 테스트 도구와 달리 테스트가 끝나면 테스트가 완료된 정확한 지점에 작업 앱이 남습니다.

테스트가 완료된 상태에서 앱을 사용할 수 있는 좋은 기회입니다. 이를 통해 단계별로 앱을 구동하는 부분 테스트를 작성하여 테스트와 앱 코드를 동시에 작성할 수 있습니다.

이 사용 사례를 지원하기 위해 사이프러스를 만들어졌습니다. 실제로 사이프러스는 테스트가 종료될 때 자체 내부 상태를 정리하지 않습니다. 테스트가 끝날 때 상태가 남아 있기를 원합니다. 스텁, 스파이, 심지어 경로와 같은 것은 테스트가 끝날 때 제거되지 않습니다. 이는 앱이 사이프러스 명령을 실행하는 동안이나 테스트가 끝나고 수동으로 작업할 때 앱이 동일하게 작동함을 의미합니다.

각 테스트 후에 앱의 상태를 제거하면 해당 상태에서 앱을 사용할 수 있는 능력을 잃게 됩니다. 마지막에 로그아웃하면 테스트가 끝날 때 항상 동일한 로그인 페이지가 남습니다. 앱을 디버그하거나 부분 테스트를 작성하려면 항상 사용자 정의 cy.logout() 명령을 주석으로 남겨야 합니다.

장점은 없고 단점뿐

일단 어떤 이유로든 앱이 실행되기 위해 afterafterEach 코드가 절실히 필요하다고 가정해 보겠습니다. 해당 코드가 실행되지 않으면 모든 것이 손실되는 상황입니다.

하지만 이 경우에도 afterafterEach가 필요하지 않습니다.

다른 예로 데이터베이스를 재설정해야 하는 패턴을 살펴보겠습니다. 각 테스트 후에 데이터베이스에 레코드가 없는 상태로 만들어서 다음 테스트가 실행될 때 깨끗한 상태로 실행되게 하고 싶습니다.

다음과 같은 테스트 코드를 작성할 것입니다.

js
afterEach(() => {
cy.resetDb();
});
js
afterEach(() => {
cy.resetDb();
});

문제는 이 코드가 실행된다는 보장이 없다는 것입니다.

다음 테스트 전에 실행해야 하기 때문에 이 명령을 작성했다고 가정하면, 이 명령을 작성하기에 가장 나쁜 위치는 afterafterEach 훅입니다. 테스트 중간에 사이프러스를 새로고침하면 데이터베이스에 부분적인 상태가 생성되고 사용자 정의 cy.resetDb() 함수가 호출되지 않기 때문입니다. 사이프러스를 새로고침할 때 상태 재설정이 되지 않았기 때문에 다음 테스트는 즉시 실패하게 됩니다.

각 테스트 전에 상태를 재설정

여기서 가장 간단한 해결책은 재설정 코드를 테스트 실행 전으로 옮기는 것입니다.

beforebeforeEach 훅에 넣은 코드는 항상 테스트 전에 실행됩니다. 기존 코드 중간에 사이프러스를 새로고침한 경우에도 동일합니다.

이곳은 또한 모카의 루트 수준 훅을 사용하기에 좋은 위치입니다.

지원 파일은 테스트 파일이 평가되기 전에 로드되기 때문에 이 설정을 넣기에 좋은 위치는 지원 파일입니다.

루트에 추가한 훅은 항상 모든 스위트에서 실행됩니다.

cypress/support/e2e.js 또는 cypress/support/component.js
js
beforeEach(() => {
// 이 코드는 모든 파일의 모든 테스트 전에 실행됩니다.
cy.resetDb();
});
cypress/support/e2e.js 또는 cypress/support/component.js
js
beforeEach(() => {
// 이 코드는 모든 파일의 모든 테스트 전에 실행됩니다.
cy.resetDb();
});

상태 재설정이 필요한가요?

사이프러스는 이미 각 테스트 전에 상태를 지워 자동으로 테스트 격리를 적용합니다. 사이프러스에서 이미 정리한 상태를 정리하려는 것이 아닌지 확인할 필요가 있습니다.

정리하려는 상태가 서버에 있는 경우에는 반드시 해당 상태를 정리해야 합니다. 그러나 상태가 현재 테스트 중인 앱과 관련된 경우에는 정리하지 않아도 됩니다.

상태를 정리해야 하는 유일한 경우는 한 테스트가 실행하는 작업이 다른 테스트 다운스트림에 영향을 미치는 경우입니다. 이러한 경우에만 상태 정리가 필요합니다.