개발공부일지/NodeJS

TDD(Test-Driven-Development) 기본 이해하기

Hynn1429 2023. 1. 19. 19:47
반응형

안녕하세요.

Hynn 입니다.

 

이번 포스팅에서는 TDD, 테스트 코드 작성에 대한 기초 이해를 위한 포스팅을 작성해보겠습니다.

이 테스트코드는, 실제 서버에서 의존성을 줄이고, 각 파일을 실행함으로서 테스트를 진행했다면, 이러한 코드를 이용해서 파일 실행이 아니라, 단위 별로 테스트를 수행하고, 어느 단계에서 오류가 발생하는지, 정상적으로 의도한 바와 같이 동작하는 지를 파악하기 위해서 입니다.

 

이전 포스팅에 예제로 올려둔, Login 기능과 같이 묶어서 보시면 더욱 도움이 될 겁니다.

 

시작해보겠습니다.

==========

==========

 

 

테스트는, 주로 단위테스트, 통합테스트, 부하 테스트 3개의 테스트가 있습니다.

하지만 여기서 다룰 것은 단위테스트만을 기초적으로 다룰 예정입니다.

 

여기서 단위테스트와 통합테스트는 아래와 같은 뜻을 가지고 있습니다.

 

  • 단위 테스트 : 코드 단위별로 테스트를 진행
  • 통합 테스트 : File 단위별로 테스트를 진행

물론, 코드를 작성함에 있어, 테스트코드를 작성함으로서, 같은 기능을 구현하더라도, 코드의 작성량이 2배가 넘을 수도 있습니다.

혹은 테스트코드가 실제 사용될 코드보다 더 많은 량을 작성하는 경우도 발생할 수 있습니다.

하지만 테스트코드를 단위별로 확인하거나, 통합으로 확인함으로서 , 코드의 구조나 안정성을 확인하는데 더욱 도움이 될 수 밖에 없습니다.

 

이는 규모가 커질 수록, 개발자의 사이클에서 초기단계에서부터 오류를 식별,원인을 파악할 수있기 때문에, 전체흐름도에서 본다면 코드의 양적인 면에서 늘더라도, 실제 효율에서는 더 나은 선택일 것이라 생각됩니다.

 

 

단위 테스트는 아래의 조건이 모두 충족되어야 합니다.

  • 단위 테스트는 의존성이 없이 독립적이여야 합니다. 
  • 다른 코드와 격리되어야 합니다. 즉, Ajax, Axios 같은 Module 조차도 의존되지 않도록 다른 것으로 대체되어 테스트가 이루어져야 합니다.

 

이러한 단위테스트를 하면 아래의 이점이 존재합니다.

  • 코드의 양이 많거나, 수행하는 프로그램이 리소스가 크거나, 다른 리소스(ex.DB) 가 필요한 상황에서는, Local 에서는 실제 테스트가 불가능한 상황에서도 단위 테스트를 구현하면, 코드 단위별로 테스트가 가능합니다.
  • 의존성이 발생하는 다른 클래스(코드)에서 문제가 발생하는 것을 사전에 방지할 수 있습니다.

 

 

이러한 테스트환경을 구현하기 위한 Framework 를 이용해야 합니다.

대표적으로 몇가지 소개 드리도록 하겠습니다. 

 

  • FaceBook 으로 잘 알려진 Meta 의 주도하에 개발된 Framework 입니다. Test-Runner, Assertion Library, Mocking Library 를 포함하는 자주 사용되는 대표적 Test Framework 입니다. React 환경에서 주로 사용됩니다. 
  • 다양한 Assertion Library (Should.js) 를 사용할 수 있는 자주 사용되는 TestFramework 입니다. Mocha 는 단위테스트 뿐 아니라 통합테스트에도 유연하게 사용이 가능한 Framework 입니다.

여기서 소개드리고자 하는 것은, Jest 를 이용해 단위 테스트를 간단하게 구축하고, 그를 통해 어떠한 식으로 테스트가 이루어지는지 살펴볼 예정입니다.

 

 

먼저 NPM 에서 Module 을 설치를 하여 환경을 구축해야 합니다.

npm install -D jest nodemocks-http supertest

위의 코드는 각각 아래의 뜻을 지닙니다.

  • -D : Development 를 뜻합니다
  • modemocks-http : 단위 테스트를 위한 파트입니다.
  • Supertest : 통합테스트를 위한 파트입니다.

이제 테스트를 위한 파일을 구성해야 합니다.

파일명은 아래의 형태로 구성해야 합니다.

//실제 사용될 파일
파일명.js

// 테스트 파일
파일명.test.js

위와 같이 구성이 이루어지면 되겠습니다.

 

이제 package.json 파일의 내용을 일부 수정 및 생성해야 합니다.

먼저 환경파일을 생성해야 합니다.

 

