확신을 위한 도구, API 테스팅

펄핏 Perfitt·2022년 3월 3일
3
post-thumbnail

얼마 전 Perfitt 앱의 이벤트 시스템을 개편해서 Production 릴리즈를 했습니다. 거기에 버그 하나가 있었고 서버를 Production으로 보내자마자 엄청나게 많은 비정상 앱 종료 알림들이 오더군요 ㅎㅎ..

식은 땀이 나고 호흡이 거칠어지고 사고가 둔해지고 시야가 흐릿해지는 아찔한 경험이었습니다.
정말 다신 하고 싶지 않은 경험이었구요.

망치로 한 대 맞은 느낌으로 반성 및 회고를 하면서 '어떻게 하면 프로덕션에서 버그를 안 낼까' 이런 아주 기초적이면서도 필수적인 고민들을 많이 했습니다.

그래서 도달한 결론은 API 테스팅입니다. 개발자들은 자신들의 코드를 여러가지 경우에 따라 값을 테스트하는 테스트 코드를 작성하고 추후에 나타날 수 있는 버그들을 미리 잡기 위해서 많은 테스트를 합니다.

이 글에서는 아찔한 버그 생성 경험을 통해 깨달은 삶의 레슨과 현재 펄핏에서 제가 맡은 백엔드 부분에서의 테스트 자동화 구현 방법을 다뤄보려 합니다.

Why?

사실 왜 해야 하냐고 하면 제 대답은 간단합니다.

"모든 것에 확실하기 위해서"

저는 빠르게 개발하는 것에 초점을 맞춰왔던 것 같습니다.
빠르게 대응하고, 빠르게 기능을 추가하고, 빠르게 개선하고, 이런 점들이 끊임없이 변화하고 성장하는 스타트업에서 더 중요한 건 개발자의 실력이 아닌가? 라고 생각합니다.

이러한 생각에는 변함이 없습니다만 요즘 들어 제 동료 개발자분들과 협업하면서 크게 배우고 느끼는 것이 있다면 이 것입니다.

확실하게 개발해야 하는 이유

제가 맡은 일, 제가 개발한 내용들에는 저 자신이 확실해야 합니다. 그래야 협업의 과정에서 개발적인 나의 의도와 내용을 확실히 전달하고 협업할 수 있습니다. 프론트 개발자와 무조건 커뮤니케이션을 잘 해야하는 백엔드 개발자의 경우 이건 필수라고 생각합니다.

이 관점에서 더 나아가 신뢰성과도 연결된다고 생각합니다. 내가 만든 결과가 확실하지 않은데 어떻게 협업하는 동료분들이 제 파트에 신뢰를 하겠습니까?

확실하게 개발한다는 것은 나 자신이 내가 만든 코드에 확신이 있고 동료분들이 제가 만든 코드에 믿음이 생기도록 개발해 나간다는 것 같습니다.

그래서 제 코드에 확신을 가지고 동료분들에게도 제 코드에 대한 확신이 생기게끔 API 테스팅이란 하나의 도구를 사용해 보려 합니다.


개발 방법

