[Canvas-불꽃놀이-28] Canvas 클래스 단위 테스트 (초기화)

이전 글 : [Canvas-불꽃놀이-27] CanvasOption 클래스 단위 테스트

 

[Canvas-불꽃놀이-27] CanvasOption 클래스 단위 테스트

이전 글 : [Canvas-불꽃놀이-26] TextData 클래스 단위 테스트 [Canvas-불꽃놀이-26] TextData 클래스 단위 테스트이전 글 : [Canvas-불꽃놀이-25] ParticleManager 클래스 단위 테스트 [Canvas-불꽃놀이-25] ParticleMana

jinsk-joy.tistory.com

 

Canvas 클래스 - 생성자와 멤버변수 초기화 검증

 

Canvas 클래스 초기화 관련 테스트

Canvas 클래스는 파티클 생성, 업데이트, 렌더등 애니메이션을 총괄하는 로직을 담고 있으므로 테스트 범위가 크다.

이에 따라 각 파트별로 나누어 테스트를 진행한다.

먼저 이 글에서는 생성자와 멤버변수 초기화, 초기화와 관련된 메서드를 테스트하겠다.

 

Canvas 클래스 초기화 관련 코드

더보기
class Canvas extends CanvasOption {
    /**
     * 애니메이션을 실행할 캔버스 클래스
     * - 화면에 불꽃놀이와 텍스트 파티클을 그리기 위한 기능을 제공
     * - CanvasOption을 확장하여 캔버스의 다양한 설정을 관리
     */
    constructor() {
        super();
        this.initCanvasVars();
    }

    init() {
        super.init();

        this.canvas.width = this.canvasCssWidth * this.dpr;
        this.canvas.height = this.canvasCssHeight * this.dpr;
        this.ctx.scale(this.dpr, this.dpr);

        this.canvas.style.width = `${this.canvasCssWidth}px`;
        this.canvas.style.height = `${this.canvasCssHeight}px`;

        this.mainTailVY = this.calculateTailVY(this.mainY);
        this.createTailPosX();
        this.createTailVY();

        this.pm.isSmallScreen = this.isSmallScreen;
        this.pm.maxPoolSize[TYPE_TEXT] *= this.textLength;
        this.pm.maxPoolSize[TYPE_SPARK] *= this.textLength;
    }

    initCanvasVars() {
        this.animationId = undefined;

        this.text = "";
        this.textLength = 0;
        this.mainTextData = {};
        this.subTextData = {};

        this.tailQty = TAIL.BASE_QTY;
        this.mainTailVY = 0;
        this.tailsLeftPosX = [];
        this.tailsRightPosX = [];
        this.tailsVY = [];
        this.tailCount = 0;
        this.isLeft = false;

        this.tailParticles = [];
        this.textParticles = [];
        this.circleParticles = [];
        this.sparkParticles = [];

        this.pm = new ParticleManager(this.ctx, this.isSmallScreen);
    }
    
    createTailPosX() {
        const length = this.tailQty;
        const exclusionDist = this.isSmallScreen ? TAIL.SMALL_EXCLUSION : TAIL.EXCLUSION;
        const leftStart = this.canvasCssWidth * TAIL.START_X_RATIO;
        const leftEnd = this.mainX - exclusionDist;
        const rightStart = this.mainX + exclusionDist;
        const xOffset = (leftEnd - leftStart) / (length - 1);

        this.tailsLeftPosX = Array.from({ length }, (_, i) => Math.floor(leftStart + i * xOffset));
        this.tailsRightPosX = Array.from({ length }, (_, i) => Math.floor(rightStart + i * xOffset));
    }

    /**
     * @param {number} yPos
     * @returns {number} y좌표를 인자로 받아 y의 속도를 반환
     */
    calculateTailVY(yPos) {
        return (yPos - this.canvasCssHeight) / this.interval;
    }

    createTailVY() {
        const length = this.tailQty;
        const minTailVY = this.calculateTailVY(this.canvasCssHeight * TAIL.MIN_Y_RATIO);
        const maxTailVY = this.calculateTailVY(this.canvasCssHeight * TAIL.MAX_Y_RATIO);
        const vyOffset = (maxTailVY - minTailVY) / (length - 1);

        this.tailsVY = Array.from({ length }, (_, i) => minTailVY + i * vyOffset);
    }

    // 생략....
}

 

1. 테스트 환경 공통 설정

  • Canvas 클래스의 인스턴스, 사용자 입력 문자열, 메서드 호출을 감시할 변수는 전역에서 선언하여 사용하도록 한다.
