[Canvas-불꽃놀이-23] CircleParticle 클래스 단위 테스트

이전 글 : [Canvas-불꽃놀이-22] TextParticle 단위 테스트

 

[Canvas-불꽃놀이-22] TextParticle 단위 테스트

이전 글 : [Canvas-불꽃놀이-21] TailParticle 클래스 단위 테스트 [Canvas-불꽃놀이-21] TailParticle 클래스 단위 테스트이전 글 : [Canvas-불꽃놀이-20] Particle 클래스 단위 테스트 [Canvas-불꽃놀이-20] Particle

jinsk-joy.tistory.com

 

CircleParticle 단위 테스트

 

CircleParticle 단위 테스트

  • CircleParticle은 불꽃놀이에서 원형 효과를 담당한다. 
  • 상속받은 부모, CircleParticle의 속성, 로직을 테스트하여 안정성을 검증한다.

CircleParticle 클래스 전체 코드

더보기
import Particle from "@/js/particle/Particle.js";
import { CIRCLE } from "@/js/constants.js";

class CircleParticle extends Particle {
    /**
     * 불꽃놀이 원형 효과
     * @param {object} params
     * @param {CanvasRenderingContext2D} ctx
     * @param {boolean} isSmallScreen
     * @param {number} x
     * @param {number} y
     * @param {number} vx
     * @param {number} vy
     * @param {number} radius
     * @param {number} opacity
     * @param {string} [color]
     */
    constructor({ ctx, isSmallScreen, x, y, vx, vy, radius, opacity, color }) {
        super({ ctx, isSmallScreen, x, y, vx, vy, radius, opacity, friction: CIRCLE.FRICTION, color });
        this.gravity = CIRCLE.GRAVITY;

        this.initialState = { friction: CIRCLE.FRICTION };
    }

    draw() {
        this.ctx.save();
        this.ctx.shadowBlur = CIRCLE.SHADOW_BLUR;
        this.ctx.shadowColor = this.fillColor;
        super.draw();
        this.ctx.restore();
    }

    update() {
        this.radius += CIRCLE.RADIUS_ADJUST_OFFSET;
        this.opacity -= CIRCLE.OPACITY_ADJUST_OFFSET;
        this.vy += this.gravity;
        super.update();
        this.radius *= CIRCLE.RADIUS_ADJUST_RATE;
    }
}

export default CircleParticle;

 

1. 테스트 전 환경 설정과 Mock 함수 정의

  • 기본값 설정 시 사용되는 메서드 Mock 함수화하고 테스트 전 반환값을 설정한다.
// CircleParticle.js
jest.mock("@/js/utils.js", () => ({
    randomFloat: jest.fn(),
    setRgbaColor: jest.fn(),
}));
// CircleParticle.test.js
// 모든 테스트에 공통 적용, mock 함수화 한 메서드 반환값 설정
beforeAll(() => {
    randomFloat.mockReturnValue(PARTICLE_DEFAULT_VALUES.opacity);
    setRgbaColor.mockReturnValue(PARTICLE_DEFAULT_VALUES.color);
});

  • 필수적으로 사용되는 ctx, isSmallScreen을 전역 변수로 만들어 사용하도록 한다.
    ctx는 매번 테스트 전에 생성해 독립적인 환경을 만든다.
