본문 바로가기

TDD

Jest; JUnit 어노테이션을 참고한

반응형

Intro


Node.js 백엔드 개발자가 굳이 JUnit을 참고를 할 필요가 없는데 굳이 참고를 한 이유가 궁금할 것이다. 이유는 단순하다.

Node.js를 사용해서 TDD를 공부하려고 하는데 관련된 레퍼런스가 아무래도 자바공화국 답게 Java/Spring으로 다룬 테스트 코드가 더 많았기 때문이다(물론 Node.js와 관련된 레퍼런스도 찾을 수 있었지만 보통 Jest를 사용하는 아주 간단한 예제 정도 밖에 없었음). 책이나 유튜브, 인강을 보면서 공부를 하려고 해도 온 세상이 Java라서 어쩔 수 없이 Java, JUnit, Spring으로 만들어진 테스트 코드를 참고로 하면서 공부를 할 수 밖에 없었다. 다행히 학부 때 Java/SpringBoot를 다뤄본적이 있어 그렇게 어렵지는 않게 공부를 할 수 있었다.

 

@ParameterizedTest


  • 테스트 모듈에 전달할 값을 어노테이션의 배열로 지정한다
  • 테스트 모듈은 배열 길이 만큼 실행되고, 배열의 요소가 하나씩 인자 값으로 전달 된다
  • TYPES는 인자 값으로 사용할 타입의 복수명사(ints, strings, bytes, …)로 선언해야 한다

JUnit

ValueSource

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class ParameterizedTestExample {

    @ParameterizedTest
    @CsvSource({
        "1, 2, 3",
        "2, 3, 5",
        "3, 5, 8"
    })
    void add(int first, int second, int expectedResult) {
        assertEquals(expectedResult, first + second);
    }
}

CsvValue

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class ParameterizedTestExample {

    @ParameterizedTest
    @CsvSource({
        "1, 2",
        "2, 4",
        "3, 6"
    })
    void testMultiplication(int input, int expected) {
        assertEquals(expected, input * 2);
    }
}

 

Jest

ValueSource

describe("", () => {
	it.each([1, 2, 3, 4])('', (numbers) => {
		expect(numbers).toBeGreaterThan(0);
	});
});

CsvValue

describe('testMultiplication', () => {
  test.each([
    [1, 2],
    [2, 4],
    [3, 6],
  ])('given %i as input, returns %i', (input, expected) => {
    expect(input * 2).toBe(expected);
  });
});

 

 

 

@RepeatedTest


  • 지정된 횟수(value)만큼 반복적으로 실행되도록 설정하는 어노테이션
  • 10번 반복해서 테스트 해야 하는 경우, For 구문을 사용해서 테스트 코드를 작성할 수도 있지만, 코드의 가독성이 떨어진다
  • RepeatedTest 어노테이션을 사용하면 For 구문 없이 반복 실행할 수 있다

JUnit

import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.RepeatedTest;

class RepeatedTestExample {

    @RepeatedTest(5)
    void testRepeatedly() {
        int randomValue = (int) (Math.random() * 10);
        assertTrue(randomValue < 10);
    }
}

 

Jest

describe('testRepeatedly', () => {
  for (let i = 0; i < 5; i++) {
    test(`repetition ${i + 1}`, () => {
      const randomValue = Math.floor(Math.random() * 10);
      expect(randomValue).toBeLessThan(10);
    });
  }
});

 

 

 

@TestFactory


동적으로 테스트 모듈을 생성하는 팩토리 메서드를 정의하는 어노테이션

  • 동적으로 다양한 테스트 케이스를 검증해야 하는 경우에 사용한다
  • 인수테스트처럼 사용자 시나리오 테스트를 할 때 사용한다

JUnit

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.jupiter.api.Assertions.assertTrue;

class TestFactoryExample {

    @TestFactory
    Collection<DynamicTest> dynamicTests() {
        return Arrays.asList(
            DynamicTest.dynamicTest("Test 1", () -> assertTrue(Math.random() < 0.5)),
            DynamicTest.dynamicTest("Test 2", () -> assertTrue(Math.random() < 0.5)),
            DynamicTest.dynamicTest("Test 3", () -> assertTrue(Math.random() < 0.5))
        );
    }
}

 

Jest

describe('dynamicTests', () => {
  const tests = [
    { name: 'Test 1', fn: () => expect(Math.random()).toBeLessThan(0.5) },
    { name: 'Test 2', fn: () => expect(Math.random()).toBeLessThan(0.5) },
    { name: 'Test 3', fn: () => expect(Math.random()).toBeLessThan(0.5) }
  ];

  tests.forEach(({ name, fn }) => {
    test(name, fn);
  });
});

 

 

 