// CanvasInitialize.test.js
describe("Canvas 클래스 초기화 테스트", () => {
    let canvasInst;
    const userInput = "Canvas 초기화";
    const { INNER_WIDTH, INNER_HEIGHT } = TEST_OPTION;
    let spyInitCanvasVars;

    // 생략...
}

  • 테스트를 하다 보면 초기 설정과 달라질 수 있으므로 테스트 전 캔버스를 다시 세팅한다. 인스턴스 생성과 공통된 속성(문자열, 문자열 길이 등)을 추가하고 호출을 감시할 메서드도 정의하도록 한다.
  • 특정 메서드의 스파이를 생성하고 사용하면 호출 기록이 쌓이니 테스트 후 호출 기록을 삭제하도록 해 다음 테스트에 영향이 가지 않도록 한다.
// CanvasInitialize.test.js
// 테스트 전
beforeEach(() => {
    // 테스트트용 캔버스 생성과 크기 설정
    setTestCanvas();

    // 호출을 감시할 스파이 생성
    spyInitCanvasVars = jest.spyOn(Canvas.prototype, "initCanvasVars");

    // canvas 인스턴스와 공통 속성 설정
    canvasInst = new Canvas();
    canvasInst.text = userInput;
    canvasInst.textLength = userInput.length;
    canvasInst.isSmallScreen = false;
});

// 테스트 후 등록한 스파이의 호출 기록 일괄적으로 초기화
afterEach(() => {
    jest.clearAllMocks();
});

 

2. 생성자와 initCanvasVars 테스트

  • 생성자와 생성자 안에서 실행되는 initCanvasVars를 테스트하여 멤버변수의 초기화가 잘 이루어지는지 확인하도록 한다.
// CanvasInitialize.test.js
test("constructor, initCanvasVars 테스트 | 생성자와 멤버변수 초기화 테스트", () => {
    // canvas 인스턴스를 생성하기 전 스파이를 등록해서 생성자 안에있는 메서드 호출을 감시할 수 있다.
    // 1번 실행되었는지 확인
    expect(spyInitCanvasVars).toHaveBeenCalledTimes(1);
    
    // 멤버변수 초기화 값이 예상값과 일치하는지 검증
    expect(canvasInst.animationId).toBeUndefined();
    expect(canvasInst.text).toBe(userInput);
    expect(canvasInst.textLength).toBe(userInput.length);
    expect(canvasInst.mainTextData).toEqual({});
    expect(canvasInst.subTextData).toEqual({});
    expect(canvasInst.tailQty).toBe(TAIL.BASE_QTY);
    expect(canvasInst.mainTailVY).toBe(0);
    expect(canvasInst.tailsLeftPosX).toHaveLength(0);
    expect(canvasInst.tailsRightPosX).toHaveLength(0);
    expect(canvasInst.tailsVY).toHaveLength(0);
    expect(canvasInst.tailCount).toBe(0);
    expect(canvasInst.isLeft).toBeFalsy();
    expect(canvasInst.tailParticles).toHaveLength(0);
    expect(canvasInst.textParticles).toHaveLength(0);
    expect(canvasInst.circleParticles).toHaveLength(0);
    expect(canvasInst.sparkParticles).toHaveLength(0);
    expect(canvasInst.pm).toBeInstanceOf(ParticleManager);
});

 

3. init 메서드 테스트

  • Canvas의 init 메서드는 DPR에 따른 캔버스 물리적 크기를 설정하고 애니메이션에 필요한 멤버변수의 값을 계산하는 역할을 한다.
  • 또한 화면이 리사이즈되어 다시 로드를 할 때 화면의 크기가 바뀌니 애니메이션에 필요한 시작값들을 재계산하는 역할도 한다.
  • tail의 x 좌표값 배열과 y 좌표값 속도 배열을 계산하는 메서드는 간단히 호출여부만 확인하도록 하고 별도의 테스트로 분리해서 진행하도록 한다.
// CanvasInitiallize.test.js
test("init 테스트", () => {
    // init 메서드 실행전 tail 관련 메서드의 호출을 감시하도록 spy 등록
    const spyCreateTailPosX = jest.spyOn(canvasInst, "createTailPosX");
    const spyCreateTailVY = jest.spyOn(canvasInst, "createTailVY");

    canvasInst.init();

    // 캔버스의 물리적 크기와 렌더링 시 보여줄 캔버스 태그 크기 검증
    expect(canvasInst.canvas.width).toBe(INNER_WIDTH * canvasInst.dpr);
    expect(canvasInst.canvas.height).toBe(INNER_HEIGHT * canvasInst.dpr);
    expect(canvasInst.ctx.scale).toHaveBeenCalledWith(canvasInst.dpr, canvasInst.dpr);
    expect(canvasInst.canvas.style.width).toBe(`${INNER_WIDTH}px`);
    expect(canvasInst.canvas.style.height).toBe(`${INNER_HEIGHT}px`);

    // 꼬리를 생성하기 위해 필요한 값들 검증 (메서드는 별도의 테스트로 분리한다. 간단히 호출 여부만 확인)
    expect(canvasInst.mainTailVY).toBeCloseTo((canvasInst.mainY - INNER_HEIGHT) / canvasInst.interval);
    expect(canvasInst.tailsLeftPosX).toBeDefined();
    expect(canvasInst.tailsRightPosX).toBeDefined();
    expect(canvasInst.tailsLeftPosX).toHaveLength(TAIL.BASE_QTY);
    expect(canvasInst.tailsRightPosX).toHaveLength(TAIL.BASE_QTY);
    expect(canvasInst.createTailPosX).toHaveBeenCalledTimes(1);
    expect(canvasInst.createTailVY).toHaveBeenCalledTimes(1);

    // 파티클의 생성과 반납을 관리하는 Particle 매니저 속성과 풀 사이즈 계산값 검증
    expect(canvasInst.pm.isSmallScreen).toBeFalsy();
    expect(canvasInst.pm.maxPoolSize[PARTICLE.TYPE_TEXT]).toBe(PARTICLE.TEXT_POOL * canvasInst.textLength);
    expect(canvasInst.pm.maxPoolSize[PARTICLE.TYPE_SPARK]).toBe(PARTICLE.SPARK_POOL * canvasInst.textLength);
});

 

4. Tail 생성 관련 테스트

  • Tail의 생성 위치를 정해 그 안에서만 생성되도록 하면 시각적으로 균형 있는 모습을 볼 수 있다.
    이에 화면(모바일/PC) width에 따라 왼쪽, 중앙, 오른쪽에 생성될 tail의 x좌표값들의 배열을 검증하도록 한다.
  • 모바일 화면은 width가 좁아지므로 제외 거리가 다른 비율로 계산되기 때문에 화면 크기별로 테스트를 해야 한다.
// CanvasInitialize.test.js
test.each([
     // 두가지 환경으로 나누어 진행
    { isSmallScreen: true, notice: "모바일 화면" },
    { isSmallScreen: false, notice: "PC 화면" },
])("createTailPosX 테스트 | 멤버변수 tailsLeftPosX, tailsRightPosX 검증, $notice", ({ isSmallScreen }) => {
    canvasInst.isSmallScreen = isSmallScreen;
    canvasInst.createTailPosX();

    // 배열값들이 정의되었는지 확인하고 배열의 길이가 예상값과 일치하는지 확인한다.
    expect(canvasInst.tailsLeftPosX).toBeDefined();
    expect(canvasInst.tailsRightPosX).toBeDefined();
    expect(canvasInst.tailsLeftPosX).toHaveLength(canvasInst.tailQty);
    expect(canvasInst.tailsRightPosX).toHaveLength(canvasInst.tailQty);

    // 배열은 일정한 규칙성을 띄므로 중앙에서의 제외 거리를 구해 왼쪽의 마지막 값, 오른쪽의 첫번째 값을 검증하도록 한다.
    const expectedExclusionDist = canvasInst.isSmallScreen ? TAIL.SMALL_EXCLUSION : TAIL.EXCLUSION;
    expect(canvasInst.tailsLeftPosX.at(-1)).toBe(canvasInst.mainX - expectedExclusionDist);
    expect(canvasInst.tailsRightPosX.at(0)).toBe(canvasInst.mainX + expectedExclusionDist);
});

  • 맨 아래에서부터 출발하니 y좌표의 위치는 canvas의 height다.
    어느 정도의 속도로 움직일지 정해야 하니 tail의 y좌표의 속도 배열을 검증한다.
// CanvasIniaialize.test.js
test("createTailVY 테스트 | tail의 y 속도 배열 계산", () => {
    canvasInst.createTailVY();

    // y 속도값 배열의 정의되었는지와 예상길이가 일치한지, 배열의 첫번째 원소가 예상값과 일치한지 검증하도록 한다.
    expect(canvasInst.tailsVY).toBeDefined();
    expect(canvasInst.tailsVY).toHaveLength(TAIL.BASE_QTY);
    expect(canvasInst.tailsVY[0]).toBe(canvasInst.calculateTailVY(INNER_HEIGHT * TAIL.MIN_Y_RATIO));
});

 

테스트 결과

npx jest .__test__/canvas/CanvasInitialize.test.js

 

 

Github Repo
 

GitHub - jinsk9268/text-fireworks

Contribute to jinsk9268/text-fireworks development by creating an account on GitHub.

github.com