λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Spring

JUnit - @ParameterizedTest, @ValueSource, @CsvSource, @MethodSource μ–΄λ…Έν…Œμ΄μ…˜

by 주발2 2021. 6. 8.
λ°˜μ‘ν˜•

 μ•ˆλ…•ν•˜μ„Έμš”~ 이전에 μš΄μ˜ν•˜λ˜ λΈ”λ‘œκ·Έ 및 GitHub, 곡뢀 λ‚΄μš©μ„ μ •λ¦¬ν•˜λŠ” Study-GitHub κ°€ μžˆμŠ΅λ‹ˆλ‹€!

 λ„€μ΄λ²„ λΈ”λ‘œκ·Έ

 GitHub

Study-GitHub

 πŸ”


πŸ“Ž JUnit5 - @ParameterizedTest, @ValueSource, @CsvSource, @MethodSource

 

μ•ˆλ…•ν•˜μ„Έμš”! 이번 μ‹œκ°„μ— 정리할 λ‚΄μš©μ€ JUnitμ—μ„œ νŒŒλΌλ―Έν„°μ˜ ν…ŒμŠ€νŠΈμ™€ κ΄€λ ¨λœ λ‹€μ–‘ν•œ ν…ŒμŠ€νŠΈ λ°©λ²•μž…λ‹ˆλ‹€.

 

μ΄λ²ˆμ— λ„₯μŠ€νŠΈμŠ€ν…μ—μ„œ μ§„ν–‰ν•˜λŠ” μžλ°” ν”Œλ ˆμ΄κ·ΈλΌμš΄λ“œ 과정을 λ“€μœΌλ©΄μ„œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜κ³  μžˆλŠ”λ°μš”,

이와 κ΄€λ ¨ν•΄ νŒŒλΌλ―Έν„°μ˜ 쀑볡 값에 λŒ€ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ œκ±°ν•˜κ±°λ‚˜, μž…λ ₯ 값에 따라 κ²°κ³Ό 값이 λ‹€λ₯Έ κ²½μš°λ„ ν…ŒμŠ€νŠΈκ°€ κ°€λŠ₯ν•˜λ„λ‘ κ΅¬ν˜„ν•˜λŠ” κ³Όμ •μ—μ„œ μœ„μ˜ μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜κ²Œ λ˜μ—ˆλŠ”λ°μš”, κ°„λž΅ν•˜κ²Œ 정리해보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

 

λ¨Όμ € ν•΄λ‹Ή 라이브러리λ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄ μ˜μ‘΄μ„±μ„ μΆ”κ°€ν•©λ‹ˆλ‹€.

 

Maven - pom.xml

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

 

 

Gradle - build.gradle

testCompile("org.junit.jupiter:junit-jupiter-params:5.7.0")

 

 

* 참고둜 ν˜„μž¬ μ œκ°€ μ‚¬μš©ν•˜κ³  μžˆλŠ” Gradle의 λ””νŽœλ˜μ‹œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter:5.7.2"
    testImplementation "org.assertj:assertj-core:3.19.0"
}

 

 

 

@ParameterizedTest, @ValueSource

그럼 @ParameterizedTest μ–΄λ…Έν…Œμ΄μ…˜μ„ μ–Έμ œ μ‚¬μš©ν•˜λŠ”μ§€ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

    private Set<Integer> numbers;

    @BeforeEach
    void setUp() {
        numbers = new HashSet<>();
        numbers.add(1);
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
    }

    @Test
    void set_contains_test() {
        assertThat(numbers.contains(1)).isTrue();
        assertThat(numbers.contains(2)).isTrue();
        assertThat(numbers.contains(3)).isTrue();
        ...
    }

μœ„ μ½”λ“œμ²˜λŸΌ Set μ»¬λ ‰μ…˜μ— μ—¬λŸ¬ 값이 ν™•μΈλ˜μ–΄μžˆλŠ”μ§€ ν™•μΈν•˜λ €λ©΄ μ½”λ“œλ₯Ό μ—¬λŸ¬λ²ˆ μž‘μ„±ν•˜λŠ” 쀑볡 μ½”λ“œκ°€ λ°œμƒν•˜κ²Œ λ©λ‹ˆλ‹€.

 

