Skip to content

๐Ÿงช Hyunduxโ€test: ์ƒํƒœ๊ด€๋ฆฌ ํ…Œ์ŠคํŠธ

Dunk edited this page Aug 25, 2024 · 1 revision

์†Œ๊ฐœ

ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๊ด€๋ฆฌ ํˆด์ธ hyundux์—์„œ given when then์˜ ํŒจํ„ด์„ ๊ธฐ์ค€์œผ๋กœ state๋ฅผ spreadํ•˜์ง€ ์•Š๊ณ  ๊ฐ state์— ๋Œ€์‘๋˜๋Š” ๋ชจ๋“  ์ƒํ™ฉ์„ ํ•œ ํŒŒ์ผ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์›์น™์œผ๋กœ ํ•˜๋Š” test ๋ชจ๋“ˆ์ด๋‹ค.

์ฃผ์•ˆ์ 

  1. given when then: ์ด 3๊ฐ€์ง€ ํ…Œ์ŠคํŠธ ์›์น™์„ ๋ฐ”ํƒ•์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•˜๋ ค๋Š” ์ด์ „ ์ƒํƒœ์™€ ๊ฒฐ๊ณผ ์ƒํƒœ๊ฐ€ ๋ช…ํ™•ํžˆ ๋ณด์ธ๋‹ค.
  2. ์ˆœ์ˆ˜ํ•จ์ˆ˜: hyundux์˜ reducer๋Š” ํ˜„์žฌ์˜ ํ• ๋‹น๋œ state ์—๋งŒ ๋ฐ˜์‘ํ•˜๋Š”๋ฐ ์ด๋•Œ ์˜ค๋กœ์ง€ ์ˆœ์ˆ˜ํ•จ์ˆ˜๋กœ๋งŒ ๋งŒ๋“ค์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด hyundux๊ฐ€ testableํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ตœ๋Œ€ํ•œ ์ด์šฉํ•˜๊ณ ์ž ํ•˜์˜€๋‹ค.

hyundux-test ๊ตฌ์กฐ

Test code

const tester = createHynduxTester(initFindingGameState, findingGameReducer);

test("๊ฒฐ๊ณผ๋ณด์—ฌ์ง€๋Š” ๊ฒƒ๋“ค์˜ ์ธ๋ฑ์Šค๋ณ€๊ฒฝ_์ž…๋ ฅ๊ฐ’:1 -> ๊ฒฐ๊ณผ๊ฐ’ 1 (์˜ฎ์€ ํ…Œ์ŠคํŠธ)", () => {
  const givenData = initFindingGameState.payload;
  const whenData: Action = action.changeShowingAnswer(1);
  const thenData = tester.makeThenPayload({ answerIndex: 1 })
  tester.given(givenData).when(whenData).then(thenData, true);
});
  1. givenData: ์ฃผ์–ด์ง„ ๋ฐ์ดํ„ฐ (์œ„ ์ฝ”๋“œ์— ๊ฒฝ์šฐ initState๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.)
  2. whenData: action์ด ์ฃผ์–ด์ง€๋ฉด givenData๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.
  3. thenData: expected Data๋ผ๊ณ ๋„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. tester.given().when().then(): ์ด๋Š” ์ฃผ์–ด์ง„ ํ…Œ์ŠคํŠธ์˜ ์ƒํƒœ์™€ ์•ก์…˜๋“ค์„ ๋ช…ํ™•ํžˆ ๊ฐ€์‹œํ™” ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. then์˜ ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ•ด๋‹น ํ…Œ์ŠคํŠธ์˜ ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ƒํ•ด์„œ ์ ๋Š”๋‹ค.

๊ตฌํ˜„๋ถ€

export class HyunduxTest<T> {
  store: Store | null = null;
  currentState: State<T> | null = null;
  currentAction: Action | null = null;
  resultState: State<T> | null = null;
  