@ExtendWith


  • 테스트 클래스에서 공통적으로 사용할 확장 기능을 설정하는 어노테이션
    • Mock 객체를 쉽게 사용할 수 있도록 기능을 제공하는 프레임워크의 기능을 JUnit에서도 사용할 수 있또록 확장해주는 역할
    • JUnit의 테스트 코드에서 Spring ApplicationContext 기능을 JUnit에서도 사용할 수 있도록 확장해주는 역할

JUnit

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ExtendWithExample {

    @Mock
    private MyService myService;

    @InjectMocks
    private MyComponent myComponent;

    @Test
    void testService() {
        when(myService.doSomething()).thenReturn("Mocked Result");
        String result = myComponent.useService();
        assertEquals("Mocked Result", result);
    }
}

 

Jest

  • Jest는 Jest 자체에서 mocking 기능을 활용할 수 있다
  • jest.fn()을 이용해서 service 객체를 mocking하면서, mockReturnValue()를 활용하여 반환 값을 설정할 수 있다
const myService = {
  doSomething: jest.fn(),
};

const myComponent = {
  useService: function() {
    return myService.doSomething();
  },
};

describe('ExtendWithExample', () => {
  test('testService', () => {
    myService.doSomething.mockReturnValue('Mocked Result');
    const result = myComponent.useService();
    expect(result).toBe('Mocked Result');
  });
});

 

 

 

실습


describe("JUnit annotation을 Jest로 변환", () => {
  // JUnit: ParameterizedTest
  it.each([
    [1, 1, 2],
    [1, 2, 3],
    [2, 1, 3],
  ])(
    "ParameterizedTest(%i, %i, %i)",
    (firstElement, secondElement, expected) => {
      expect(firstElement + secondElement).toBe(expected);
    }
  );

  // JUnit: DisplayName
  it("DisplayName", () => {
    expect(true).toBe(true);
  });

  // JUnit: Disabled
  it.skip("Disabled", () => {
    expect(true).toBe(true);
  });

  // JUnit: Timeout
  it("Timeout", () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 1000);
    });
  }, 2000);

  // JUnit: Nested
  describe("Nested", () => {
    it("Nested", () => {
      expect(true).toBe(true);
    });
  });

  // JUnit: RepeatedTest
  for (let i = 0; i < 3; i++) {
    it("RepeatedTest", () => {
      expect(true).toBe(true);
    });
  }

  // JUnit: TestFactory
  const testFactory = (firstElement, secondElement, expected) => {
    it(`TestFactory(${firstElement}, ${secondElement}, ${expected})`, () => {
      expect(firstElement + secondElement).toBe(expected);
    });
  };
  testFactory(1, 1, 2);

  // JUnit: RepeatedTest + TestFactory
  for (let i = 0; i < 3; i++) {
    testFactory(1, 1, 2);
  }

  const tests = [
    { name: "Test 1", fn: () => expect(Math.random()).toBeLessThan(1) },
    { name: "Test 2", fn: () => expect(Math.random()).toBeLessThan(1) },
    { name: "Test 3", fn: () => expect(Math.random()).toBeLessThan(1) },
  ];

  tests.forEach(({ name, fn }) => {
    it(name, fn);
  });
});

 PASS  ./annotation.test.js
  JUnit annotation을 Jest로 변환
    ✓ ParameterizedTest(1, 1, 2) (3 ms)
    ✓ ParameterizedTest(1, 2, 3)
    ✓ ParameterizedTest(2, 1, 3)
    ✓ DisplayName (4 ms)
    ✓ Timeout (1003 ms)
    ✓ RepeatedTest
    ✓ RepeatedTest
    ✓ RepeatedTest (1 ms)
    ✓ TestFactory(1, 1, 2)
    ✓ TestFactory(1, 1, 2)
    ✓ TestFactory(1, 1, 2)
    ✓ TestFactory(1, 1, 2)
    ✓ Test 1
    ✓ Test 2
    ✓ Test 3
    ○ skipped Disabled
    Nested
      ✓ Nested

Test Suites: 1 passed, 1 total
Tests:       1 skipped, 16 passed, 17 total
Snapshots:   0 total
Time:        1.297 s, estimated 2 s
Ran all test suites.

Watch Usage: Press w to show more.
반응형

'TDD' 카테고리의 다른 글

Jest 많이 썼던 내용들 정리 (1)  (5) 2024.10.24
간단한 게시판 만들면서 TDD 맛보기 (POST 요청)  (1) 2024.09.01
단위 테스트: 기본 개념  (0) 2024.08.31
예제; 암호 검사기  (0) 2024.08.31
테스트의 개념과 중요성  (0) 2024.08.31