λ”°λΌμ„œ, μœ„μ™€ 같이 쀑볡 μ½”λ“œκ°€ λ°œμƒν–ˆμ„ λ•Œ @ParameterizedTest μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•  수 μžˆλŠ”λ°μš”,

μœ„ μ½”λ“œλ₯Ό λ‹€μŒκ³Ό 같이 λ‚˜νƒ€λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3})
    void set_contains_test(int element) {
        assertThat(numbers.contains(element)).isTrue();
    }

ν…ŒμŠ€νŠΈ κ²°κ³Όλ₯Ό μ‚΄νŽ΄λ³΄λ©΄ @ValueSource μ–΄λ…Έν…Œμ΄μ…˜μ— μ„ μ–Έλœ 1, 2, 3 의 값을 ν…ŒμŠ€νŠΈν•˜λŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

 

@ValueSourceλŠ” λ¦¬ν„°λŸ΄ κ°’μ˜ 배열에 λŒ€ν•œ 접근을 μ œκ³΅ν•˜λŠ” μ–΄λ…Έν…Œμ΄μ…˜ μž…λ‹ˆλ‹€.

μžμ„Έν•œ λ‚΄μš©μ€ κ³΅μ‹λ¬Έμ„œ μ—μ„œ ν™•μΈν•΄μ£Όμ„Έμš” :)

 

@ValueSource의 λ‹€λ₯Έ 예제둜 λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

public class Numbers {
    public static boolean isOdd(int number) {
        return number % 2 != 0;
    }
}


@ParameterizedTest
@ValueSource(ints = {1, 5, 9, -7, 15, 21}) // six numbers
void isOdd_ShouldReturnTrueForOddNumbers(int number) {
    assertThat(Numbers.isOdd(number)).isTrue();
}

 

 

@ValueSource의 μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

/*
 * Copyright 2015-2020 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.params.provider;

import static org.apiguardian.api.API.Status.STABLE;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
 * {@code @ValueSource} is an {@link ArgumentsSource} which provides access to
 * an array of literal values.
 *
 * <p>Supported types include {@link #shorts}, {@link #bytes}, {@link #ints},
 * {@link #longs}, {@link #floats}, {@link #doubles}, {@link #chars},
 * {@link #booleans}, {@link #strings}, and {@link #classes}. Note, however,
 * that only one of the supported types may be specified per
 * {@code @ValueSource} declaration.
 *
 * <p>The supplied literal values will be provided as arguments to the
 * annotated {@code @ParameterizedTest} method.
 *
 * @since 5.0
 * @see org.junit.jupiter.params.provider.ArgumentsSource
 * @see org.junit.jupiter.params.ParameterizedTest
 */
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.7")
@ArgumentsSource(ValueArgumentsProvider.class)
public @interface ValueSource {

	/**
	 * The {@code short} values to use as sources of arguments; must not be empty.
	 *
	 * @since 5.1
	 */
	short[] shorts() default {};

	/**
	 * The {@code byte} values to use as sources of arguments; must not be empty.
	 *
	 * @since 5.1
	 */
	byte[] bytes() default {};

	/**
	 * The {@code int} values to use as sources of arguments; must not be empty.
	 */
	int[] ints() default {};

	/**
	 * The {@code long} values to use as sources of arguments; must not be empty.
	 */
	long[] longs() default {};

	/**
	 * The {@code float} values to use as sources of arguments; must not be empty.
	 *
	 * @since 5.1
	 */
	float[] floats() default {};

	/**
	 * The {@code double} values to use as sources of arguments; must not be empty.
	 */
	double[] doubles() default {};

	/**
	 * The {@code char} values to use as sources of arguments; must not be empty.
	 *
	 * @since 5.1
	 */
	char[] chars() default {};

	/**
	 * The {@code boolean} values to use as sources of arguments; must not be empty.
	 *
	 * @since 5.5
	 */
	boolean[] booleans() default {};

	/**
	 * The {@link String} values to use as sources of arguments; must not be empty.
	 */
	String[] strings() default {};

