반응형

깨끗한 테스트 코드

 

목차

1. 테스트 코드의 중요성

2. 테스트의 종류

3. Unit Test 작성

4. FIRST 원칙

5. 오픈소스 속 Unit Test


1. 테스트 코드의 중요성

  - 테스트 코드는 실수를 바로잡아준다.

  - 테스트 코드는 반드시 존재해야하며, 실제 코드 못지 않게 중요하다.

  - 테스트 케이스는 변경이 쉽도록 한다. 코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이 바로 단위테스트다.

  - 테스트 케이스가 있으면 변경이 두렵지 않다. 테스트 케이스가 없다면 모든 변경이 잠정적인 버그다. 테스트 커버리지가 높을수록 버그        에 대한 공포가 줄어든다.

  - 지저분한 테스트 코드는 테스트를 안하니만 못하다.

 

<Effective Unit Test 책에서>

'테스트는 실사용에 적합한 설계를 끌어내준다.'

'테스트를 작성해서 얻게 되는 가장 큰 수확은 테스트 자체가 아니다. 작성 과정에서 얻는 깨달음이다.'

 

'테스트트 자동화되어야 한다.' - 매번 배포할 때마다 실행되어야 한다.

 

2. 테스트의 종류

Unit Test : 프로그램 내부의 개발 컴포넌트의 동작을 테스트한다. 배포하기 전에 자동으로 실행되도록 많이 사용한다.

Integration Test : 프로그램 내부의 개별 컴포넌트들을 합쳐서 동작을 테스트한다. Unit Test는 각 컴포넌트를 고립시켜 테스트하기 때문에 컴포넌트의 interaction을 확인하는 Integration Test가 필요하다.

E2E Test : End to End Test. 실제 유저의 시나이로대로 네트워크를 통해 서버의 Endpoint를 호출해 테스트한다.

 

3. Unit Test 작성

'테스트 라이브러리를 사용하자' (실무에서 JUnit5 + mockito를 많이 사용한다.)

 

Test Double

  * 테스트에서 원본 객체를 대신하는 객체

 

Stub :   - 원래의 구현을 최대한 단순한 것으로 대체한다.  - 테스트를 위해 프로그래밍된 항목에만 응답한다.

 

Spy :

  - Stub의 역할을 하면서 호출에 대한 정보를 기록한다.

  - 이메일 서비스에서 메시지가 몇 번 정송되는지 확인할 때

 

Mock :

  - 행위를 검증하기 위해 가짜 객체를 만들어 테스트하는 방법

  - 호출에 대한 동작을 프로그래밍할 수 있다.

  - Stub은 상태를 검증하고 Mock은 행위를 검증한다.

 

'given, when, then 패턴을 사용하자'

  - given : 테스트를 위한 pre-condition

  - when : 테스트하고 싶은 동작 호출

  - then : 테스트 결과 확인

4. FIRST 원칙

Fast : 빠르게

  - 테스트는 빨리 돌아야 한다. 자주 돌려야 하기 때문이다.

Independent : 독립적으로

  - 각 테스트를 독립적으로 작성한다. 서로에게 의존하면 실패한 원인을 찾기 어려워진다.(다른 테스트의 샐패로 인한건지, 코드 오류인지)

Repeatable : 반복가능하게

  - 테스트는 어떤 환경에서도 반복 가능해야 한다. 실제 환경, QA 환경, 모든 환경에서 돌아가야 한다.

Self-Validating : 자가검증하는

  - 테스트는 bool 값으로 결과를 내야 한다.

Timely : 적시에

  - 테스트하려는 실제 코드를 구현하기 직전에 구현한다.

 

4. 오픈소스 속 Unit Test

Trino(PrestoSQL) 프로젝트 코드

  - 책에서는 하나의 테스트에 하나의 assert를 사용하라고 했다.

  - 테스트하려는 인자가 많은 경우는 하나에 몰아서 하는 경우도 많다.

 

public class TestTypeCalculation
{
    @Test
    public void testBasicUsage()
    {
        assertEquals(Long.valueOf(42), calculateLiteralValue("42", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("NULL", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("null", ImmutableMap.of()));
        assertEquals(Long.valueOf(42), calculateLiteralValue("x", ImmutableMap.of("x", 42L)));
        assertEquals(Long.valueOf(42), calculateLiteralValue("(42)", ImmutableMap.of()));
        assertEquals(Long.valueOf(0), calculateLiteralValue("(NULL)", ImmutableMap.of()));
        assertEquals(Long.valueOf(42), calculateLiteralValue("(x)", ImmutableMap.of("x", 42L)));

        assertEquals(Long.valueOf(42 + 55), calculateLiteralValue("42 + 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 - 55), calculateLiteralValue("42 - 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 * 55), calculateLiteralValue("42 * 55", ImmutableMap.of()));
        assertEquals(Long.valueOf(42 / 6), calculateLiteralValue("42 / 6", ImmutableMap.of()));

        assertEquals(Long.valueOf(42 + 55 * 6), calculateLiteralValue("42 + 55 * 6", ImmutableMap.of()));
        assertEquals(Long.valueOf((42 + 55) * 6), calculateLiteralValue("(42 + 55) * 6", ImmutableMap.of()));

        assertEquals(Long.valueOf(2), calculateLiteralValue("min(10,2)", ImmutableMap.of()));
        assertEquals(Long.valueOf(10), calculateLiteralValue("min(10,2*10)", ImmutableMap.of()));
        assertEquals(Long.valueOf(20), calculateLiteralValue("max(10,2*10)", ImmutableMap.of()));
        assertEquals(Long.valueOf(10), calculateLiteralValue("max(10,2)", ImmutableMap.of()));

        assertEquals(Long.valueOf(42 + 55), calculateLiteralValue("x + y", ImmutableMap.of("x", 42L, "y", 55L)));
    }
}

 

code from - https://github.com/trinodb/trino/blob/master/core/trino-parser/src/test/java/io/trino/type/TestTypeCalculation.java

 

Junit5 Samples

  - @DisplayName은 테스트 클래스나 메서드에 보여질 이름을 입력하는 것이다. 테스트의 목적을 명확하게 작정할수 있다.

  - @ParameterizedTest는 하나의 테스트 메서드로 여러 가지 paramter를 테스트할 수 있다. @CsvSource의 값을 parameter로 넘       긴다.

  - JUnit5가 테스트에 관한 유용한 기능을 많이 가지고 있기 때문에 실무에서 많이 사용한다.

 

class CalculatorTests {

	@Test
	@DisplayName("1 + 1 = 2")
	void addsTwoNumbers() {
		Calculator calculator = new Calculator();
		assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
	}

	@ParameterizedTest(name = "{0} + {1} = {2}")
	@CsvSource({
			"0,    1,   1",
			"1,    2,   3",
			"49,  51, 100",
			"1,  100, 101"
	})
	void add(int first, int second, int expectedResult) {
		Calculator calculator = new Calculator();
		assertEquals(expectedResult, calculator.add(first, second),
				() -> first + " + " + second + " should equal " + expectedResult);
	}
}

 

code from - https://github.com/junit-team/junit5-samples/blob/main/junit5-jupiter-starter-gradle/src/test/java/com/example/project/CalculatorTests.java

 

728x90
반응형

'Book > Clean Code' 카테고리의 다른 글

[Clean Code] Chapter 11  (0) 2022.03.27
[Clean Code] Chapter 10  (0) 2022.03.26
[Clean Code] Chapter 08  (0) 2022.03.20
[Clean Code] Chapter 07  (0) 2022.03.17
[Clean Code] Chapter 06  (0) 2022.03.13

+ Recent posts