사용 라이브러리

  • Mocha
    현재 펄핏의 모바일 서비스 백엔드는 Express로 구현되어 있고 Express 앱을 테스팅하기 위한 프레임워크로 Mocha를 사용하였습니다.
    Javascript 테스팅 툴 같은 경우 유명한 Jest나 다른 라이브러리들이 많이 있지만 Mocha는 상대적으로 세팅이 쉽고 빠르며, 확장성이 좋고 간단한 비동기 처리 등의 장점이 있기 때문에 선택하였습니다.

  • Chai
    Chai JS는 Assertion 라이브러리로, 테스팅할 때의 문법을 제공합니다. Assert, Should, Expect 등 세 종류의 문법을 제공하는데, 사용자의 입맛에 맞춰 사용하면 되고 문법이 언어적으로 구현되어 있어 이해하기 쉽고 사용하기 편합니다.

  • Chai-HTTP
    Chai-HTTP는 Chai의 애드온 같은 느낌으로, Chai Test 시 HTTP Request를 보내는 형식으로 테스트를 가능하게 합니다. 구현하는 API Testing 에서는 각 Function 단위를 테스트하는 게 아닌 각 API Route Call에 대한 Test를 하려고 할 때 유용할 것으로 생각되어 선택하였습니다.

  • Mongo-unit
    펄핏은 데이터베이스로 MongoDB를 사용하고 있는데 MongoDB에 알맞는 테스팅 방법을 찾아보다가 Mongo-unit을 발견하였습니다.
    API 테스트를 할 때 실제 데이터나 개발 데이터는 가급적 조작하지 않는게 좋습니다.
    데이터를 복제하는 테스트용 DB를 아예 새로 만들고 테스트 시작 시에 트랜젝션 설정 후 테스트를 완료한 뒤 다시 롤백하는 방법을 사용할 수도 있지만 안전한 테스트를 위해 다른 방법을 찾아본 결과, 테스트 시에만 인 메모리로 형성되는 DB인 Mongo-unit 을 사용하기로 하였습니다.
    새로 DB를 만들 필요도 없고, Mocha와 같이 각 테스트 별로 데이터를 셋팅 하는 경우 구현이 쉬운 장점이 있습니다.

설치

필요한 devDependencies 정의

"devDependencies": {
    "mocha": "^8.4.0", // Mocha 프레임워크
    "chai": "^4.3.4", // assertion 라이브러리
    "chai-http": "^4.3.0", // chai 에서 http 콜 가능하게함
		"sinon": "^11.1.1", // mocking library
    "mongo-unit": "^2.0.1" // 인메모리 몽고디비
  }

필요한 라이브러리를 설치합니다.
아래 코드로 한번에 설치할 수 있습니다.

npm install mocha chai chai-http sinon mongo-unit --save-dev

ExpressApp 내 API Testing 파일 구조

보라색 글자는 Mocha 테스트를 위해 새로 생성하는 것을 의미합니다.
파랑색 글자는 Mocha 테스트를 위해 존재하는 파일에 추가 사항이 있는 것을 의미합니다.

Project Folder

  • .env.test - 테스트를 위한 env 파일
  • .package.json (밑의 package.json 섹션에 정의)
  • /src
    • /routers
      • /route1
        • action1.js
      • /route2
        • action2.js
  • /test
    • app.test.js
    • init.spec.js
    • /route1
      • action1.spec.js
    • /route2
      • action2.spec.js

package.json: 테스트 실행을 위한 명령어 정의

터미널에 $ npm test 를 입력해 Mocha 테스트를 실행합니다.

"scripts": {
    "test": "mocha \"./test/**/*.spec.js\" --delay"
}

\"./test/**/*.spec.js\"

  • test 폴더 내의 모든 서브 디렉토리에서 .spec.js 로 끝나는 파일들을 실행함

--delay

  • 사용자가 선택 지점에서 테스트를 시작할 수 있게 하는 run() 함수를 사용 가능하게 함
  • 테스트용 인 메모리 Mongo DB인 Mongo Unit을 테스트 시작 전에 Initialize 해주기 위해 사용함

scripts 를 설정하고 나중에 $ npm run test 를 입력해주면 자동으로 모든 테스트를 실행합니다.

init.spec.js: 테스트 시작 / 끝 설정

const mongoUnit = require('mongo-unit');

mongoUnit.start().then(() => {
    run() // this line start mocha tests
});
 
after(() => {
    mongoUnit.stop();
    console.log('테스트 완료');
    process.exit(0);
});

Mongo-unit을 시작한 후 테스트를 실행시키고, 테스트 완료 후 프로그램을 종료해줍니다.
필요시 mongoUnit.load를 통해 DB의 초기 상태를 설정해주는 것 또한 가능합니다.

Mocha 실행시 --delay 플래그와 함께 실행하여 생성된 run() 콜백 함수를 통해 테스트 시작 전에 비동기 함수를 처리할 수 있게 합니다.