	/**
	 * The {@link Class} values to use as sources of arguments; must not be empty.
	 *
	 * @since 5.1
	 */
	Class<?>[] classes() default {};

}

 

 

 

@ParameterizedTest, @CsvSource

μœ„μ˜ @ValueSource μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•œ set ν…ŒμŠ€νŠΈμ—μ„œ λͺ¨λ“  쑰건은 항상 trueλ₯Ό λ¦¬ν„΄ν•˜λ„λ‘ λ˜μ–΄μžˆμ—ˆλŠ”λ°μš”,

λ§Œμ•½ μœ„μ™€κ°™μ€ 상황이 μ•„λ‹Œ, μž…λ ₯값에 따라 결과값이 λ‹€λ₯Έ 경우λ₯Ό ν…ŒμŠ€νŠΈ ν•˜λ €λ©΄ @CsvSourceλ₯Ό μ‚¬μš©ν•΄ ν…ŒμŠ€νŠΈλ₯Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

CSV(Comma Separated Values)λŠ” μ΄λ¦„을 톡해 μ•Œ 수 μžˆλ“―μ΄, 콀마(default)λ₯Ό κΈ°μ€€μœΌλ‘œ CSVλ₯Ό κ΅¬λΆ„ν•΄μ„œ μ½μŠ΅λ‹ˆλ‹€.

  β€» κ΅¬λΆ„μžλŠ” μ»€μŠ€ν„°λ§ˆμ΄μ§•μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

@CsvSource Annotation

 

 

ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

    @DisplayName("true, false 값을 같이 λ¦¬ν„΄ν•œλ‹€")
    @ParameterizedTest
    @CsvSource(value = {"1:true", "2:true", "3:true", "4:false", "5:false"}, delimiter = ':')
    void set_contains_true_false(int element, boolean expected) {
        assertThat(numbers.contains(element)).isEqualTo(expected);
    }

@CsvSource μ–΄λ…Έν…Œμ΄μ…˜μ— delimiter 을 직접 μ •μ˜ν•¨μœΌλ‘œμ¨ κ΅¬λΆ„μžλ₯Ό 지정할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

μœ„ μ½”λ“œμ—μ„œ set은 1, 2, 3 을 가지고 있기 λ•Œλ¬Έμ— trueλ₯Ό, 4와 5λŠ” falseλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

 

 

 

 

@ParameterizedTest, @MethodSource

@MethodSource μ–΄λ…Έν…Œμ΄μ…˜μ€ λ‹€μŒκ³Ό 같은 μƒν™©μ—μ„œ μ‚¬μš©μ„ ν–ˆμŠ΅λ‹ˆλ‹€.

 

μˆ«μžμ•Όκ΅¬κ²Œμž„μ— λŒ€ν•΄ μ§ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜λ˜ 도쀑, 슀트라이크 & 볼에 λŒ€ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±μ„ ν•΄μ•Όν–ˆλŠ”λ°μš”, 

두 ν…ŒμŠ€νŠΈ λͺ¨λ‘ ν”Œλ ˆμ΄μ–΄, μ»΄ν“¨ν„°μ˜ 숫자λ₯Ό 가지고 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

 

λ”°λΌμ„œ νŒŒλΌλ―Έν„°λ₯Ό ν…ŒμŠ€νŠΈλ₯Ό ν•˜λŠ”λ° λ”°λ‘œ κ΅¬ν˜„μ„ ν•˜κ²Œ 되면 쀑볡 μ½”λ“œκ°€ λ°œμƒν•΄μ„œ @MethodSource μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 μ—¬λŸ¬ 값듀에 λŒ€ν•΄ λ™μΌν•˜κ²Œ μ‚¬μš©ν•  수 μžˆλ„λ‘ μž‘μ„±μ„ ν–ˆμŠ΅λ‹ˆλ‹€.

 

 

@MethodSource 의 μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.7")
@ArgumentsSource(MethodArgumentsProvider.class)
public @interface MethodSource {

	/**
	 * The names of factory methods within the test class or in external classes
	 * to use as sources for arguments.
	 *
	 * <p>Factory methods in external classes must be referenced by <em>fully
	 * qualified method name</em> &mdash; for example,
	 * {@code com.example.StringsProviders#blankStrings}.
	 *
	 * <p>If no factory method names are declared, a method within the test class
	 * that has the same name as the test method will be used as the factory
	 * method by default.
	 *
	 * <p>For further information, see the {@linkplain MethodSource class-level Javadoc}.
	 */
	String[] value() default "";

}

 

 

 

