자동화테스트/Selenium2018. 11. 10. 08:00

기능적(functional) 테스트 자동화 도구의 왕좌를 노리는 새로운 도구가 나타났다. 바로 Cypress.io다. Cypress는 빠른가? 그렇다. Cypress는 상호적인가? 물론. Cypress는 믿을만한가? 당연히. 그리고 무엇보다... 멋지다!

하지만 Cypress가 Selenium WebDriver의 대안일까? 현재 웹 자동화와 테스트 프레임워크의 제왕이라고 할 수 있는 Selenium은 과연 이 새로운 강자를 두려워할까, 아니면 다 알고 있다는 듯이 "아직, 애잖아"라며 자애로운 미소를 지을까.

나는 "Cypress가 Selenium WebDriver보다 더 좋은가?"라는 질문을 많이 받았다. 솔직히 "A vs B" 같은 성격의 글을 쓸 때 가장 쉬운 방법은 무엇이 "최고"인지를 찾는 것이다. 하지만 이 방식은 따르지 않을 것이다. 대신에 Cypress가 Selenium WebDriver와 어떻게 다른지를 설명하도록 하겠다.

Cypress와 Selenium WebDriver가 어떻게 다른지를 설명하면, Cypress가 어떻게 동작하는지를 이해하게 될 것이다. 또한 이 글은 왜 Cypress가 테스트를 자동화하기 위해 WebDriver가 선택한 방식과는 완전히 다른 방식을 선택했는지도 설명할 것이다.

Cypress와 Selenium WebDriver의 닮은 점은 무엇일까? 둘 다 브라우저를 조작하고 자동화하며, 이를 통해 사용자의 액션을 시뮬레이션하는 테스트를 작성할 수 있게 해 주고 이들 테스트의 결과가 맞는지를 확인해준다. 하지만 닮은 점은 여기까지가 전부다. 이 두 테스팅 도구의 차이점을 알고 이러한 차이점이 생긴 이유를 알게 되면, 언제 어떤 도구를 선택해야 하는지를 아는 데에 도움이 될 것이다.

자, 이제 차이점에 대해 알아보자.

프론트엔드 개발자를 위한 도구이다!

2000px-front-end_magazine_logo svg_

Selenium WebDriver는 웹 애플리케이션의 E2E(end-to-end) 회귀(regression) 테스트를 위해 만들어졌다. Selenium 프론트엔드 테스트 프레임워크를 사용하는 것은 대부분 개발자들이 아닌 QA 개발자들이다. 이것은 프론트엔드 개발자들이 자신들의 코드를 테스트한다는 개념이 그리 보편적이진 않기 때문이다.

하지만 프론트엔드 개발자들의 세상에 작은 혁명이 시작되고 있다. 프론트엔드 개발자들이 자신만의 테스트를 작성하기 시작한 것이다. 그렇다. 그들은 애자일 소프트웨어 방법론이라는 도전을 헤쳐나가고 있다. 이들은 자신들이 직접 테스트를 작성하지 않는다면 애자일한 개발을 할 수 없다는 것을 알고 있다. 이들은 단위 테스트나 통합 테스트를 작성할 뿐만 아니라, QA 개발자들이 작성하는 자동화 테스트와 마찬가지로 실제 브라우저를 이용해 프론트엔드를 검증하는 E2E 테스트까지 작성하고 있다. "Shift Left" 운동이 이러한 경향을 잘 반영하고 있다.

프론트엔드 개발자들에게 필요한 E2E 테스트는 QA 개발자들에게 필요한 E2E 테스트와는 다르다. 프론트엔드 개발자들에게는 프론트엔드, 백엔드, 데이터베이스 시스템 등의 전체 애플리케이션이 배포되는 스테이징 환경이 필요 없다. 이들은 프론트엔드 영역만 부분적으로 실행할 수 있으면 테스트를 할 수 있으며, 손쉽게 백엔드를 목킹한다.

바로 이 사실, 즉 Cypress의 주 사용자가 프론트엔드 개발자라는 사실이 Cypress와 WebDriver의 차이점을 만드는 핵심 요소이다. 이들은 다른 사용자를 위한 다른 도구이다.

자바스크립트만 사용 가능하다!

pasted-image-0

프론트엔드 개발자들은 오직 하나의 언어로만 개발한다. 바로 자바스크립트이다. 자바스크립트는 브라우저가 실행할 수 있는 유일한 언어이기 때문에 다른 선택의 여지가 없다. 이 제약은 장점과 단점을 모두 갖고 있지만, 프론트엔드 개발자들을 위한 언어가 한 가지라는 사실은 분명하다(물론 TypeScript와 같은 변형이 있기는 하다).

그리고 Cypress가 프론트엔드 개발자들을 위한 도구이기 때문에, Cypress 테스트는 오직 자바스크립트로만 작성할 수 있다. 다른 어떤 언어도 지원하지 않는다. C#, Python, Ruby, R, Dart, Objective-C, 자바스크립트 등 다양한 언어와의 바인딩을 지원하는 Selenium WebDriver와 비교해보자.

즉, Cypress로 테스트를 하기 위해서는 자바스크립트를 배워야 한다는 것을 명심하자. 이는 멋진 일이다. 오늘날의 자바스크립트는 더 이상 예전의 오래된 자바스크립트가 아니기 때문이다. 현대적이고 강력하며 간결한 언어이며, 이미 몇 년 전에 자바 Maven을 넘어서 세상에서 가장 큰 패키지 저장소가 된npm을 갖고 있는 언어이기도 하다.

Mocha만 사용 가능하다!

pasted-image-0-1

Cypress는 언어를 제한할 뿐만 아니라, 사용하는 테스트 프레임워크 또한 제한한다. 테스트를 작성하기 위해서는 Mocha 테스트 라이브러리를 사용해야만 한다(자바스크립트에서 테스트 프레임워크가 하는 역할은 자바에서의 Junit이나 C#에서의 NUnit과 비슷하다). Jest나 Tape과 같은 다른 테스트 프레임워크는 사용할 수 없다.

반면, Selenium WebDriver는 테스트 프레임워크를 제한하지 않는다. 심지어 테스트 프레임워크를 사용할 필요도 없다. 테스트가 아니라 브라우저를 조작하는 일반적인 프로그램도 작성할 수 있다. 많은 프로젝트가 WebDriver를 이러한 방식으로 사용하는데, 예를 들면 웹 페이지를 크롤링해서 정보를 수집하는 경우를 들 수 있다.

하지만 Cypress는 초기부터 프론트엔드 테스트를 작성하는 데에만 집중하기로 결정했고, 테스트 프레임워크 내부에서만 사용할 수 있으며, 그 테스트 프레임워크는 반드시 Mocha 여야만 한다.

이제 충분해. Cypress 코드를 좀 보여줘!

"아아 알겠어, 알겠다고. 근데 좀 뜬구름 잡는 기분이라, 부탁인데 Cypress 코드 좀 보여주지 않을래?" 아, 물론. 여기를 보시라.

'use strict'

describe(`TodoMVC using Cypress`, function () {
  it('can add todos and then delete them', () => {
    cy.visit('http://todomvc.com/examples/react/#/')

    cy.get('.new-todo').type('Cook dinner{enter}')

    cy.get('.view > label').should('have.text', 'Cook dinner')

    cy.get('.new-todo').type('Clean house{enter}').then(() => {debugger})

    cy.get('.view > label').eq(1).should('have.text', 'Clean house')

    cy.get('.toggle').eq(1).click()

    cy.contains('1 item left').should('exist')

    cy.contains('Clear completed')
  })
})

위의 예제는 Cypress의 명령(command)와 단언(assertion)에 대한 예제이다.

cy.visit는 브라우저가 주어진 URL로 접속하도록 만든다.

cy.get는 질의문에 대한 엘리먼트의 참조를 반환하며, 이 참조를 이용해서 키보드 입력, 클릭 등을 하거나 contains, 단언 등을 통한 검증을 할 수 있다.

예제에서 볼 수 있듯이, 표면적으로는 Selenium Webdriver와 놀랄 만큼 비슷하지만 더 간단하면서도 훨씬 더 강력하다.

Chrome만 사용 가능하다!

Cypress 테스트 러너는 Chrome에서만 동작한다. Firefox나 Safari, Edge, IE 등은 지원하지 않는다. 아마 WebDriver의 놀라운 브라우저 지원에 익숙한 QA 개발자들에게는 충격일 수도 있겠다. 하지만 Cypress는 Chrome 전용이다. 크로스 브라우저 지원을 위한 이슈가 열려 있기는 하지만, 1년이나 지난 상태이다. 아마도 Cypress가 다른 곳에 좀 더 우선순위를 두고 있는 것 같다.

왜 프론트엔드 개발자들이 Chrome에서만 테스트를 실행하려고 할까? 내 생각에 이 질문에 대한 답변은 여러 가지가 있을 것 같다. 첫째, 최근 Chrome, Firefox, Safari, Edge 간의 차이는 아주 작고, 갈수록 그 차이가 줄어들고 있다. 그러므로 개발 단계에서는 Chrome에서 확인하는 것만으로 충분할 수도 있다.

둘째, 모든 브라우저에서 모든 테스트를 실행하는 것은 시간이 걸리며, 위에서 말했듯이 개발자들은 테스트를 위해 기다리고 싶어 하지 않는다.

셋째, 많은 회사들에는 모든 브라우저에서 E2E 테스트를 진행하는 QA 개발자들이 있으며, 아주 드물게 발생하는 버그들이 가끔 QA 개발자들에 의해 감지되는 정도는 괜찮다고 여긴다. 그럼 크로스 브라우저 테스트를 꼼꼼하게 하지 않는 회사들은? 그런 회사들은 동일한 종류의 버그가 아주 가끔 사용자들에게서 발생하는 것 정도는 괜찮다고 여긴다.

넷째, 프론트엔드 개발자들이 신경을 쓰지 않는 것은 아니며, Cypress가 더 많은 브라우저를 지원해주길 바라고 있다. 다만, 위에서 설명한 이유들로 인해 우선순위가 아주 높지 않을 뿐이다.

브라우저 내부에서 동작한다!

자바스크립트만 지원되는 이유 중에는 프론트엔드 개발자들이 자바스크립트에 익숙하다는 이유 외에도 더 기술적인 이유가 있다.

Cypress를 이용해 작성한 테스트는 WebDriver처럼 브라우저 외부에서 실행되는 것이 아니다. 테스트 코드를 실행하는 것은 브라우저이다. 사실, 브라우저는 테스트 코드를 애플리케이션 코드와 함께 실행한다!

테스트를 실행할 때 무슨 일이 벌어지는지를 살펴보자. 첫째, Cypress는 브라우저에서 코드를 실행하기 위한 준비를 한다(기술적으로 설명하면, 코드상에 있는 모든 모듈을 webpack을 이용해 하나의 JS 파일로 번들링 한다). 준비가 완료되면 Chrome을 실행하고 빈 페이지에 테스트 코드를 삽입한다. 다음으로 이 테스트 코드를 브라우저에서 실행한다.

이 테스트 코드는 가장 먼저 iframe 내부에서 애플리케이션의 페이지로 접속한다(cy.navigate('http://localhost:8080') 를 사용). Cypress는 iframe이 해당 페이지로 접속하도록 만들며, 테스트 코드는 브라우저에서 실행되고 있기 때문에 (약간의 마법을 통해) 이 iframe 내부에 있는 DOM에 접근할 수 있으며, "click", "type"과 같은 모든 명령도 일반적인 DOM API를 통해 브라우저의 내부에서 발생한다.

비교를 위해 Selenium WebDriver의 아키텍처를 통해 Selenium 테스트가 어떻게 실행되는지를 살펴보도록 하겠다. 아래의 다이어그램을 살펴보자.

screenshot-2018-10-19-21 33 55

Selenium WebDriver는 아래와 같이 세 개의 프로세스를 갖는다.

이들 프로세스 간의 모든 통신을 생각하면 Selenium을 실행하는 데에 긴 시간이 걸린다는 것을 알 수 있을 것이다. 이는 바로 다음 내용과 이어진다.

빠르다!

pasted-image-0-2

브라우저 내부에서 실행된다는 점은 Cypress 테스트 속도의 기반이 된다. Crypess는 오직 하나의 프로세스, 즉 브라우저만 사용한다. Cypress에서는 테스트 코드가 애플리케이션 코드와 함께 실행된다.

그러므로 (버튼 클릭과 같은) 자동화를 위한 명령은 WebDriver가 외부 프로세스와 통신하듯이 브라우저로 전달되는 것이 아니다. 대신 Cypress는 버튼을 클릭하라는 명령을 내리기 위해 DOM 이벤트를 사용한다. 훨씬 훨씬 빠르다.

또한 WebDriver의 외부 프로세스를 통한 자동화가 비동기 통신을 이용하는 반면, Cypress의 자동화 명령은 대부분 동기적이고 메모리 내부(in-memory)에서 발생한다. 이러한 특징이 테스트를 아주 빠르게 만든다.

나는 프론트엔드 개발자들로 하여금 Cypress로 빠져들게 하는 가장 큰 이유가 바로 이 속도라고 믿는다. WebDriver는 체감적으로 이들이 원하는 속도를 보장해주지 않는다. 프론트엔드 개발자들은 테스트가 항상 실행되길 원하며, 사치스러운 기능들을 위해 10분가량이나 기다리는 것을 참을 수가 없다. 이들은 필요한 만큼의 기능만을 원하며, 실행하는 데에 1~2분이 넘어가지 않길 바란다.

Cypress의 문서에서 설명하는 것처럼 실제로도 속도의 차이가 많이 날까? 아주 많은 경험은 아니지만, 내 경험에 비추어 본다면 사실이다. 하지만 그렇다고 이 속도의 차이가 WebDriver의 다른 장점들을 모두 대신할 만큼 가치 있는지는 확신할 수 없다. 몇 가지 예제 테스트를 작성해 보고 스스로 판단하길 바란다!

서버 목킹이 내장되어 있다!

unnamed-2

하지만 Cypress가 더 빠르다고 느껴지는 이유는 하나가 더 있다. Cypress가 프론트엔드 개발자를 위한 도구라고 했던 것을 떠올려보자. 또한 프론트엔드 개발자들이 가끔 실제 백엔드를 사용하기보다는 XML HttpRequests를 목킹해서 사용한다는 사실도 생각해보자. 이렇게 하면 Cypress를 사용하든 Selenium WebDriver를 사용하든 관계없이 몇 초는 더 빠른 테스트를 작성할 수 있다.

Cypress는 이 점을 알고, 서버 응답을 목킹할 수 있는 기능을 내장된(built-in) 형태로 제공한다. 이 기능은 Cypress의 주 사용자인 프론트엔드 개발자에게 아주 중요하다. Selenium WebDriver는 이러한 기능을 제공하지 않기 때문에, 서버의 응답을 목킹하려면 정확한 응답을 반환하는 가상의 서버를 구동해야만 한다. 비록 가능하긴 하지만 더 느리고 불편할 수밖에 없다. 프로세스 외부에서 목킹하는 것보다는 메모리상에서 목킹하는 것이 항상 더 뛰어난 성능을 보여준다.

이것이 단순히 실행 속도의 문제만은 아니다. 서버 목킹이 내장되어 있기 때문에 테스트를 빠르게 작성할 수 있다는 점은 아무리 강조해도 지나치지 않다. 각각의 테스트가 명시적으로 목킹된 응답을 기술할 수 있다는 사실은 동일한 응답을 별도의 서버 프로그램으로 목킹하는 것보다 코드를 작성하고 이해하기 훨씬 쉽게 해 준다.

실행 모델이 특이(bizarre)하다!

unnamed-3

아, 인정한다. 좀 어울리지 않는 제목이긴 하다. 하지만 사실이다! Cypress의 실행 모델은 아주 특이하다. 증명을 위해 아래의 예제 코드를 살펴보기 바란다.

describe(`TodoMVC using Cypress`, function () {
  it('can add todos and then delete them', () => {
    // visit the todomvc application
    cy.visit('http://todomvc.com/examples/react/#/')

    // type a new todo into the "new todo" input field.
    cy.get('.new-todo').type('Cook dinner{enter}')

    // ensure that a new todo was created correctly
    cy.get('.view > label').should('have.text', 'Cook dinner')

    // Add another todo and asser that it was added to.
    cy.get('.new-todo').type('Clean house{enter}').then(() => {debugger})

    cy.get('.view > label').eq(1).should('have.text', 'Clean house')

    // now check the toggle, to make it "done".
    cy.get('.toggle').eq(1).click()

    cy.contains('1 item left').should('exist')
  })
})

"하나도 특이할 게 없는데!"라는 반응일텐데, 아마 이 코드가 당신이 생각한 대로 동작한다면 맞는 말일 것이다. 하지만 그렇지 않다. cy.click이나 cy.navigate 혹은 cy.type 을 호출할 때 실제로 당신은 이 명령을 실행하고 있는 것이 아니다. 사실은 Cypress가 테스트를 실제로 실행할 때 완료되길 원하는 명령들을 기록(record) 하고 있는 것이다. 테스트는 it 함수 내부에서 실행되지 않는다. 테스트는 it 함수가 끝난 이후에 당신이 기록한 명령에 따라 실행된다. cy.navigate/click/type 함수는 실행되는 것이 아니라, 나중에 재생(play) 되기 위해 기록되는 것이다.

이 결정이 개발자에게 실제로 어떤 영향을 미칠까? 페이지 내의 값에 따라 코드를 분기하는 것이 불가능해진다. 예를 들어, 다음과 같이 페이지의 특정 텍스트 값에 따라 코드를 실행하는 경우를 살펴보자.

const countItemsLeft = cy.contains('item left')

if (countItemsLeft.innerText === '1 items left') {
 // do something...
}

이 코드는 동작하지 않는다. 테스트를 실행할 때 실제로 자동화 명령을 실행하는 것이 아니기 때문에, 반환값의 텍스트를 확인해 봤자 소용이 없다.

물론 Cypress가 이러한 테스트를 지원하기는 하지만, 복잡한 방식을 써야 한다.

  cy.contains('item left')
      // do the following code on the element that contains "item left".
    .then(countItemsLeft => {

    // if the text in the element is "1 items left", log it.
    if (countItemsLeft.innerText === '1 items left') {
      console.log('lalala!')
    }
})

.then을 사용하면 "실제로" Cypress가 자동화 명령을 실행한 이후에 실행되는 코드를 작성할 수 있다. 이 .then을 이용하면 "녹화 후 실행" 모델에서 테스트를 분기해서 실행하는 문제를 해결할 수 있다. 하지만, 만약 Cypress가 WebDriver처럼 일반적인 실행 모델을 선택했다면 굳이 이런 방식이 필요 없었을 것이다.

그럼, Cypress는 왜 이런 특이한 실행 모델을 선택했을까? 대답은 다음 단락으로 이어진다.

더 안정적이다!

Cypress에 따르면 navigate, click, type과 같은 명령의 실행을 Cypress에게 위임함으로써 Cypress가 이들 명령을 어떻게 실행할지를 좀 더 상세하게 조작할 수 있다고 한다. 예를 들면 버튼을 클릭하라는 요청을 했을 때 그 버튼이 화면에 나타난 다음 애니메이션이 멈출 때까지 기다린 다음 클릭을 할 수 있다.

Cypress에 따르면, 이를 통해 더 안정적인 테스트를 만들 수 있다고 한다.

사실 난 잘 모르겠다.

엘리먼트가 나타날 때까지 기다리는 것은 WebDriver의 암묵적 대기(implicit wait)와 아주 비슷해 보이는데, 나는 왜 Cypress가 이 방식이 더 낫다고 믿는지 잘 모르겠다.

나는 분명 Cypress가 더 안정적이라고는 생각하지만, 그 이유가 실행 과정을 조작하는 것과는 관계가 없다고 생각한다. 이유는 테스트가 프론트엔드에서만 실행되고, 모든 서버 응답을 목킹하며, 테스트가 아주 빠르게 실행되기 때문이다. 브라우저가 테스트를 빠르게 실행할수록 더 안정적이게 된다.

즉, 나는 Cypress가 더 안정적이긴 하지만 그것이 실행을 조작하기 때문은 아니라고 생각한다. 슬프게도, 복잡한 실행 방식은 이 멋진 도구의 빛을 바래게 한다.

하지만, 내가 틀렸을지도 모른다 🙂

더 상호적(interactive)이다!

Cypress의 가장 멋진 점 중의 하나이며, 수많은 개발자들이 Cypress로 빠져드는 이유 중의 하나는 바로 Cypress가 일종의 IDE와 같다는 점이다. Cypress는 실행 환경(브라우저)를 환벽하게 통제하기 때문에 개발자들에게 좀 더 상호적인 경험을 줄 수 있다.

pasted-image-0-3

위의 스크린샷에서 볼 수 있듯이, 테스트가 구동될 때 명령들이 실행되면서 좌측 패널에 표시된다. 뿐만 아니라 테스트가 종료되고 나면 왼쪽 패널에 있는 각각의 명령들을 클릭해서 해당 명령이 실행된 시점의 페이지를 스크린샷 형태로 볼 수 있다. 이것을 시간 여행이라고 부른다.

이 기능은 테스트 디버깅을 위한 경이로운 기능이다! Cypress의 스크린샷을 통해 테스트가 어떻게 진행되는지를 보는 것은, 테스트를 디버깅할 하거나 테스트가 왜 실패했는지를 이해할 때 아주 유용하다. 이 모든 경험이 테스트를 작성하고 이해하고 디버깅하며 결과를 분석하는 것을 도와준다.

문서가 아주 훌륭하다!

아래는 Cypress의 문서이다.

screenshot-2018-10-15-22 37 16

다음의 Selenium 문서와 비교해보자.

screenshot-2018-10-15-22 37 55

Cypress의 문서를 다시 살펴보면 훨씬 더 깔끔해 보인다. 하지만 보이는 것이 전부는 아니다. Cypress 문서는 훨씬 더 깊고 넓다. Cypress의 문서는 당신이 알고자 하는 대부분의 내용을 담고 있어서 웹상의 다른 곳을 찾아볼 필요가 없다. 개발자들이 문서를 위해 얼마나 많은 시간을 들였는지를 볼 수 있을 것이다. 좋은 문서는 아주 훌륭한 경험을 제공한다. 개발자들에게 박수를 보내고 싶다.

Selenium의 문서는 꽤 훌륭하다. 특히 Selenium이 다양한 언어와 다양한 브라우저를 지원하기 때문에 다루어야 하는 내용이 훨씬 더 많다는 점을 고려한다면 말이다. 불행하게도 Selenium의 문서는 수 년 동안의 무게를 짊어지고 있기에, 깊이나 넓이 모두 Cypress의 수준에는 미치지 못한다.

결론

지금까지 살펴보았듯이, Cypress와 Selenium은 표면적으로는 유사해 보이지만 실제로 많은 차이점을 갖고 있다. 내 생각에 이러한 차이점을 만들어내는 가장 근본적인 원인은 바로 두 도구의 요구 사항이 다르다는 점이다. Selenium은 다양한 언어를 지원하는 크로스 브라우저 도구이며 테스트 이외에도 많은 목적을 갖고 있다. 반면 Cypress는 프론트엔드 개발자들이 빠르고 안정적인 테스트를 실행하도록 해 준다는 단 하나의 목적만을 위해 만들어졌다.

이것이 바로 Cypress가 제한적으로 보이는 이유이다. Cypress는 한 가지 언어(자바스크립트), 한 가지 프레임워크(Mocha), 한 가지 브라우저(Chrome)만을 지원한다. Selenium WebDriver는 다양한 사용자들을 위한 보편적인 목적의 도구이기 때문에 이러한 제한을 가질 수가 없다. 하지만 이러한 제한은 프론트엔드 개발자에게 그다지 문제가 되지 않으며, 오히려 Cypress가 그들이 필요로 하는 속도와 안정성을 것을 제공할 수 있도록 해 준다.

Cypress는 "적을 수록 좋다"라는 말의 고전적인 예시이며, Selenium WebDriver는 스위스 군용 칼(Swiss Army knife)의 고전적인 예시이다.


출처 : https://applitools.com/blog/cypress-vs-selenium-webdriver-better-or-just-different

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
Posted by 프리스케이터