// CircleParticle.test.js
describe("CircleParticle 단위 테스트", () => {
    let ctx;
    const isSmallScreen = false;
    const { PARTICLE_DEFAULT_VALUES } = TEST_OPTION;

    // 생략...

    // 테스트전에 ctx 다시 생성하여 독립된 테스트 환경 설정
    beforeEach(() => {
        ctx = createMockCanvasCtx();
    });
    
    // 테스트 후 등록한 스파이의 호출 기록을 일괄적으로 초기화
    afterEach(() => {
        jest.clearAllMocks();
    });
    
    // 생략...
}

  • CircleParticle의 모든 속성의 결과값이 예상값과 일치하는지 검증하는 공통 함수 구현
// CircleParticle.test.js
function expectAllCircleVars(circle, expectedresult) {
    expectAllParticleVars(circle, expectedresult);
    expect(circle.gravity).toBe(CIRCLE.GRAVITY);
    expect(circle.initialState).toEqual({ friction: CIRCLE.FRICTION });
}

 

2. CircleParticle 생성자와 멤버 변수 테스트

  • 부모의 생성자가 잘 호출되는지, 인자로 전달한 값으로 속성이 설정되는지, CircleParticle만의 고유 속성이 오류 없이 설정되는지 확인한다.
// CircleParticle.test.js
test("CirCleParticle 생성자와 멤버변수 테스트", () => {
    const params = { x: 1, y: 2, vx: 10, vy: 4, radius: 2, opacity: 1, color: "rgba(255, 0, 0, 1)" };
    const circle = new CircleParticle({ ctx, isSmallScreen, ...params });

    expectAllCircleVars(circle, { ...params, friction: CIRCLE.FRICTION });
});

 

3. drarw 테스트

  • CircleParticle의 draw함수는 고유 로직이 추가되어있다. 
    shadowBlur와 shacowColor를 적용해서 파티클에 글로우 효과를 추가했다. 
  • shadowBlur와 shadowColor는 일단 화면에서 실제로 렌더링되는 부분을 보며 확인하도록 한다. draw를 잔행하고 난 후 캔버스를 이전 상태로 되돌리기 때문에 shadowBlur, shadowColors 속성에 들어간 값은 원상태로 초기화되기 때문에 테스트 코드로 별도로 확인하긴 복잡하기 때문이다.
  •  맨 처음 캔버스의 상태를 저장하고 복원하는 save, rsetore의 호출은 파티클 그리기 중 다른 속성에 영향을 끼 지지 않도록 해주기 때문에 호출 여부를 확인한다.
// CircleParticle.test.js
test("CircleParticle draw 메서드 테스트", () => {
    const color = "hsla(25, 50%, 50%)";
    const circle = new CircleParticle({ ctx, isSmallScreen, x: 10, y: 10, x: 5, y: 5, radius: 1, opacity: 0.5, color });
    
    // 부모의 draw 메서드 감시하기 위해 jest spy 추가
    const spyParticleDraw = jest.spyOn(Particle.prototype, "draw");
    circle.draw();

    // 자식의 로직과 부모의 로직이 모두 호출되었는지 검증
    expect(circle.ctx.save).toHaveBeenCalledTimes(1);
    expect(spyParticleDraw).toHaveBeenCalled();
    expect(circle.ctx.restore).toHaveBeenCalledTimes(1);
});

 

4. update 테스트

  • 부모의 업데이트 메서드와 CircleParticle만의 로직을 오류 없이 수행하는지 검증한다. 
  • 업데이트 후 결괏값이 예상한 값과 일치하는지 검증한다.
// CircleParticle.test.js
test("CircleParticle update 메서드 테스트", () => {
  const [x, y, vx, vy, radius, opacity, color] = [10, 10, 1, 1, 10, 0.8, "hsla(15, 50%, 50%)"];
    const circle = new CircleParticle({ ctx, isSmallScreen, x, y, vx, vy, radius, opacity, color });
    
    // 부모의 update를 감시하기위해 jest jpy 테스트
    const spyParticleUpdate = jest.spyOn(Particle.prototype, "update");
    circle.update();

    const expectedVy = (vy + CIRCLE.GRAVITY) * CIRCLE.FRICTION;
    const expectedVx = vx * CIRCLE.FRICTION;
    const expectedResult = {
        x: x + expectedVx,
        y: y + expectedVy,
        vx: expectedVx,
        vy: expectedVy,
        radius: (radius + CIRCLE.RADIUS_ADJUST_OFFSET) * CIRCLE.RADIUS_ADJUST_RATE,
        opacity: opacity - CIRCLE.OPACITY_ADJUST_OFFSET,
        color,
        friction: CIRCLE.FRICTION,
    };
    expectAllCircleVars(circle, expectedResult);
    
    // 부모의 update 메서드가 호출되었는지 검증
    expect(spyParticleUpdate).toHaveBeenCalled();
});

 

5. reset 테스트

  • reset도 마찬가지로 사용한 파티클을 기본값으로 초기화해서 풀로 돌려보낼 때와, 풀에서 꺼내와서 재활용할 때 전달한 값으로 초기화가 이루어지는지 검증한다. 

  • 사용한 파티클을 기본값으로 초기화해서 풀에 반환
// CircleParticle.test.js
test("CircleParticle reset 테스트 - 사용된 파티클 풀에 반환시 초기화", () => {
    const circle = new CircleParticle({
        ctx,
        isSmallScreen,
        x: 5,
        y: 3,
        vx: -4,
        vy: 2,
        radius: 3,
        opacity: 0.67,
        color: "rgba(255, 255, 255, 1)",
    });
    
    // 부모의 reset 메서드 감시하기 위해 jest spy 추가
    const spyParticleReset = jest.spyOn(Particle.prototype, "reset");
    circle.reset();

    // 반환 후 속성이 기본값으로 초기화 되었는지 검증한다.
    expectAllCircleVars(circle, { ...PARTICLE_DEFAULT_VALUES, friction: CIRCLE.FRICTION });

    // 부모의 reset 메서드가 호출 되었는지 검증
    expect(spyParticleReset).toHaveBeenCalled();
});

  • 기본값으로 초기화된 파티클을 풀에서 꺼내와 재사용
// CircleParticle.test.js
test("CircleParticle reset 테스트 - 풀에서 꺼내와서 재사용", () => {
    const circle = new CircleParticle({ ctx, isSmallScreen });

    // params의 값들로 파티클 속성 설정
    const params = { x: 3, y: 45, vx: -1, vy: 2, radius: 1.5, opacity: 0.78 };
    circle.reset(params);

    // 재사용 후 파티클의 모든 속성이 예측값과 일치하는지 검증
    expectAllCircleVars(circle, { ...PARTICLE_DEFAULT_VALUES, ...params, friction: CIRCLE.FRICTION });
});

 

테스트 실행 결과

npx jest ./__test__/particle/CircleParticle.test.js

 

Github Repo