  constructor(initState: State<T>, reducer: Reducer<T>) {
    this.store = createStore();
    this.currentState = initState;
    if (this.store !== null) {
      this.store.subscribe(initState, reducer, (newState) => {
        this.resultState = newState;
	  });
	}
  }
  
  makeThenPayload(payLoad: object) {
    return { ...this.currentState.payload, ...payLoad };
  }
  
  given(givenPayload: T) {
    this.store.states = [createState(this.currentState.type, givenPayload)];
    if (this.store !== null && this.currentState !== null) {
      this.currentState = createState(this.currentState.type, givenPayload);
    }
    return this;
  }

  when(action: Action) {
    this.currentAction = action;
    return this;
  }
  
  async then<T>(expectedPayload: T, isTrue: boolean = true) {
    if (this.currentAction != null &&
        this.store !== null &&
        this.currentState !== null
    ) {
      this.store.states = [this.currentState];
      await this.store?.dispatch(this.currentAction);
      if (this.resultState !== null) {
        if (isTrue) {
          expect(this.resultState?.payload).toEqual(expectedPayload);
        } else {
          expect(this.resultState?.payload).not.toEqual(expectedPayload);
        }
      }
    }
  }
}

given, when, then ํŒจํ„ด์„ ๊ธฐ์ค€์œผ๋กœ ํ˜„์žฌ ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง€๋Š” ๋ณ€์ˆ˜๋“ค์„ ๋ฐ”๊พธ๋ฉด ๋งˆ์ง€๋งŒ then์˜ ์‹คํ–‰์„ ๋•๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์ฆ‰ given, when, then์€ ๋ช…์‹œ์ ์ด ํ•จ์ˆ˜์ด๋‹ค.

+ Hyundux-saga์˜ ํ…Œ์ŠคํŠธ

๋†€๋ž๊ฒŒ๋„ Hyundux-saga๋Š” ๋‹จ์ˆœํžˆ API์™€ action์„ ์—ฐ๊ฒฐํ•˜๋Š” ์—ญํ• ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธํ• ๋•Œ๋Š” ๊ธฐ์กด action๊ณผ ๋˜‘๊ฐ™์ด ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค.

test("Test Title", () => {
  const givenData = initFindingGameState.payload;
  const whenData: Action = action.click({ request: {
      answerList: [
        { positionX: 0.5, positionY: 0.5 },
      ]
    }, response: {
      responseCode: 1000,
      message: "success",
      data: {
        ticketIryd: "1212",
        startTime: "asdsad",
        correctAnswerList: [
          {
            positionX: 0.5,
            positionY: 0.5,
            descriptionImageUrl: "",
            title: "",
            content: ""
          }
        ]
      }
  }})
  const thenData = tester.makeThenPayload({ showingAnswers: [{
    positionX: 0.5,
    positionY: 0.5,
    descriptionImageUrl: "",
    title: "",
    content: ""
  }]})
  tester.given(givenData).when(whenData).then(thenData, true);
});

๋†€๋ž๊ฒŒ๋„ click action์„ saga์—์„œ ์‚ฌ์šฉํ•˜๋Š” action์ธ๋ฐ ์œ„์ฒ˜๋Ÿผ mock ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด๋„ ์ž˜ ํ…Œ์ŠคํŠธ ๋œ๋‹ค.

์ถ”ํ›„ ์ˆ˜์ •์‚ฌํ•ญ

  1. saga์˜ test๋Š” ํŠน์ • ์ˆœ๊ฐ„์— test๋ฅผ ๊ฑธ ์ˆ˜ ์žˆ๋Š”๋ฐ ๊ทธ ์ ์€ ๋งค์šฐ ํฐ ์žฅ์ ์ธ๊ฑฐ๊ฐ™๋‹ค(generate function์ด๊ธฐ ๋•Œ๋ฌธ์ด๊ธด ํ•˜์ง€๋งŒ...)
  2. ์‹ค์ œ hyundux-saga์— ๋Œ€ํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ๋„ ํ•„์š”๋กœํ• ๊ฒƒ ๊ฐ™๋‹ค.