본문 바로가기

프로젝트 개발일지/toss-slash 라이브러리

[toss-slash 라이브러리] localStorage와 테스트 코드

이번 코드는 토스 슬래시 라이브러리에 있는 코드는 아니다.

토스 슬래시에도 스토리지 관련 코드들이 있기는 한데 내가 느끼기에 더 효율적으로 쓸 수 있을 거 같은 코드가 있어서 들고 오게 되었다.

 

일단 나는 로컬 스토리지로 범위를 좁혔고, 로컬 스토리지 상에서 직렬화, 비직렬화 관련 함수까지 정의해서 효율적으로 사용하는 코드가 있다는 것을 알게 되어 이 코드로 로컬 스토리지를 정의해서 쓰게 되었고, 그와 관련된 테스트 코드까지 작성해봤다.

이 코드는 같이 프로젝트를 진행하는 친구가 작성한 코드, 당근과 토스 라이브러리 상에 있는 스토리지 관련 코드들을 쭉 한 번 둘러본 후 필요한 부분 위주로 취합하여 작성한 코드이다.

 

코드 전체는 다음과 같다.

interface Storage<T extends SerializableParam> {
  get(key: string): T | null;
  set(key: string, value: T): void;
  remove(key: string): void;
  clearAll(): void;
}

export class LocalStorage implements Storage<SerializableParam> {
  private serialize = (value: SerializableParam): string => {
    return JSON.stringify(value);
  };

  private deserialize = (value: string | null): SerializableParam => {
    if (value) {
      return JSON.parse(value);
    } else {
      return null;
    }
  };

  get(key: string) {
    return this.deserialize(window.localStorage.getItem(key));
  }

  set(key: string, value: SerializableParam) {
    window.localStorage.setItem(key, this.serialize(value));
  }

  remove(key: string) {
    window.localStorage.removeItem(key);
  }

  clearAll() {
    window.localStorage.clear();
  }
}

export const localStorage = new LocalStorage();

 

일단, 로컬 스토리지를 사용하면서 호출 시 필요한 메소드가 네 개 있다.

get, set, remove, clearAll 이렇게 네 가지인데, 로컬 스토리지에 값을 저장하고 불러올 때는 직렬화라는 부분을 신경써줘야 한다.

그래서, 직렬화를 하기 위한 함수와 비직렬화를 하기 위한 함수를 따로 정의해주었는데, 이게 각각 serialize, deserialize 함수이다.

하지만, 로컬 스토리지를 사용함에 있어서 이 함수들을 별도로 호출할 것은 아니기 때문에 private으로 선언해주었다.

 

사실 이제까지는 항상 로컬 스토리지가 필요한 상황이 발생하면 그냥 매번 직렬화하고, 매번 데이터 하나 저장하는 코드들을 작성해왔다.

그래서, 응집성 있게 코드를 관리하지 못했던 것 같다.

그런데 이 코드를 보고 나서는 이런 부분들을 보완 할 수 있을 거 같다는 생각에 앞으로 로컬 스토리지를 적극 사용할 일이 있다면 이 코드를 사용할 예정이다.

 

이제 테스트 코드를 한 번 알아보자.

const testData: SerializableParam = {
  key: "testData1",
};

export default describe("로컬 스토리지", () => {
  let storage: LocalStorage;

  beforeEach(() => {
    storage = localStorage;
    storage.set("testData1", testData);
  });

  afterEach(() => {
    storage.clearAll();
  });

  // get
  it("get 메소드를 통해 로컬 스토리지에 저장된 데이터를 불러옵니다.", () => {
    const data = storage.get("testData1");

    expect(data).toEqual(testData);
  });

  // set + get
  it("로컬 스토리지에 set 메소드를 통해 데이터 저장 후, get 메소드를 통해 로컬 스토리지에 데이터가 저장되었는지 확인합니다.", () => {
    const newData: SerializableParam = {
      key: "testData2",
    };
    storage.set("testData2", newData);
    const savedData = storage.get("testData2");

    expect(savedData).toEqual(newData);
  });

  // remove + get
  it("로컬 스토리지에 저장된 데이터를 remove 메소드를 통해 제거한 후 get 메소드를 통해 로컬 스토리지에 데이터가 있는지 확인합니다.", () => {
    storage.remove("testData1");
    const data = storage.get("testData1");

    expect(data).toBeNull();
  });

  // clearAll + get
  it("로컬 스토리지 내부 데이터를 clearAll 메소드를 통해 모두 삭제한 후, get 메소드를 통해 데이터가 잘 삭제되었는지 확인합니다.", () => {
    storage.clearAll();
    const data = storage.get("testData1");

    expect(data).toBeNull();
  });
});

 

이번에는 beforeEach, afterEach 구문을 함께 적어주었는데, 이는 setup과 teardown이라고 테스트 전후 설정을 해준 것이다.

모든 테스트는 매번 독립적으로 실행되어야 하기 때문에 이전 테스트에 종속적인 결과를 발생시키지 않도록 하기 위해서 이 작업을 해주는 것이다.

 

여기서는 로컬 스토리지를 불러와 최소한의 테스트 데이터를 하나 넣어주는 작업을 setup 시 해주기 위해서 beforeEach 구문에 넣어주었고, afterEach 구문에는 스토리지를 다 비워주는 작업을 진행하였다.

이렇게 하면 각 테스트마다 beforeEach, afterEach 구문이 실행되어 테스트를 독립적으로 진행할 수 있게 된다.

 

그리고 이번 테스트 코드에서 테스트해봐야하는 로직을 다음과 같이 정리해보았다.

1. get 메소드 동작 확인

2. set 메소드 동작 확인

3. remove 메소드 동작 확인

4. clearAll 메소드 동작 확인

 

이를 위해서 각 테스트를 작성하였고 모두 테스트를 잘 통과한 것까지 확인하였다.

테스트 코드에 대한 설명은 필요 없을 것 같아 생략한다.