ν…ŒμŠ€νŠΈμ— μ‚¬μš©ν•œ μ‹€μ œ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

...

    @DisplayName("슀트라이크 ν…ŒμŠ€νŠΈ")
    @ParameterizedTest
    @MethodSource("generateData")
    public void test1(List<Integer> playerLists, List<Integer> computerLists, int index) {
        PlayResult playResult = new PlayResult();
        int strikeCount = playResult.countStrike(playerLists, computerLists);

        int[] result = {1, 0};
        assertThat(strikeCount).isEqualTo(result[index]);
    }

    @DisplayName("λ³Ό ν…ŒμŠ€νŠΈ")
    @ParameterizedTest
    @MethodSource("generateData")
    void test_ball(List<Integer> playerLists, List<Integer> computerLists, int index) {
        PlayResult playResult = new PlayResult();
        int ballCount = playResult.countBall(playerLists, computerLists);

        int[] result = {2, 0};
        assertThat(ballCount).isEqualTo(result[index]);
    }

    static Stream<Arguments> generateData() {
        return Stream.of(
                Arguments.of(Arrays.asList(1, 2, 3), Arrays.asList(1, 3, 2), 0),
                Arguments.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), 1)
        );
    }

 

generateData() λ©”μ„œλ“œλ₯Ό 톡해 ν…ŒμŠ€νŠΈν•  νŒŒλΌλ―Έν„°λ₯Ό Stream<Arguments>둜 μƒμ„±ν•©λ‹ˆλ‹€.

κ·Έ ν›„ @MethodSource μ–΄λ…Έν…Œμ΄μ…˜μ˜ κ΄„ν˜Έμ— ν•΄λ‹Ή λ©”μ„œλ“œ 이름을 μž‘μ„±ν•˜λ©΄ νŒŒλΌλ―Έν„° ν…ŒμŠ€νŠΈκ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€.


@MethodSource μ–΄λ…Έν…Œμ΄μ…˜μ΄ μž‘λ™ν•˜λŠ” ꡬ쑰λ₯Ό λŒ€λž΅μ μœΌλ‘œ λ‚˜νƒ€λ‚΄λ©΄ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

 

 

μ΄μƒμœΌλ‘œ JUnit의 νŒŒλΌλ―Έν„° ν…ŒμŠ€νŠΈμ™€ κ΄€λ ¨λœ @ParameterizedTest, @ValueSource, @CsvSource, @MethodSource μ–΄λ…Έν…Œμ΄μ…˜μ— λŒ€ν•΄ κ°„λž΅νžˆ μ‚΄νŽ΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

 

ν˜Ήμ‹œ μœ„ μ–΄λ…Έν…Œμ΄μ…˜ 말고도 AssertJ에 λŒ€ν•΄ λ‹€μ–‘ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄μ‹œλ €λ©΄ μ•„λž˜ 링크λ₯Ό μ°Έμ‘°ν•΄μ£Όμ„Έμš” :)

https://www.baeldung.com/introduction-to-assertj

 

 

배우면 배울수둝 κ³΅λΆ€ν• κ²Œ 더 λ§Žμ•„μ§€λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€ 😭

μœ„μ™€ 같은 νŒŒλΌλ―Έν„° ν…ŒμŠ€νŠΈλŠ” λ‹€μ–‘ν•œ νŒŒλΌλ―Έν„°λ₯Ό ν…ŒμŠ€νŠΈν•΄μ•Ό ν•˜λŠ” 경우, μ€‘λ³΅λ˜λŠ” μ½”λ“œλ₯Ό 쀄일 수 μžˆλ‹€λŠ” μ μ—μ„œ μœ μš©ν•œ 방법인 것 κ°™μŠ΅λ‹ˆλ‹€

 

 

 

 

 

References

https://www.baeldung.com/parameterized-tests-junit-5

 

 

λ°˜μ‘ν˜•

λŒ“κΈ€