app.test.js: 테스트용 App 설정

var path = require('path');
var dotenv = require('dotenv');

// 테스트용 env로 설정
dotenv.config({path: path.join(__dirname, '/../.env.test')});

const app = require('../src/app.js');

// 테스트용 App 모듈 Export
module.exports = app;

Chai Request로 보내주기 위한 테스트용 Express 서버 앱을 Export 해줍니다.

[route1].js: 테스트용 App 설정

const mongoUnit = require('mongo-unit');
let chai = require('chai');
let chaiHttp = require('chai-http');
chai.use(chaiHttp);

// expect, assert, should 중 개인의 취향대로 assertion 진행
var expect = chai.expect;
var assert = chai.assert;
var should = chai.should();

// 테스트용 서버 앱을 불러옴
const app = require('../app.test');

// 테스트 DB의 초기 상태 설정용 데이터 정의
const testData = {
  "Collection1": [
    {
      "_id": "112233445566778899",
      "data": "...",
			...
    },
		...
  ]
};

// Route1 의 GET API
describe('어떤 API 테스트 인지 설명', () => {
  // 테스트를 진행하기전 Mongo-unit에 테스트 데이터를 가진 DB를 설정
  before(() => mongoUnit.load(testData))

  // 테스트
  it('it should GET string result', (done) => {
    chai.request(app)
    .get('/route1/route2') // 라우트 설정
    .query({page: 0, limit: 10}) // 파라미터 정의
    .end((err, res) => {
      expect(res.status).to.equal(200);
      const { result } = res.body; // res.body에서 받은 결과 "result"로
      result.should.be.a('string'); // result가 string인지 확인
      done(); // 테스트 끝 정의
    });
  });
});

테스트 파일은 이런 식으로 작성하면 됩니다.


결과

초반에 설정해준 scripts로 $ npm run test 를 입력하여 테스트를 실행시킨 결과입니다

작성해둔 테스트 코드를 모두 실행시켜서 그에 대한 결과를 보여줍니다.

그 외에도 에러 코드에 대한 테스트 및 걸리는 시간 등 상세한 테스트 케이스를 작성해 실행시킬 수 있습니다.

잘 작성된 테스트 코드로 테스트를 실행하게 되면 어느 부분에서 에러가 났는지, 어느 부분과 영향이 있는지, 어떤 이유로 에러가 났는지까지 바로 파악이 가능합니다.


마치며

사실 API Testing 같은 경우 시도할 수 있는 다양한 방법이 있습니다. 굳이 위의 방법으로 자동화를 하지 않아도 하나하나 Postman으로 테스트하는 방법으로 해도 괜찮습니다.

테스트 자동화를 위해 수많은 테스트 코드를 작성해야 한다는게 할 일이 많아지는 것이기도 하고 시간이 더 걸리게 되는 일이기도 합니다. 그래서 작은 프로젝트를 할 때에나 개발 사항이 그렇게 많지 않은 경우 쉽게 테스트하고 넘길 때도 분명 많습니다. 그게 빠르고 효과적일 때도 있구요.

하지만 테스트 코드를 작성하는게 시간이 조금 더 걸리고 귀찮더라도 잘 작성해서 예상할 수 있는 버그를 확실히 잡는게 낫다고 생각합니다.

유저가 앱을 사용하다가 갑작스럽게 앱이 종료되는 경험만큼 기분 나쁜 게 없잖아요?

제가 저를 위해서, 팀원들을 위해서 "확실" 하게 해야 하는 것 중 하나는 신뢰할 수 있는 개발자가 되는 것이었습니다. 그리고 제가 믿음직한 동료가 된다는 "확신" 을 얻기 위해 API 테스팅을 도구로써 사용해보는 방법을 담아보았습니다.

질문 하나를 던지며 글을 마치려 합니다.

여러분들은 무엇을 "확실" 하게 하고 어떤 "확신" 을 가지고 싶나요?

감사합니다.


Reference

profile
Beyond The Size Limit

0개의 댓글