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

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

 

[Canvas-불꽃놀이-26] TextData 클래스 단위 테스트

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

jinsk-joy.tistory.com

 

캔버스의 여러 속성을 설정할 CanvasOption 클래스 단위 테스트

 

CanvasOption 클래스 단위 테스트

드디어 캔버스 테스트로 넘어왔다.

CanvasOption 클래스는 캔버스 렌더링 및 속성 설정(DPI, 크기, 폰트 등)을 관리한다. 

또한 반응형을 고려한 canvas의 크기와 여러 좌표값들을 변경해야 의도한 모습이 나오므로 오류 없이 수행돼야 원하는 결과를 얻을 수 있다.

 

CanvasOption 클래스 전체 코드

더보기
import { ANIMATION, SCREEN, POS, FONT } from "@/js/constants.js";

class CanvasOption {
    /**
     * 캔버스 기본 설정을 관리하고 초기화하는 클래스
     * - 캔버스 엘리먼트를 정의하고, 물리적 및 CSS 크기, DPI, 폰트 설정 등을 처리
     * - 화면 크기에 따라 캔버스 옵션을 동적으로 조정
     */
    constructor() {
        this.canvas = document.getElementById("canvas");
        if (!this.canvas) {
            throw new Error("캔버스 객체를 발견하지 못했습니다. 다시 확인해주세요.");
        }
        this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
        this.fps = ANIMATION.FPS;
        this.interval = 1000 / this.fps;
        this.initCanvasOptionVars();
    }

    init() {
        this.initCanvasOptionVars();
    }

    initCanvasOptionVars() {
        this.dpr = Math.min(Math.round(window.devicePixelRatio), SCREEN.MAX_DPR) || 1;
        this.canvasCssWidth = window.innerWidth;
        this.canvasCssHeight = window.innerHeight;
        this.isSmallScreen = window.matchMedia(`(max-width: ${SCREEN.SMALL_WIDTH}px)`).matches;
        this.mainX = this.canvasCssWidth / POS.MAIN_X_DIVISOR;
        this.mainY = Math.floor(this.canvasCssHeight * POS.MAIN_Y_RATIO);
        this.mainFontSize = this.setMainFontSize();
        this.subFontSize = this.setSubFontSize();
    }

    /**
     * @param {number} [fontSize] 폰트 사이즈, 없을 시 화면 크기에 따라 설정
     * @returns {number} 메인 폰트 사이즈 반환 (작은 화면일 경우 별도의 비율 적용)
     */
    setMainFontSize(fontSize) {
        if (!fontSize) {
            const ratio = this.isSmallScreen ? FONT.MAIN_RATIO_SMALL : FONT.MAIN_RATIO_GENERAL;
            fontSize = ((this.canvasCssWidth + this.canvasCssHeight) / 2) * ratio;
        }

        return Math.round(fontSize);
    }

    /**
     * @returns {number} 서브 폰트 사이즈 반환
     */
    setSubFontSize() {
        return Math.round(this.mainFontSize * FONT.SUB_RATIO);
    }

    /**
     * @param {number} fontSize
     * @returns ctx font 설정을 위한 문자열 반환
     */
    setFontStyle(fontSize) {
        return `${fontSize}px ${FONT.FAMILY}`;
    }

    /**
     * @param {string} color
     */
    fillFullCanvas(color) {
        this.ctx.fillStyle = color;
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    }

    /**
     * @param {number} x
     * @param {number} y
     * @returns 파티클이 캔버스 영역을 벗어날 경우 true를 반환, 영역안이면 false를 반환
     */
    isOutOfCanvasArea(x, y) {
        return x < 0 || x > this.canvasCssWidth || y < 0 || y > this.canvasCssHeight;
    }
}

export default CanvasOption;

 

1. 테스트 공통 설정하기

  • 테스트마다 CanvasOption의 인스턴스를 생성해야하니 beforeEach로 이 과정을 처리한다.
