ํ ์คํธ ๋ฐ์ดํฐ๋๊ตฌ - Fixture Monkey
by JiwonDev
์์ง ์ถ์๋์ง ์ผ๋ง ์๋์ด ์ฌ์ฉ์ ์ถ์ฒํ๋ ๋จ๊ณ๋ ์๋์ง๋ง, ์์๋๋ฉด ์ข๊ธฐ์ ์ ๋ฆฌํด๋ก๋๋ค.
Fixture Monkey๋ฅผ ๋ง๋ ๋ค์ด๋ฒ ํ์ด ํ์์๋ ์ ์ฌ์ฉํ๊ณ ์๋ค๊ณ ํฉ๋๋ค.
๋ค์ด๋ฒํ์ด ๊ฐํธ TF๋ฅผ ํจ๊ปํ๊ณ ์๋ Platform Labs์์ ๋ง๋ ๋๊ตฌ์ด๊ณ , ์ฌ๋ด์์ ์ค์ ๋ก ์ฌ์ฉ์ค์ด๋ผ๊ณ ํฉ๋๋ค.
์์์ ๋ณด๋ ์์ง ์ถ์๋์ง์์ ์๋ก์ด ๊ธฐ๋ฅ์ด ๊ต์ฅํ ํ๋๋๋ฐ, ๋นจ๋ฆฌ ์์ ํ ๋์์ผ๋ฉด ์ข๊ฒ ๋ค์ ๐
๋ ํผ๋ฐ์ค : https://tv.naver.com/v/23650158
๐ญ PBT, ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ๋ ๋๊ตฌ
ํ ์คํธ๋ฅผ ์์ฑํ๋ฉด์ ์ ์ผ ๋ฒ๊ฑฐ๋ก์ด ๊ฒ์ด ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ๋ง๋๋ ๊ฒ์ด๋ค.
์ฑ๊ณตํ๋ ํ ์คํธ ๋ฐ์ดํฐ๋ ๋ง๋ค๊ธฐ ์ฝ์ง๋ง, ์คํจํ๋ ๊ฑด ๋ง๋ค๊ธฐ๋ ๊ท์ฐฎ๊ณ ๋๋ฝ๋ ํ๋ฅ ๋ ๋๋ค.
์ฌ์ง์ด๋ ์๋์ ๊ฐ์ด ์์ฑ์๋ ๋ด๋ถ ํ๋๋ฅผ ๋ณ๊ฒฝํ ๊ฒฝ์ฐ์๋ ๋ชจ๋ ํ ์คํธ ๋ฐ์ดํฐ๊ฐ ์ปดํ์ผ์ด ๋ถ๊ฐ๋ฅํ๊ฒ ๋ฐ๋๋ค.
๊ทธ๋ฅ ๊ฐ์ฒด์ ํ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์์ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ ์๋ ์์๊น?
โก ์ด๊ฒ ๋ฐ๋ก PBT (Property Based Testing) ๋๊ตฌ์ด๋ค.
๋ค๋ฅธ์ธ์ด(C#)์์๋ ์ ๋ช ํ PBT ๋๊ตฌ๋ค์ด ๋ง์ง๋ง, ์๋ฐ์์๋ ์์ง ๋์คํ๋ ๋๊ตฌ๊ฐ ๋ง์ด ์๋ค. ๊ทธ๋๋ง ์ธ๋งํ ์คํ์์ค๊ฐ AutoParms, Fixture Monkey ์ ๋์ธ๋ฐ, ๋ค์ด๋ฒํ์ด์์ ๊ฐ๋ฐํ๋ค๋ Fixture Monkey์ ๋ํด์ ์์๋ณด์.
๐ญ Fixture Monkey
Fixture Monkey๋ ๋ค์ด๋ฒํ์ด์์ ๋ง๋ PBT ์คํ์์ค ๋๊ตฌ์ด๋ค. ์๋ฐ์ PBT ๋๊ตฌ์ธ Jqwik ์ ๊ธฐ๋ฐ์ผ๋ก ๋ง๋ค์ด์ก๋ค.
Gradle์ด๋ Maven์ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด, ์๋์ ๊ฐ์ด ๊ฐ๋จํ๊ฒ ๋ฐ๋ก ์ถ๊ฐํ ์ ์๋ค.
๐งจ Java8+, Junit5, jqwik 1.3.9 ๋ฅผ ์ฌ์ฉํ๋ค.
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter:0.3.2")
<dependency>
<groupId>com.navercorp.fixturemonkey</groupId>
<artifactId>fixture-monkey-starter</artifactId>
<version>0.3.2</version>
<scope>test</scope>
</dependency>
๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฐ/Junit5๋ฅผ ์ง์ํ๋ฉฐ ํ์์ ๋ฐ๋ผ ์๋์ ๊ฐ์ ์๋ํํฐ ๋ชจ๋๋ค์ ์ฌ์ฉํ ์ ์๋ค.
- fixture-monkey-jackson (๊ณต์): ํ ์คํธ์์ jackson์ ํตํ ๊ฐ์ฒด ์ง๋ ฌํ/์ญ์ง๋ ฌํ๋ฅผ ์ง์ํฉ๋๋ค.
- fixture-monkey-kotlin (๊ณต์): ์ฝํ๋ฆฐ ์ง์
- fixture-monkey-autoparams (๋ฒ ํ ๋ฒ์ ): ๋ค๋ฅธ PBT ์คํ์์ค์ธ AutoParams ๊ณผ์ ์ฐ๋์ ์ง์ํฉ๋๋ค.
- fixture-monkey-mockito (๋ฒ ํ ๋ฒ์ ): ๋ชจํน ๋๊ตฌ์ธ mockito์ ๋ชฉ ๊ฐ์ฒด ์์ฑ/์ธํฐํ์ด์ค๋ฅผ ์ง์ํฉ๋๋ค.
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-kotlin:0.3.2")
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-jackson:0.3.2")
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-autoparams:0.3.2")
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-mockito:0.3.2")
Jqwik์ด ๋ญ์ฃ ? ์ด๊ฒ ๊ธฐ๋ฅ์ด ์ข๋ค๋ฉด ๊ทธ๋ฅ ๋ฐ๋ก ์ฐ๋ฉด ๋์์์?
Jqwik์ ๊ธฐ๋ฅ์ ๋ค์ํ์ง๋ง, ์ค์ ์๋น์ค์ ์ ์ฉํ๊ธฐ์ Jqwik์ ๋ฐ๋ก ๊ณต๋ถํด์ผ๋ ์ ๋๋ก ์ฝ๋ ์์ฑ์ด ๋ฒ๊ฑฐ๋ก์ ๋ค.
๋ํ ํ ์คํธ์์ ํ์ํ ๋ฌด์์ ๊ฐ์ ๋ง๋ค๊ธฐ๋ ์ด๋ ค์ ๊ณ , List[List[String]] ๊ฐ์ ์ ๋ค๋ฆญ ์ปฌ๋ ์ ์ ์ง์ํด์ฃผ์ง ์์๋ค.
@RequiredArgsConstructor
class Person { // ํ
์คํธ ๋์ ๊ฐ์ฒด
private final String name;
private final int age;
}
@Property // ํ
์คํธ ์ฝ๋
void validPeopleHaveIDs(@ForAll("validPeople") Person aPerson) {
Assertions.assertThat(aPerson.getID()).contains("-");
Assertions.assertThat(aPerson.getID().length()).isBetween(5, 24);
}
@Provide // ํ
์คํธ์ ํ์ํ ์์ฑ ์ ์
Arbitrary<Person> validPeople() {
Arbitrary<String> names = Arbitraries.strings().withCharRange('a', 'z')
.ofMinLength(3).ofMaxLength(21);
Arbitrary<Integer> ages = Arbitraries.integers().between(0, 130);
return Combinators.combine(names, ages)
.as((name, age) -> new Person(name, age));
}
์ด๋ป๊ฒ ์ฌ์ฉํ๋์?
Fixture Monkey๋ ์๋ฐ์ ํ์ค Bean Validation 2.0 ์ด๋ ธํ ์ด์ (JSR-303, JSR-380)์ ์ฌ์ฉํ๋ค.
์ฝ๊ฒ ๋งํด, ์๋์ ๊ฐ์ด ํ์ค ์ด๋ ธํ ์ด์ ์ ํตํด ํ ์คํธ์ฉ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค๋ ๋ง. ์ ์ฉ ์ด๋ ธํ ์ด์ ์ด ํ์ํ์ง ์๋ค.
@Test
void test() {
// FixtureMonkey ๋น๋๋ฅผ ์์ฑํ๋ค.
FixtureMonkey sut = FixtureMonkey.create();
// ๋น๋๋ฅผ ์ด์ฉํ์ฌ ํ
์คํธ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
Order actual = sut.giveMeOne(Order.class);
then(actual.getId()).isNotNull(); // @NotNull
then(actual.getOrderNo()).isNotBlank(); // @NotBlank
then(actual.getProductName().length()).isBetween(2, 10); // @Size(min = 2, max = 10)
then(actual.getQuantity()).isBetween(1, 100); // @Min(1) @Max(100)
then(actual.getPrice()).isGreaterThanOrEqualTo(0); // @Min(0)
then(actual.getItems()).hasSizeLessThan(3); // @Size(max = 3)
then(actual.getItems()).allMatch(it -> it.length() <= 10); // @NotBlank @Size(max = 10)
then(actual.getOrderedAt()).isBeforeOrEqualTo(Instant.now()); // @PastOrPresent
}
์ด ํ ์คํธ์ ์ฌ์ฉ๋ Person ๊ฐ์ฒด์ฝ๋๋ ์๋์ ๊ฐ๋ค. (Fixture Monkey์ ๊ด๋ จ๋ ์์กด์ฑ์ด๋ ์ฝ๋๊ฐ ํ๋๋ ์๋ค!)
@Data // lombok getter, setter
public class Order {
@NotNull
private Long id;
@NotBlank
private String orderNo;
@Size(min = 2, max = 10)
private String productName;
@Min(1)
@Max(100)
private int quantity;
@Min(0)
private long price;
@Size(max = 3)
private List<@NotBlank @Size(max = 10) String> items = new ArrayList<>();
@PastOrPresent
private Instant orderedAt;
}
๐ญ ์์ธํ ์ฌ์ฉ๋ฒ
์ฐธ๊ณ ๋ก Arbitray ๋ผ๋ ์ฉ์ด๊ฐ ๋ง์ด ์ฌ์ฉ๋๋๋ฐ, ์ด๋ ๋๋ค์, ์์์, ๋ฉ๋๋ก์ ๋ผ๋ ๋ป์ ๊ฐ์ง๊ณ ์๋ค.
๐ FixtureMonkey ์์ฑํ๊ธฐ
- FixtureMonkey.create()๋ฅผ ์ฌ์ฉํด์ ์์ฝ๊ฒ ๋ฐ์ดํฐ ๋น๋๋ฅผ ์์ฑํ ์ ์๋ค.
๊ธฐ๋ณธ ์์ฑ ์ ๋ต์ BeanArbiyraryGenerator ๋ก ์๋ฐ๋น์ฆ ๊ท์น์ ๋ฐ๋ผ Getter, Setter๋ฅผ ์ด์ฉํด์ ์์ฑํ๋ค.
์ฆ, ๋ฐ๋ก ์ค์ ์ ํ์ง ์๋๋ค๋ฉด Getter Setter๊ฐ ์์ ๋ ์์ธ๊ฐ ๋ฐ์ํ๋ฏ๋ก, ์์ฑ ์ ๋ต์ ๋ฐ๊ฟ์ฃผ์ด์ผํ๋ค.
FixtureMonkey sut = FixtureMonkey.create();
Order actual = sut.giveMeOne(Order.class);
List<Order> orders = sut.giveMe(Order.class, 5); // 5๊ฐ
ํน๋ณํ ์ค์ ์ด ํ์ํ๋ค๋ฉด giveMeBuilder๋ฅผ ํตํด ๋น๋ ๊ฐ์ฒด๋ก ์ปค์คํ ํ ์ ์๋ค. (AribitrayBuilder๊ฐ ๋ฐํ๋๋ค.)
Order actual = sut.giveMeBuilder(Order.class)
.generator(JacksonArbitraryGenerator.INSTANCE) // ์์ฑ ์ ๋ต ์ง์
.set("name", "factory")
.set("quantity", Arbitraries.integers().between(5, 10))
.sample(); // Order๋ฅผ ๋ณต์ฌํด์ ์์ฑํ๋ค. ์๋ณธ์ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
์ค์ ์ ํ ์คํธ ๋ฉ์๋์์ ๋งค๋ฒ ํ ์๋ ์์ง๋ง, FixtureMonkey๋ฅผ ํ๋(private final)๋ก ๋ฝ์์ ์ฌ์ฌ์ฉํ๋ ๊ฑธ ๊ถ์ฅํ๋ค.
private final FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultGenerator(JacksonArbitraryGenerator.INSTANCE) // Jackson ๊ท์น ์ฌ์ฉ
.build();
๐ ๊ฐ์ฒด ์์ฑ ์ ๋ต (ArbitrayGenerator)
์ฐธ๊ณ ๋ก ๊ฐ๋ฐ์๊ฐ ์ง์ ์ปค์คํ ์์ฑ ์ ๋ต์ ๋ง๋ค ์ ์๋ค. ๋ฌผ๋ก ์ง์ ๋ง๋ค์ผ์ ๊ฑฐ์ ์๊ธด ํ์ง๋ง.
BeanArbitraryGenerator(๊ธฐ๋ณธ)
์๋ฐ๋น์ฆ ๊ท์น, ๋งค๊ฐ๋ณ์๊ฐ ์๋ ๊ธฐ๋ณธ ์์ฑ์์ Setter๋ฅผ ์ฌ์ฉํ๋ค.
@Setter
@NoArgsConstructor
public class Person {
private Long id;
private String name;
}
๋ง์ฝ ์์ํ ์๋ฐ์ฝ๋์ธ๋ฐ, ์์ฑ์ ์ฃผ์ ์ ํ๊ณ ์ถ๋ค๋ฉด ConstructorPropertiesArbitraryGenerator ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
์ด๋ Java Bean์ ํ์ค @ConstructorProperties ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํด์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ด๋ค.
public class Product{
private Long id;
private String name;
@ConstructorProperties({"id", "name"})
public Person(Long id, String name) {
this.id = id;
this.name = name;
}
}
BuilderArbitraryGenerator (Lombok @Builder)
Lombok ์ @Builder๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ๋ค.
์์ ์ฒ๋ผ ๋น๋ ์ด๋ฆ์ ๋ฐ๋ก ์ง์ ํ ๊ฒฝ์ฐ, BuilderArbitraryGenerator.setbuilderMethodName ๋ฑ์ ์ง์ ํด์ฃผ์ด์ผํ๋ค.
@Builder
public class Person {
private Long id;
private String name;
}
@Builder(builderMethodName = "builder2", buildMethodName = "build2")
public class Order {
private Long id;
private String name;
}
// ๋น๋ ์ด๋ฆ ์ง์ ํ๋ ๋ฐฉ๋ฒ
var generator = BuilderArbitraryGenerator.INSTANCE;
generator.setBuilderMethodName(Order.class, "myBuilderName");
generator.setDefaultBuilderMethodName("myDefaultBuilderName");
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.defaultGenerator(generator)
.build();
FieldReflectionArbitraryGenerator (๋ฆฌํ๋์ )
๋ฆฌํ๋์ ์ ํตํด ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ์คํ๋ง ํ๋ก์๋ JPA Entity ์ ๋น์ทํ๋ค.
ํ๋๊ฐ ์๋ public ๊ธฐ๋ณธ์์ฑ์๊ฐ ํ์ํ๋ฉฐ, ํ๋ ์ ๊ทผ์ ์ด์๋ ์๊ด์์ง๋ง final, transient ๊ฐ ์ ์ธ๋ ๊ฑด ๋ง๋ค ์ ์๋ค.
@Entity
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@AllArgsConstructor
public class Person {
@Id
private Long id;
private String name;
}
JacksonArbitraryGenerator (FixtureMonkey.Jaskson ๋ชจ๋ ํ์)
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-jackson:0.3.0")
Jackson์ ์ด์ฉํด์ ๊ฐ์ฒด๋ฅผ ์์ฑํด์ค๋ค. ํด๋น ๋น๋๋ฅผ ์ฌ์ฉํ๋ฉด Jackson ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐ๋ก ์ง์ํ๋ค.
@Data // lombok getter, setter
public class Order {
@NotNull
private Long id;
@JsonProperty("name")
private String productName;
private int quantity;
@JsonIgnore
private String sample;
}
@Test
void test() {
// given
FixtureMonkey sut = FixtureMonkey.create();
// when
Order actual = sut.giveMeBuilder(Order.class)
.generator(JacksonArbitraryGenerator.INSTANCE)
.set("name", "factory")
.set("quantity", Arbitraries.integers().between(5, 10)) // ์ด๋ ๋ค์์ ์ค๋ช
ํ๋ค
.sample();
// then
then(actual.getId()).isNotNull(); // @NotNull
then(actual.getProductName()).isEqualTo("factory");
then(actual.getQuantity()).isBetween(5, 10);
then(actual.getSample()).isNull(); // @JsonIgnore
}
๐ ArbitrayCustomizer - ๋๋ค์์ผ๋ก ๊ฐ์ฒด ๋ฐ์ดํฐ ์์ฑํ๊ธฐ
FixtureCustomizer<Person> personAgeFixedCustomizer = person -> {
person.setAge(20);
return person;
}; // ๋๋ค์์ผ๋ก ์ปค์คํฐ๋ง์ด์ ์ ์
Person person20Age = fixture.giveMeBuilder(Person.class)
.customize(p -> { // ์ด๋ ๊ฒ Customizer ์ต๋ช
๊ฐ์ฒด(๋๋ค)๋ฅผ ๋ฐ๋ก ์ ์ํด์ ์ฌ์ฉํ ์ ์๋ค.
p.setAge(20);
return p;
})
.sample();
์ถ์ฒํ๋ ๋ฐฉ๋ฒ์ ์๋์ง๋ง, ๋๋ค์์ ์์ฐ๊ณ ์ง์ AbitraryCustomizer๋ฅผ ์์ํ์ฌ ๊ตฌํํ๋ ๋ฐฉ๋ฒ๋ ์์ต๋๋ค.
class PersonArbitraryCustomizer implements ArbitraryCustomizer<Person> {
@Override
public void customizeFields(Class<Person> type, FieldArbitraries fieldArbitraries) {
fieldArbitraries.removeArbitrary("id");
fieldArbitraries.replaceArbitrary("name", Arbitraries.strings().ofMaxLength(30));
....
}
@Nullable
@Override
public Person customizeFixture(@Nullable Person person) {
person.setAge(20);
return person;
}
}
FixtureMonkey fixture = FixtureMonkey.builder().build();
Person person20Age = fixture.giveMeBuilder(Person.class)
.customize(p -> {
p.setAge(20);
return p;
})
.sample();
// ์ด๋ ๊ฒ ์ ๋ฌํด๋ ๋๊ฐ์ด ๋์ํ๋ค.
Person person = fixture.giveMeBuilder(Person.class)
.customize(new PersonFixtureCustomizer())
.sample();
List<Person> persons = fixture.giveMeBuilder(Person.class)
.customize(new PersonFixtureCustomizer())
.sampleList(3);
๐ ๋ค์ํ ํ๋ ์์ฑ (giveMeOne, giveMeBuilder)
์์๋ ์ธ๊ธํ์ง๋ง, ์์ ์ฒ๋ผ ๋งค๋ฒ FixtureMonkey๋ฅผ ๋ง๋ค๊ธฐ๋ณด๋จ private final ํ๋๋ก ๋ฝ์์ ์ฐ๋๊ฑธ ๊ถ์ฅํ๋ค.
ํจ์๋ฅผ ์ฒด์ธํด์ ์ค์ ์ ํ๊ณ ์ถ๋ค๋ฉด, giveMeBuilder() ๋ฉ์๋๋ฅผ ํตํด ArbitraryBuilder๋ฅผ ๋ฐ์์ ์ฌ์ฉํ๋ฉด ๋๋ค.
// ์์ ๊ฐ์ฒด ์์ฑ
Sample sample = FixtureMonkey.create().giveMeOne(Sample.class);
// ์์ ๊ฐ์ฒด ์์ฑ
Sample sample = FixtureMonkey.create().giveMeBuilder(Sample.class).sample();
// ์์ ๊ฐ์ฒด ์์ฑ n๊ฐ ์์ฑ
Sample sample = FixtureMonkey.create().giveMe(Sample.class, n);
// ์์ ๊ฐ์ฒด ์์ฑ n๊ฐ ์์ฑ
Sample sample = FixtureMonkey.create().giveMeBuilder(Sample.class).sampleList(n);
// ํน์ ์กฐ๊ฑด ๋๋ ๊ฐ์ ๊ฐ์ง ์์ ๊ฐ์ฒด ์์ฑ
Sample sample = FixtureMonkey.create().giveMeBuilder(Sample.class)
.set("name", "test")
.setNull("id")
.sample();
// ์์ Integer ์์ฑ
Integer listCount = FixtureMonkey.create().giveMeOne(Integer.class);
// 1~50 ์ฌ์ด ์์ Interger ์์ฑ
// .set("$", Arbitraries.integers().between(1, 50)) ์ด ์ฝ๋์ ๊ฐ์ ๋์์ด๋ค.
Integer listCount =
FixtureMonkey.create()
.giveMeBuilder(Integer.class)
.set(Arbitraries.integers().between(1, 50))
.sample();
// ์์ String ์์ฑ
String parameter = FixtureMonkey.create().giveMeOne(String.class);
// min=1 ์ ์์ String ์์ฑ
// .set("$", Arbitraries.strings().ofMinLength(1)) ์ ๊ฐ์ ๋์์ด๋ค.
String parameter =
FixtureMonkey.create()
.giveMeBuilder(String.class)
.set(Arbitraries.strings().ofMinLength(1))
.sample();
๐ Mockito์ ์ฐ๋ (๋ฒ ํ, ์คํ์ ๊ธฐ๋ฅ)
ํ ์คํธ ๋ฐ์ดํฐ์ ์ธํฐํ์ด์ค์ ์ถ์ํด๋์ค๊ฐ ์๋ ๊ฒฝ์ฐ, ์๋์ผ๋ก Mockito์ MockBean์ ์ฃผ์ ์์ผ์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค.
์๋ ๋ชจ๋์ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค.
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-mockito:0.3.0")
์๋์ ๊ฐ์ด Item ์์กด์ฑ์ ์ฃผ์ ํด์ค์ผํ๋ ๊ฒฝ์ฐ, ํด๋น ๊ธฐ๋ฅ์ ์ด์ฉํด Mockito์ MockBean์ ์ฃผ์ ํด์ค ์ ์๋ค.
public interface Item {
String getName();
}
@Data // lombok getter, setter
public class Order {
@NotNull
private Long id;
private String productName;
private int quantity;
@NotNull
private Item item; // ์ธํฐํ์ด์ค. ์ฃผ์
์ด ํ์ํ๋ค.
}
์ฌ์ฉ๋ฒ์ ๊ฐ๋จํ๋ค. InterfaceSupplier๋ฅผ ์ถ๊ฐํด์ฃผ๊ณ , Mockito๋ก ๊ฒ์ฆํ๋ฉด ๋๋ค.
@Test
void test() {
// given
FixtureMonkey sut = FixtureMonkey.builder()
.defaultInterfaceSupplier(MockitoInterfaceSupplier.INSTANCE)
.build();
// when
Order actual = sut.giveMeOne(Order.class);
// then
then(actual.getItem()).isNotNull();
when(actual.getItem().getName()).thenReturn("ring");
then(actual.getItem().getName()).isEqualTo("ring");
}
๐ ์ค์ ์์
Builder์ ์ ๋ฌํ ๊ฐ์ฒด๋ ๊ทธ๋๋ก ์ฌ์ฉ๋์ง์๊ณ ๋ณต์ฌํด์ ์๋กญ๊ฒ ์์ฑํ๋ค.
์ฆ ์๋์ ๊ฐ์ด ํ๋๋ก ๋ฝ์๋๊ณ , ๋ค๋ฅธ ํ ์คํธ์ ์ฌ์ฌ์ฉํ ์ ์๋ค.
// ์ด๊ฒ๋ ๋งค๋ฒ ๋ง๋ค ํ์์์ด, ํ๋๋ก ๋ฝ์๋๋ฉด ๋ ๊น๋ํ๊ฒ ์ฌ์ฉ ๊ฐ๋ฅ
private final FixtureMonkey fixture = FixtureMonkey.builder()
.defaultGenerator(JacksonArbitraryGenerator.INSTANCE)
.build();
private final Order orderFixture = new Order(
1L,
"LINE FRIENDS",
1000L,
List.of(
new Product("SALLY", 300L),
new Product("BROWN", 300L)
),
List.of(new Coupon(100L)),
new Delivery("๊ทธ๋ฆฐํฉํ ๋ฆฌ", null, true, 500L),
Instant.now()
);
์ด๋ ๊ฒ ๋ง๋ ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ์ง์ ํ ์คํธ๋ฅผ ํด๋ณด์.
@Test
void test_with_fixture() {
// ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ๋ฉด, ๊ณ ์ ๋ ๊ฐ์ ์ฌ์ฉํ ์ ์๋ค.
var order = fixture.giveMeBuilder(orderFixture)
.set("delivery.road", false) // ํด๋น ๋ฐ์ดํฐ์์ road = false ๋ณ๊ฒฝ
.setNull("delivery.detailAddress") // ํด๋น ๋ฐ์ดํฐ์์ detailAddress = null ๋ณ๊ฒฝ
.sample(); // ์ธ์คํด์ค ๋ณต์ฌ
log.info("๐ Test Data :: {}", order.toString());
thenThrownBy(() -> orderService.validateDeliveryAddress(order))
.isExactlyInstanceOf(MyDomainException.class);
}
๋ฌผ๋ก ๋ฌด์์ ๊ฐ์ ๋ฃ๊ณ ์ถ๋ค๋ฉด ๋ฐ๋ก ๋ง๋ค ํ์ ์์ด ๊ทธ๋ฅ Order.class๋ฅผ ๋ฐ์ผ๋ฉด ๋๋ค.
@Test
void test_with_class_token() {
// ์ค์ ํ์ง ์์ ๋ค๋ฅธ ๊ฐ๋ค์ ๋๋คํ ๊ฐ์ด ๋ค์ด๊ฐ๋ค.
var order = fixtureMonkey.giveMeBuilder(Order.class)
.set("delivery.road", false)
.setNull("delivery.detailAddress")
.sample();
log.info("\n\n ๐ Test Data :: {}", order.toString());
thenThrownBy(() -> orderService.validateDeliveryAddress(order))
.isExactlyInstanceOf(MyDomainException.class);
}
๐ ์ถ์ฒํ๋ ์ฌ์ฉ๋ฐฉ๋ฒ
๋งค๋ฒ FixtureMonkey๋ฅผ ์ ์ํ๊ธฐ๋ณด๋จ, ๊ทธ๋ฅ ์๋์ ๊ฐ์ ์ ์ ๋น๋๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋๊ฑธ ๊ถ์ฅํ๋ค.
public abstract class MonkeyUtils {
public static FixtureMonkey monkey() {
return FixtureMonkey.builder()
.defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE)
.defaultInterfaceSupplier(MockitoInterfaceSupplier.INSTANCE)
.build();
}
}
class SampleRepositoryTest {
@Autowired private SampleRepository sampleRepository;
@Test
void create() {
Sample sample = MonkeyUtils.monkey()
.giveMeBuilder(Sample.class)
.setNull("id") // ๋ฆฌํ๋์
์ฃผ์
์ด๋ผ, private ๋ซ๊ธฐ ๊ฐ๋ฅ
.sample();
Sample entity = sampleRepository.save(sample);
Assertions.assertNotNull(entity.getId());
}
}
'๐ฑ Spring Framework > Test (JUnit, SpringBootTest)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ข์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ (5) | 2022.03.08 |
---|---|
spring security ํ ์คํธ์ฉ ์ค์ ๋ฐฉ๋ฒ (1) | 2022.03.07 |
Spring Boot Test์ ์ฌ๋ผ์ด์ค ํ ์คํธ (0) | 2021.09.08 |
์๋ฐ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ - JUnit5, AssertJ (0) | 2021.09.07 |
๋ค์ํ ํ ์คํธ์ Test Double (+Mockist) (0) | 2021.09.07 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev