2020년 9월 8일

테스트 자동화와 Mocha

테스트 자동화는 앞으로 풀어야 할 과제에서뿐만 아니라 현업에서도 광범위하게 쓰입니다.

테스트는 왜 해야 하는가?

함수를 하나 만들고 있다고 해 봅시다. 대부분 매개변수-결과 관계를 중심으로 어떻게 코드를 작성할지 구상하실 겁니다.

개발 중엔 콘솔 창 등을 이용해 실제 실행 결과가 기대했던 결과와 같은지 계속 비교하면서 원하는 기능이 잘 구현되고 있는지 확인할 겁니다.

실제 실행 결과가 기대했던 결과와 다를 땐, 코드를 수정하고 다시 실행해 그 결과를 기대했던 결과와 다시 비교해 볼 겁니다. 원하는 기능을 완성할 때까지 이 과정을 계속 반복하겠죠.

그런데 이렇게 수동으로 코드를 '재실행’하는 건 상당히 불완전합니다.

코드를 수동으로 ‘재실행’ 하면서 테스트를 하면 무언가를 놓치기 쉽습니다.

구체적인 예를 들어봅시다. 현재 함수 f를 구현하고 있다고 가정해보겠습니다. 코드를 작성하고 f(1)이 제대로 동작하는지 확인합니다. 제대로 동작하네요. 그런데 f(2)를 테스트해 보니 제대로 동작하지 않습니다. 코드를 수정한 후 다시 f(2)를 확인해 봅니다. 제대로 동작하네요. 여기서 끝일까요? 아닙니다. f(1)이 제대로 동작하는지 확인하지 않았으니까요. 이렇게 테스트를 수동으로 하면 에러가 발생할 여지를 남깁니다.

이런 일은 아주 흔히 발생합니다. 개발자는 무언가를 만들 때 머릿속에 수많은 유스 케이스를 생각하며 코드를 작성하는데, 코드를 변경해야 할 때마다 모든 유스 케이스를 상기하면서 코드를 수정하는 것은 거의 불가능합니다. 하나를 고치면 또 다른 문제가 튀어나오는 이유가 바로 이 때문입니다.

테스팅 자동화는 테스트 코드가 실제 동작에 관여하는 코드와 별개로 작성되었을 때 가능합니다. 테스트 코드를 이용하면 함수를 다양한 조건에서 실행해 볼 수 있는데, 이때 실행 결과와 기대 결과를 비교할 수 있습니다.

Behavior Driven Development

Behavior Driven Development(BDD)라 불리는 방법론에 대해 알아봅시다.

BDD는 테스트(test), 문서(documentation), 예시(example)를 한데 모아놓은 개념입니다.

실제 개발 사례를 이용해 BDD가 무엇인지 차근차근 설명해 보도록 하겠습니다.

거듭제곱 함수와 명세서

xn번 곱해주는 함수, pow(x, n)를 구현하고 있다고 가정해 봅시다.(단, n은 자연수이고, 조건 n≥0을 만족해야 합니다.)

사실 자바스크립트엔 거듭제곱 연산자 **가 있습니다. 그럼에도 불구하고 함수를 직접 구현하는 이유는, 구현 과정에 초점을 두면서 BDD를 직접 적용해 보기 위해서입니다. 기능이 간단한 함수를 구현하면서 BDD를 직접 적용해 보면 큰 문제에 BDD를 적용하는 건 쉬울 테니까요.

본격적으로 코드를 작성하기 전에 먼저 해야 할 것이 있습니다. 코드가 무슨 일을 하는지 상상한 후 이를 자연어로 표현해야 합니다.

이때, 만들어진 산출물을 BDD에선 명세서(specification) 또는 짧게 줄여 스펙(spec) 이라고 부릅니다. 명세서엔 아래와 같이 유스 케이스에 대한 자세한 설명과 테스트가 담겨있습니다.

describe("pow", function() {

  it("주어진 숫자의 n 제곱", function() {
    assert.equal(pow(2, 3), 8);
  });

});

스펙은 세 가지 주요 구성 요소로 이루어집니다.

describe("title", function() { ... })

구현하고자 하는 기능에 대한 설명이 들어갑니다. 우리 예시에선 함수 pow가 어떤 동작을 하는지에 대한 설명이 들어갈 겁니다. it 블록을 한데 모아주는 역할도 합니다.

it("유스 케이스 설명", function() { ... })

it의 첫 번째 인수엔 특정 유스 케이스에 대한 설명이 들어갑니다. 이 설명은 누구나 읽을 수 있고 이해할 수 있는 자연어로 적어줍니다. 두 번째 인수엔 유스 케이스 테스트 함수가 들어갑니다.

assert.equal(value1, value2)

기능을 제대로 구현했다면 it 블록 내의 코드 assert.equal(value1, value2)이 에러 없이 실행됩니다.

함수 assert.*pow가 예상한 대로 동작하는지 확인해줍니다. 위 예시에선 assert.equal이 사용되었는데, 이 함수는 인수끼리 동등 비교했을 때 다르다고 판단되면 에러를 반환합니다. 예시에선 pow(2, 3)의 결괏값과 8을 비교하겠죠. 비교나 확인에 쓰이는 다른 함수들은 아래에서 다시 소개해 드리겠습니다.

명세서는 실행 가능합니다. 명세서를 실행하면 it 블록 안의 테스트가 실행됩니다. 자세한 내용은 아래에서 다시 설명하겠습니다.

개발 순서

실제 개발에 착수하면 아래와 같은 순서로 개발이 진행됩니다.

  1. 명세서 초안을 작성합니다. 초안엔 기본적인 테스트도 들어갑니다.