describe("CanvasOption 테스트 (jest-canvas-mock 활용)", () => {
    // CanvasOption 인스턴스
    let canvasOption;

    // 테스트 시작전 캔버스를 세팅하고 인스턴스를 생성한다.
    beforeEach(() => {
        setTestCanvas();
        canvasOption = new CanvasOption();
    });

    // 생략...
}

 

2. 캔버스 생성자와 멤버변수 초기화 테스트

캔버스 객체를 잘 가져오는지, 가져오지 못한다면 지정한 오류를 처리하는지와 멤버변수가 의도한 대로 설정되는지 검증하도록 한다.


  • canvas element와 context 설정 여부 테스트
// CanvasOption.test.js
test("캔버스 객체 관련 멤버변수 초기화", () => {
    // 캔버스 엘리먼트와 context가 정상적으로 생성되는지 검증
    expect(canvasOption.canvas).toBeInstanceOf(HTMLCanvasElement);
    expect(canvasOption.ctx).toBeInstanceOf(CanvasRenderingContext2D);
});

  • canvas element를 가져오지 못할 경우 에러를 발생시키는지 테스트
// CanvasOption.test.js
test("캔버스 객체를 불러오지 못할 경우 에러 처리", () => {
    // 캔버스 객체를 삭제하여 오류를 던지는지 확인한다.
    document.getElementById(TEST_OPTION.CANVAS_ID).remove();

    expect(() => new CanvasOption()).toThrow("캔버스 객체를 발견하지 못했습니다. 다시 확인해주세요.");
});

  • 애니메이션 관련 멤버 변수 검증
// CanvasOption.test.js
test("애니메이션 관련 멤버변수 초기화", () => {
    expect(canvasOption.fps).toBe(ANIMATION.FPS);
    expect(canvasOption.interval).toBe(1000 / canvasOption.fps);
    expect(canvasOption.dpr).toBeGreaterThanOrEqual(1);
    expect(canvasOption.dpr).toBeLessThanOrEqual(SCREEN.MAX_DPR);
});

  • 캔버스 CSS 크기와, 텍스트 픽셀데이터를 뽑고, 불꽃놀이의 메인이 되는 x, y좌표 검증
// CanvasOption.test.js
test("캔버스 css 크기, 메인좌표 관련 멤버변수 초기화", () => {
    expect(canvasOption.canvasCssWidth).toBe(INNER_WIDTH);
    expect(canvasOption.canvasCssHeight).toBe(INNER_HEIGHT);
    expect(canvasOption.mainX).toBe(INNER_WIDTH / POS.MAIN_X_DIVISOR);
    expect(canvasOption.mainY).toBe(Math.floor(INNER_HEIGHT * POS.MAIN_Y_RATIO));
});

  • PC화면과 모바일 화면(480px 미만) 여부 검증
// CanvasOption.test.js
test("PC 화면일때", () => {
    expect(canvasOption.isSmallScreen).toBe(false);
});

test("모바일 화면일때", () => {
    defineDomObjectProperty({ domObj: window, property: TYPE_INNER_WIDTH, value: SMALL_INNER_WIDTH });
    canvasOption.initCanvasOptionVars();

    expect(canvasOption.isSmallScreen).toBe(true);
});

  • PC화면과 모바일화면일때 메인 폰트와 서브 폰트의 사이즈 검증
// CanvasOption.test.js
test.each([
    { fontsize: undefined, testWidth: INNER_WIDTH, notice: "PC 화면-인자 전달하지 않아 기본값 적용" },
    { fontsize: 100, testWidth: SMALL_INNER_WIDTH, notice: "모바일 화면-인자 100 전달" },
])("mainFontSize($fontsize) 테스트 ($notice)", ({ fontsize, testWidth }) => {
    // 화면 사이즈 세팅
    defineDomObjectProperty({ domObj: window, property: TYPE_INNER_WIDTH, value: testWidth });
    canvasOption.initCanvasOptionVars();

    // 화면별 폰트 사이즈별 달라지는 비율 적용
    const result = canvasOption.setMainFontSize(fontsize);
    const ratio = canvasOption.isSmallScreen ? FONT.MAIN_RATIO_SMALL : FONT.MAIN_RATIO_GENERAL;
    const expectedResult = fontsize ?? Math.round(((testWidth + INNER_HEIGHT) / 2) * ratio);

    // 예상값과 결과값이 일치하는지 확인
    expect(result).toBe(expectedResult);
    expect(canvasOption.isSmallScreen).toBe(testWidth <= SMALL_INNER_WIDTH);
});

test("subFontSize 테스트", () => {
    const result = canvasOption.setSubFontSize();

    expect(result).toBe(Math.round(canvasOption.mainFontSize * FONT.SUB_RATIO));
});

 

3.  캔버스 전체 화면 덮기 테스트

  • 캔버스 전체 화면을 지정한 색상으로 채우는지 검증
// CanvasOption.test.js
test("fillFullCanvas 테스트", () => {
    canvasOption.fillFullCanvas("#121212");

    // fillStyle이 예상값과 같은지 검증
    expect(canvasOption.ctx.fillStyle).toBe("#121212");
    
    // 실제 fillRect를 호출할 때의 인자와 테스트 인자가 동일한지 검증
    expect(canvasOption.ctx.fillRect).toHaveBeenCalledWith(0, 0, canvasOption.canvas.width, canvasOption.canvas.height);
});

 

4. 파티클의 좌표값이 캔버스 영역을 벗어났는지 테스트

  • 파티클의 좌표값이 캔버스 영역 밖을 벗어난 경우 애니메이션 업데이트와 그리는 걸 진행할 필요가 없다.
    이 메서드를 통해 영억을 벗어나면 파티클 배열에서 제외시키면 메모리 효율성도 좋아진다.
// CanvasOption.test.js
test.each([
    { x: 10, y: 10, expected: false, notice: "캔버스 내부" },
    { x: INNER_WIDTH, y: INNER_HEIGHT, expected: false, notice: "캔버스 내부" },
    { x: -10, y: 10, expected: true, notice: "캔버스 왼쪽 밖" },
    { x: 10, y: -10, expected: true, notice: "캔버스 위쪽 밖" },
    { x: -10, y: -10, expected: true, notice: "캔버스 왼쪽, 위쪽 밖" },
    { x: INNER_WIDTH + 10, y: 10, expected: true, notice: "캔버스 오른쪽 밖" },
    { x: 10, y: INNER_HEIGHT + 10, expected: true, notice: "캔버스 아래쪽 밖" },
    { x: INNER_WIDTH + 10, y: INNER_HEIGHT + 10, expected: true, notice: "캔버스 오른쪽, 아래쪽 밖" },
])("isOutOfCanvas($x, $y) 테스트 / $notice", ({ x, y, expected }) => {
    expect(canvasOption.isOutOfCanvasArea(x, y)).toBe(expected);
});

 

테스트 결과

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

 

 

Github Repo
 

GitHub - jinsk9268/text-fireworks

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

github.com