브라우저 확장/단위 테스트


개요

브라우저 확장은 일반 브라우저보다 더 다양한 통합 환경을 필요로 하기 때문에, 테스트 환경을 구축하는 난이도가 다른 환경에 비해 높다. 이 글에서는 브라우저 확장을 위한 테스트 환경 구축을 위한 요구사항을 정리하고, 간단한 구현을 통해 해당 요구사항을 구현하는 것이 가능함을 증명한다.

목표

test runner 내에서 브라우저 확장의 API를 이용할 수 있는 테스트 환경을 구축한다. 단, sinon-chrome같은 stub 환경이 아닌 실제 Chromium 브라우저 환경을 이용할 수 있어야 한다.

간단히 말해 다음과 같은 테스트를 실행할 수 있어야 한다:

/// <reference types="jasmine" />

describe('test', () => {
  it('works well', async () => {
    await new Promise<void>((resolve) => {
      chrome.storage.local.set({ hello: 'world' }, resolve);
    });

    const result = await new Promise((resolve) => {
      chrome.storage.local.get(['hello'], (res) => {
        resolve(res);
      });
    });

    expect(result).toEqual({ hello: 'world' });
  });
});

구현의 편의를 위해, 다음 사항은 고려하지 않는다.

  • 처음에는 background script만 대상으로 한다: content script나 popup script에서 사용하는 통합 환경을 모두 background script에서도 접근 가능하기 때문에 background script에서 돌아가는 테스트 환경을 구축하는 것을 목표로 한다.
  • Mocking은… out of scope: 테스트 환경을 구축하는 것이 목표이기 때문에, 테스트 환경 내에서 사용하는 mocking은 외부 라이브러리를 사용하는 것을 가정한다.

배경

기존 테스트 툴들은 테스트 코드의 실행환경을 미리 가정하고, 개발자가 테스트 코드만 작성할 수 있도록 도와주는 것에 초점이 맞춰져 있다. 이는 널리 사용되는 실행환경을 사용하는 코드의 테스트 환경에는 적합한 방식이지만, 브라우저 확장과 같이 특수한 실행환경을 필요로 하는 코드의 테스트 환경을 구축하기에는 적합하지 않다. 대부분의 테스트 툴들은 다음과 같은 실행환경들을 지원한다:

  • Node.js에서 바로 테스트 코드를 수행한다.
    • Node.js 실행환경을 테스트
    • jsdom / sinon 등을 이용해 임의의 환경을 모사한 실행환경에서 테스트
  • 서버를 띄우고 브라우저에서 서버에 접속해서 테스트 코드가 들어있는 HTML을 로드함으로써 테스트를 수행한다.
    • 유저가 직접 브라우저를 띄우거나, Selenium, Webdriver 등을 이용해 띄우거나

초기 디자인

Chrome에서는 브라우저 확장 디버깅을 위해 dev console을 제공하고, 여기다 javascript 코드를 넣으면 배경 코드 실행환경에서 실행한 결과를 볼 수 있다. 그렇다면 여기에 테스트 코드를 넣으면 테스트도 수행할 수 있겠다는 것이 기본적이 아이디어였다.

테스트용 브라우저 확장을 포함해서 브라우저를 실행하는 것은 web-ext 등의 툴을 통해 이미 구현되어 있었고, 이를 통해 브라우저에서 명령줄 인자를 통해 임의의 브라우저 확장을 로드하는 것이 가능하다는 것을 알 수 있었다.

API를 통한 임의의 코드 실행

dev console이 내부적으로 CDP를 사용하기 때문에, 이걸 이용해서 임의의 코드를 실행할 방법을 조사해 봤다. 결론적으로 Runtime#runScript를 사용하기로 했다.

실행 결과 받아오기

마찬가지로 Runtime#evaluate를 사용하면 된다.

테스트 환경 조성

보통 테스트 작성에 사용하는 describe, it등의 환경을 내가 다시 정의하는건 번거로운 작업이 될 테니, 이것들은 그냥 이미 존재하는 Jasmine 라이브러리를 사용하기로 했다.

테스트 코드 변환

만약 테스트 코드가 typescript라던가, runScript / evaluate 함수에서 지원하지 않는 ES module을 사용한다던가, 외부 dependency가 있으면 실행이 안될 것이다. 따라서 이런 문제를 모두 처리해서 단 하나의 javascript 코드로 변환하는 작업이 필요하고, 이건 Rollup.js 등의 라이브러리가 아주 잘 처리하는 작업이다.

결론 - 여기까지 해 놓고 보니

결국 내가 하고 싶었던 작업은 임의의 실행환경을 정의할 수 있도록 하는 도구를 만들어서, 거기다 테스트 코드를 삽입함으로써 어디서든 테스트를 실행할 수 있게 만드는 것으로 정리할 수 있었다.

다음 시간에 계속

사실 이미 도구는 다 만들었다. 블로그는 작업과정 정리하려고 쓰는 것뿐…