module.exports = {
    testEnvironments : "node",
    verbose : true 
}

이 파일의 두가지는 각각 아래의 뜻을 가진 속성입니다.

  • testEnvironments : 테스트 환경을 뜻합니다. 예제에서는 Nodejs 환경이기 때문에 Node 를 지정합니다. 
  • verbose : 테스트 결과가 자세히 출력되도록 설정합니다.

 

이제 JWT 기능을 간단하게 사용하면서 테스트를 구현해보도록 하겠습니다.

 

각각의 환경을 구축하기 위해, 동일한 규칙으로 테스트파일과 정규파일을 생성합니다.

그리고 테스트 파일에 이제 코드를 작성해야 합니다.

테스트를 위한 기본 코드의 구성은 아래와 같습니다.

 

describe( ()=>{
	it( ()=>{
    })
})

각각의 매개변수는 아래와 같이 들어가야 합니다.

먼저 첫번째 "describe" 에는 테스트할 명칭 , Callback) 형태로 들어가 있고, 그 안에 it을 통해 그룹화단위로 나눌 수 있고, 역시 동일하게 "테스트명칭", Callback 형태로 구성합니다.

 

그리고 마지막으로 package.json 에 아래의 영역에 코드를 수정하도록 합시다.

 

  "scripts": {
    "test": "jest"
  },

그리고 나서 "npm run test" 를 수행하면, 테스트가 실행됩니다.

 

 

 

먼저 예제를 작성하기 위해 저는 코드를 2개의 영역으로 나누어 구분하도록 하겠습니다.

 

describe("Test Code", () =>{
    it("decode",()=>{
        console.log("Test Code by Hynn-decode")
    })

    it('encode', ()=>{
        console.log("Test Code by Hynn-encode")
    })
})

즉 "describe" 라는 테스트 코드 내에 it 이라는 두개의 단위 테스트가 구현이 되어 있는 것이고, 

각각은 "decode", "encode" 라는 설명을 부여했습니다.

 

이를 실제로 테스트 하기 위해 jwt.js 파일에 실제 코드를 먼저 작성해보겠습니다.

 

class JWT {
    constructor({ crypto }) {
        this.crypto = crypto
    }

    sign(data, options = {}) {
        const header = this.encode({ tpy: "JWT", alg: "HS256" }) //base64url
        const payload = this.encode({ ...data, ...options }) //base64url
        const signature = this.createSignature([header, payload])

        // return `${header}.${payload}.${signature}`
        return [header, payload, signature].join(".")
    }

    // token:string
    verify(token, salt) {
        const [header, payload, signature] = token.split(".")
        const newSignature = this.createSignature([header, payload], salt)
        if (newSignature !== signature) {
            throw new Error("token error")
        }

        return this.decode(payload)
    }

    encode(obj) {
        return Buffer.from(JSON.stringify(obj)).toString("base64url")
    }

    decode(base64) {
        return JSON.parse(Buffer.from(base64, "base64url").toString("utf-8"))
    }

    createSignature(base64urls, salt = "web7722") {
        // header.payload .join
        const data = base64urls.join(".")
        return this.crypto.createHmac("sha256", salt).update(data).digest("base64url")
    }
}

module.exports = JWT
const JWT = require("./jwt")
const crypto = require("crypto")

describe("lib/JWT.js", () => {
    let jwt
    it("constructor", () => {
        expect(typeof JWT).toBe("function") // class
        jwt = new JWT({ crypto }) // {crypto: {....}}
        expect(typeof jwt.crypto).toBe("object")
    })

    it("encode", () => {
        expect(typeof jwt.encode).toBe("function")
        const value = { foo: "bar" }
        const base64 = jwt.encode(value)
        expect(base64).toBe("eyJmb28iOiJiYXIifQ")
    })
})

위의 코드에서는 JWT 를 이용해 base64로 encoding, constructor 라는 생성자 함수, 그리고 Repository 의 저장된 method  및 resolve,reject 에 따른 조건별 테스트를 구현한 모습입니다.

 

이러한 방법으로 테스트코드를 확인할 수 있고, 단위, 통합테스트를 수행할 수 있습니다.

 

각각의 코드에서 typeof,  결과값이 object or function, string 과 같은 유형들을 확인할 수 있습니다.

테스트 코드의 사용은 위의 예시보다 다양하고 많을 수 있기 때문에, 이를 잘 활용하면 단계에서부터 오류를 파악하고 수정이 가능합니다.

 

즉 코드의 양이 많아질 수록, 코드 단위의 테스트가 올바르게 이루어지는지 확인하는데 큰 도움이 됩니다.

 

다음 포스팅에서 다시 또 다룰 때, 여러가지로 나뉘어 테스트를 구현해보도록 하겠습니다.

 

감사합니다.

반응형