본문 바로가기

FrontEnd+

cypress - 테스트 주도 개발(TDD, BDD) 적용

 

 

 

백엔드와 달리 프론트엔드는 테스트 주도 개발(TDD)을 할 수 있는 환경이 잘 갖춰져 있지는 않지만, 테스트도구 사용자들은 만족도가 높은 편이라고 한다. 테스트 도구 중 하나인 cypress는 브라우저 기반의 GUI를 지원하고 문서 정리가 잘되어있어 진입장벽이 낮다.

 

용어부터 정리하고 넘어가자.

 

관련용어 정리

TDD(Test-Driven Development)

요구사항에 걸맞는 테스트 케이스를 우선 작성한 다음 ,각 테스트를 통과하기 위한 최소한의 코드를 작성하고 리팩토링 하는 프로그래밍 방식이다. 테스트의 단위는 함수 단위로 매우 작고 거의 모든 함수가 테스트 대상에 포함된다. 모듈크기를 작게하고 모듈간 의존성을 작게한다.

add(1, 1)이 2인지 확인하는 것은 TDD 이다.

 

BDD(Behaviour-Driven Development)

TDD에서 파생된 개념으로 BDD에서는 사용자의 행위를 작성한 기획서의 '시나리오'를 기반으로 테스트 케이스를 작성하며 함수 단위 테스트를 권장하지 않는다. 하나의 '시나리오'는 Given(초기설정값), When(실행조건), Then(기대결과) 구조를 기본 패턴으로 갖는다. 더 간략하게 정리하면 테스트 대상의 상태 변화를 테스트하는 것이다. 사용자의 인터렉션이 수반되는 프론트엔트 에서는 TDD보다 BDD가 더 적절하다고 볼 수 있겠다.

사용자가 "="를 눌렀을 때, 1+1의 값 2가 화면에 표시되는지 확인하는 것은 BDD이다.

 

유닛테스트(Unit Test)

가능한 가장 작은 단위의 코드를 테스트하는 기법이다. 빠른 디버깅이 가능하지만 앱의 전체 플로우가 정상임을 보장받을 수 없다.

 

통합테스트(Integration Test)

여러 요소가 상호작용할 때 의도한 대로 동작하는지 테스트하는 기법이다.

 

E2E테스트(End-to-End Test)

기능테스트(Functional Test)라고도 불린다. 사용자가 직접 앱을 사용하는 것처럼 동작하는지 테스트하는 기법이다. 프론트엔드 환경에서는 cypress, testcafe, nightwatch 등의 테스트 도구가 있다.

 

참고자료

- BDD에 대한 간략한 정리
- 프론트엔드, 어떻게 테스트할 것인가
- kotest가 있다면 TDD 묻고 BDD로 가!
- 나무위키

 

cypress 설치 및 실행하기

cypress 공식 레포를 클론하고, 패키지 매니저 npm(권장)으로 설치해준다.

git clone https://github.com/cypress-io/cypress.git
cd cypress
npm install cypress --save-dev
./node_modules/.bin/cypress open

 

또는 더 쉽고 깔끔하게 사용할 수 있는 메이커준 님의 레포를 클론하고, 패키지 매니저 yarn으로 설치해준다. 아래 예제는 이 방법을 따라 진행했다.

git clone https://github.com/imakerjun/cypress-basic.git
cd cypress-basic
yarn install
yarn run cypress open

 

 

cypress 테스트 파일 작성하기

cypress/integration 디렉토리 내 'file_name.spec.js' 파일을 생성하고 다음 형식에 따라 파일을 작성한다.

describe('테스트 전체 이름', () => {
  beforeEach(() => {
    cy.visit('접속할 서버 포트');
  })

  it('각 테스트 내용을 적는다.', () => {
    cy.get('선택자1').click();
    cy.get('선택자2').should('조건', '값');
  });

 

 

주로 사용하는 구문을 요약해보면 다음과 같다. not을 붙인 부정 Assertion을 사용할 경우 다른 이유로 테스트를 통과할 수도 있기 때문에 가능하면 긍정 Assertion을 사용하도록 한다.

// 길이
cy.get('li.selected').should('have.length', 3)

// 클래스
cy.get('form').find('input').should('not.have.class', 'disabled')

// 값
cy.get('textarea').should('have.value', 'foo bar baz')

// 텍스트
cy.get('a').parent('span.help').should('not.contain', 'click me')

// 가시성
cy.get('button').should('be.visible')

// 존재
cy.get('#loading').should('not.exist')

// 상태
cy.get(':radio').should('be.checked')

// CSS
cy.get('.completed').should('have.css', 'text-decoration', 'line-through')
cy.get('#accordion').should('not.have.css', 'display', 'none')

 

 

 

BDD Assertions 사용 예제

describe('ui-counter', () => {
  beforeEach(() => {
    cy.visit('http://localhost:5500/');
  });

  it('생성시 버튼과 초기값(10)을 렌더링 한다', () => {
    cy.get('.count-display').should('have.value', '10');
  });

  it('+ 버튼 클릭시 1 증가한다.', () => {
    cy.get('.count-display').then(($input) => {
      const prevInputValue = Number($input.val());

      cy.get('.plus-button').click();
      cy.get('.count-display').should('have.value', prevInputValue + 1);
    });
  });

  it('count max값이 12이다.', () => {
    for (let i = 0; i < 10; i++) {
      cy.get('.plus-button').click();
    }

    cy.get('.count-display').should('have.value', '12');
  });
});

 

 

TDD Assertions 사용 예제

describe('ui-counter', () => {
  beforeEach(() => {
    cy.visit('http://127.0.0.1:5500/woowa/cypress-basic/#');
  });

  it('생성시 버튼과 초기값(10)을 렌더링 한다', () => {
    cy.get('.count-display').then(($input) => {
      assert.equal(+$input.val(), 10);
    });
  });

  it('+ 버튼 클릭시 1 증가한다.', () => {
    cy.get('.count-display').then(($input) => {
      const prevInputValue = +$input.val();

      cy.get('.plus-button').click();
      cy.get('.count-display').then(($input) => {
        assert.equal(+$input.val(), prevInputValue + 1);
      });
    });
  });

  it('count max값이 12이다.', () => {
    for (let i = 0; i < 10; i++) {
      cy.get('.plus-button').click();
    }

    cy.get('.count-display').then(($input) => {
      assert.isAtMost(+$input.val(), 12);
    });
  });
});

 

예제 코드는 유조님과 페어프로그래밍으로 함께 작성하였다!

 

 

 

 

 

 

 

실용적인 프론트엔드 테스팅 전략