Fixture๋ฅผ ํ์ฉํ ํ
์คํธ ์ฝ๋ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ด์ ๐ FixtureMonkey
๋ฅผ ๊ณ๋ค์ธ...
์๋
ํ์ธ์. ๐
์ฌ๋ฆฌ๋ธ์์์ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ํ๊ณ ์๋ ์ค๋
ธํธ์
๋๋ค.
ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๋ ์ฌ์ฉํ๋ Fixture์ ๋ํ ์ค๋ช ๊ณผ ์ฌ์ฉํ๋ฉด์ ๋ฐ์ํ ์ด์๋ค ๊ทธ๋ฆฌ๊ณ FixtureMonkey๋ก ์ด๋ฅผ ํด๊ฒฐํ ๋ฐฉ๋ฒ์ ์๊ฐํฉ๋๋ค.
๐ ๏ธ Fixture
Fixture
๋ฅผ ๋จ์ด ๊ทธ๋๋ก ํด์ํ๋ฉด ๊ณ ์ ๋์ด ์๋ ๋ฌผ์ฒด
๋ฅผ ์๋ฏธํฉ๋๋ค.
๊ฐ๋ฐ์์ Fixture
๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
ํน์ ๊ฐ์ฒด์ ๋ํด ์ฌ์ ์ ๋ฏธ๋ฆฌ ์ ์ํ์ฌ ํ์์ ์ ์๋ ๊ฐ์ฒด๋ฅผ ํธ์ถํด์ ์ฌ์ฉํ๋ ์ฉ๋
๊ฐ์ฒด ์์ฑ ๋ฐ ์ด๊ธฐํ ์ฝ๋๋ฅผ Fixture๋ก ์ ์ํ๊ณ , ํ ์คํธ ์ฝ๋์์ ํ์ํ ๊ฐ์ฒด๋ฅผ ํธ์ถํ์ฌ ์ฌ์ฉํ๋ ์ฉ๋
๐ค Fixture ์ด๋ป๊ฒ ์ฌ์ฉํ๊ณ ์๋๋ฐ?
ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฐ๋ฐ์๋ผ๋ฉด ํ.๋ฒ.์ฏค์ "์ด๋ป๊ฒ ํ ์คํธ์ฉ ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํด์ผ ํ ์ง?" "์ด๋ป๊ฒ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์ฑํด์ผ ์ฌ์ฌ์ฉ ํ๊ธฐ ์ข์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ์ง" ๊ณ ๋ฏผํด ๋ณด์ จ์ ๊ฑฐ๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์ด๋ฐ ๊ณ ๋ฏผ์ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๋ณดํต ์๋์ ๊ฐ์ ๋ฐฉ์์ ํ์ฉํ ์ ์์ต๋๋ค.
์์ฑ์๋ฅผ ํตํด ์์ฑํ๋ ๋ฐฉ์
ํจํด์ ํ์ฉํ๋ ๋ฐฉ์
JSON ํ์ผ๋ก ๋ง๋ค์ด๋๊ณ ObjectMapper๋ฅผ ํตํด ๋ถ๋ฌ์ค๋ ๋ฐฉ์
์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ์์ค ํํธ๋์คํผ์ค ์ค์ฟผ๋ ์์๋ Test Data Builder ํจํด
๊ณผ Object Mother ํจํด
๋ฑ์ ํจํด์ ํ์ฉํ๋ ๋ฐฉ์
๊ณผ, JSON ํ์ผ์ Object๋ก ๋ณํ
ํ๋ ๋ฐฉ์์ ํ์ฉํ์ฌ
ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ์ต๋๋ค.
๐ ๏ธ ์ง์ ์์ฑ์๋ก Fixture ์์ฑํ๊ธฐ
๋ง ๊ทธ๋๋ก ์ฌ์ฉํ๋ ํ ์คํธ ์์ ์ ์์ฑํ๊ฑฐ๋, @BeforeAll ๋๋ @BeforeEach๋ฅผ ํตํ์ฌ ํ ์คํธ ์์ ์ ์ ์์ฑํ ์ ์๋ค.
public class SampleTest {
private static User user;
@BeforeAll
static void setup() {
user = new User(/* name */ "์ค๋
ธํธ", /* age */ 32, /* intro */"๐งโ๐ป");
}
@Test
void sampleTest() {
final String expectName = "์ค๋
ธํธ";
final int age = 32;
final String intro = "๐งโ๐ป";
final User actual = UserFixture.createUser();
assertThat(actual.getName()).isEqualTo(expectName);
assertThat(actual.getAge()).isEqualTo(age);
assertThat(actual.getIntro()).isEqualTo(intro);
}
}
๐ ๏ธ Test Data Builder ํจํด๊ณผ Object Mother ํจํด์ผ๋ก Fixture ์์ฑํ๊ธฐ
Builder๋ฅผ ์ง์ ๊ตฌํํ์ฌ ํธ์ ๋ฉ์๋๋ฅผ ๋ง๋ค๊ฑฐ๋, Lombok์์ ์ ๊ณตํ๋ @Builder๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ๋ ์์ต๋๋ค.
์๋์ ๊ฐ์ด ๊ตฌํํ๋ฉด ํ์ํ ๋ถ๋ถ์ ๋ํด ํธ์ ๋ฉ์๋๋ก ์ง์ ์์ฑํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@Builder๋ฅผ ์ฌ์ฉํ User ์ฝ๋
/** User.class */
@Getter
@Builder
public class User {
private String name;
private int age;
private String intro;
private User (UserBuilder userBuilder) {
this.name = userBuilder.name;
this.age = userBuilder.age;
this.intro = userBuilder.intro;
}
}
์ง์ ๊ตฌํํ Builder๋ฅผ ์ฌ์ฉํ๋ User ์ฝ๋
/** User.class */
@Getter
public class User {
// ํ๋ ์๋ต
//...
private User (UserBuilder userBuilder) {
//...
}
public static class UserBuilder {
// ํ๋ ์๋ต
//...
// ๋ฉ์๋ ์๋ต
// ...
public User build() {
return new User(this);
}
}
}
UserFixture ์ฝ๋
public class UserFixture {
public static User createUser() {
// ์ง์ ์์ฑํ Builder
return new User.UserBuilder()
.name("์ค๋
ธํธ")
.age(32)
.intro("๐งโ๐ป")
.build();
}
public static User createUserBuilderType() {
// Lombok์ ์ฌ์ฉํ Builder
return User.builder()
.name("์ค๋
ธํธ")
.age(32)
.intro("๐งโ๐ป")
.build();
}
}
ํ ์คํธ ์ฝ๋
@DisplayName("[User] ์ง์ ๊ตฌํํ Builder Test")
@Test
void builderTypeTest() {
final String expectName = "์ค๋
ธํธ";
final int age = 32;
final String intro = "๐งโ๐ป";
// ์ง์ ๊ตฌํํ Builder
final User actual = UserFixture.createUser();
assertThat(actual.getName()).isEqualTo(expectName);
assertThat(actual.getAge()).isEqualTo(age);
assertThat(actual.getIntro()).isEqualTo(intro);
}
@DisplayName("[User] Lombok Builder Test")
@Test
void lombokBuilderTypeTest () {
final String expectName = "์ค๋
ธํธ";
final int age = 32;
final String intro = "๐งโ๐ป";
// Lombok์ ํตํด ๊ตฌํ
final User actual = UserFixture.createUser();
// .. ๊ฒ์ฆ ์๋ต ์์ ๋์ผ
}
๐ค ๊ธฐ์กด ๋ฐฉ์์์ ๋ฐ์ํ ์ ์๋ ๋ฌธ์
์์ฒ๋ผ Fixture๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๋ ๋ฐ์ํ ์ ์๋ ๋ฌธ์ ๋ค์ด ์์ต๋๋ค.
-
์ ์ฐ์ฑ ๋ถ์กฑ ๋ฐ ์ฝ๋ ๋๊ธฐํ ์ด๋ ค์
ํ ์คํธ ์ฝ๋ ์์ฑ ํ API์ ์์ ์ด ์์ ๊ฒฝ์ฐ, ํ ์คํธ ์ฝ๋๋ ํจ๊ป ์์ ์ด ํ์ํฉ๋๋ค.
๋ํ ๋น ๋ฅด๊ฒ API๋ฅผ ์์ ํ๋ค ๋ณด๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ ํ์ง ๋ชปํ์ฌ ๋๊ธฐํ ๋์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. -
๊ฐ๋ฐ์๊ฐ ์ปค๋ฒํ์ง ๋ชปํ๋ ์ฃ์ง ์ผ์ด์ค ๋ฐ์
ํ ์คํธ ์ฝ๋๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํ๊ณ API๋ฅผ ์์ฑํ๋ฉด ์ฌ๋ฌ ์์ธ ์ํฉ์ ๋ฏธ๋ฆฌ ์ปค๋ฒํ ์ ์์ง๋ง, ๊ทธ๋ ์ง ๋ชปํ ๊ฒฝ์ฐ์๋ API๋ฅผ ๋จผ์ ์์ฑํ ํ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ค ๋ณด๋ ์ด๋ฏธ ์ด๋ป๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ผ ์๋ฌ๊ฐ ์ ๋๋์ง ํ์ตํด๋ฒ๋ ค์ ์ฃ์ง ์ผ์ด์ค๋ฅผ ๋์น๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํฉ๋๋ค.
-
ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋์ด๊ธฐ ์ํด ์คํ๋ ค ๋ณต์กํด์ง๋ ์ฝ๋
๋ณต์กํ ๋ก์ง ํ๋๋ฅผ ํ์ธํ๊ธฐ ์ํด ์ฌ๋ฌ ๊ฐ์ฒด๋ฅผ ์ถ๊ฐํ๊ณ ๋ ๋ณต์กํด์ง๋ ์ฝ๋๊ฐ ๋ง๋ค์ด์ง๋๋ค.
๋ํ ๋ค์ํ ์ผ์ด์ค์ ๋ํด์ ํ ์คํธํ๊ธฐ ์ํด์๋ JSON ํ์ผ๋ค์ ์ผ์ด์ค๋ง๋ค ํ๋์ฉ ๋ง๋ค์ด์ค์ผ ํ๋ฉฐ ๊ฐ๋ฐ์ ์ ์ฅ์์ ์ฃ์ง ์ผ์ด์ค๋ฅผ ํ์ธํ๋ ๋ฐ์ ๋ถํธํจ์ด ์์์ต๋๋ค.
ํํธ๋์คํผ์ค ์ค์ฟผ๋์์๋ ์ด๋ฐ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด Fixture Monkey
๋ฅผ ๋์
ํ์ต๋๋ค.
๊ธฐ์กด Fixture๋ฅผ ๋ฐ๊ฟ๋ณด์! ๐ Fixture Monkey ๋ฑ์ฅ!
Fixture Monkey๋ 2023.11.10์ผ์ ์ ์ 1.0.0 ๋ฒ์ ์ด Release ๋์์ต๋๋ค.
Java & Kotlin library for automatically generating reusable and controllable, arbitrary test fixtures
Fixture Monkey์ ๊ณต์ ํํ์ด์ง ๋๋ฌธ์ ๊ฑธ๋ ค์๋ ๊ธ์ ๋๋ค.
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ ๋ณต์กํ ์์์ ํ ์คํธ Fixture๋ฅผ ์๋์ผ๋ก ์์ฑํด ์ฃผ๋ Java&Kotlin ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๋ผ๊ณ ๋์์์ต๋๋ค.๋ํ ์๋ฐ ํ์ค Bean Validation 1.0(JSR-303), Bean Validation 2.0 (JSR-380) ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ์ ์ฉ ์ด๋ ธํ ์ด์ ์ด ์ถ๊ฐ๋ก ํ์ํ์ง๊ฐ ์๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค.
โ๏ธ ํธํ ๊ฑด ์์์ผ๋ ์ด์ ์ฌ์ฉํด๋ณด์!
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ ๋ฐฉ๋ฒ์ Github์ ๋๋ฌด ์ ๋์์๊ธฐ ๋๋ฌธ์ ์ ์ฉํ์๋ค๊ณ ๊ฐ์ ํ๊ณ ์งํํ๊ฒ ์ต๋๋ค.
์ฌ์ฉ๋ฒ์ ๊ฐ๋จํฉ๋๋ค. ์ฌ์ฉํ๊ณ ์ ํ๋ ์ฝ๋์ FixtureMonkey.create()
๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๊ฒ ํ ์ ์์ต๋๋ค.
@Test
void FixtureMonkeySample() {
final int MAX_SIZE = 5;
FixtureMonkey fixtureMonkey = FixtureMonkey.create();
List<User> users = fixtureMonkey.giveMe(User.class, MAX_SIZE);
assertThat(users).hasSize(MAX_SIZE);
}
์ ์ฌ์ง์ ์ค์ ์ฝ๋๋ฅผ ๋๋ฒ๊น ๋ชจ๋๋ก ์คํํ์์ ๋ ๋ฐ์ดํฐ ๊ฒฐ๊ณผ๊ฐ์ ๋๋ค.
users
๋ณ์๋ฅผ ํ์ธํด ๋ณด๋ฉด ๋๋คํ๊ฒ ์์ฑ๋ size 5์ ์ปฌ๋ ์
์ ๋ฐํํ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ํ ๊ฐ๊ฐ index์ ํด๋นํ๋ row์ Fixture Monkey๊ฐ ๋๋คํ ๊ฐ๋ค์ ๋ฃ์ด์ค ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
ํด๋น ์ฝ๋๋ Builder๋ฅผ ํตํด ์์ฑ๋๋ ํน์ Field์ ๋ฐ์ดํฐ ๊ฐ์ ๊ณ ์ ํ๊ฑฐ๋, Fixture Monkey์์ ์ ๊ณตํ๋ Arbitraries๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ๋ฒ์์ ๊ฐ๋ง ๋ฃ๋๋ก ์ค์ ํ ์์
๋๋ค.
age
์ ๊ฒฝ์ฐ 10~100์ฌ์ด์ ๊ฐ๋ง ํ์ฉํ๋ค๋ ๋ด์ฉ์
๋๋ค.
์ฐ์ธก ์ฌ์ง์ฒ๋ผ ๊ฐ index์ ๋ฐ์ดํฐ์ age๊ฐ 10 ์ด์ 100 ์ดํ์ธ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
@Test
void FixtureMonkeySample() {
final int MAX_SIZE = 5;
FixtureMonkey fixtureMonkey = FixtureMonkey.create();
List<User> users = fixtureMonkey.giveMeBuilder(User.class)
.set("age" , Arbitraries.integers().between(10, 100))
.sampleList(MAX_SIZE);
assertThat(users).hasSize(MAX_SIZE);
assertThat(actual.get(0).getAge()).isBetween(10, 100);
}
๐ฅ ์ง์ ํ ์คํธํ๊ธฐ ์ํด User.class๋ฅผ ์์ฑํ์ฌ ๋๋ ธ๋๋ ์คํ์ด ์ ๋๊ฑฐ๋ ๋ฐ์ดํฐ๊ฐ ๋ง๋ค์ด์ง์ง ์๋๋ค๋ฉด Getter, Setter๋ฅผ ์ถ๊ฐํด ์ฃผ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค! ํด๋น ๋ด์ฉ์ ์๋์์ ํ ๋ฒ ๋ ๋ค๋ฃจ๊ฒ ์ต๋๋ค.
โ ํํธ๋์คํผ์ค ์ค์ฟผ๋์์๋ ์ด๋ป๊ฒ ์ ์ฉํ์์๊น?
- ๋ฐ์ดํฐ ์์ฑ ์ ๋ต์ FailoverArbitraryIntrospector ์ค์
- ๊ธฐ์กด Fixture ์์ฑ ๋ฐฉ์์ Fixture Monkey๋ก ๋ณ๊ฒฝ
- ์ฌ์ฌ์ฉโป๏ธํ ์ ์๋ ๋ถ๋ถ์ Util๋ก ๋ณ๊ฒฝ
๋ฐ์ดํฐ ๊ธฐ๋ณธ ์์ฑ ์ ๋ต ๋ณ๊ฒฝ
Fixture Monkey ์ธ์คํด์ค ์์ฑ ๋ฐฉ๋ฒ
Fixture Monkey์ ๊ธฐ๋ณธ ์์ฑ ๋ฐฉ์์ BeanArbitraryIntrospector
์
๋๋ค.
โญ BeanArbitraryIntrospector
BeanArbitraryIntrospector ๋ฐฉ์์ ๋ฆฌํ๋ ์
๊ณผ Setter ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ์์ฑํ๊ณ ์ ํ๋ ํด๋์ค์ ๊ธฐ๋ณธ ์์ฑ์
์ Setter
๊ฐ ์์ด์ผ ํฉ๋๋ค.
โญ ConstructorPropertiesArbitraryIntrospector
ConstructorPropertiesArbitraryIntrospector ๋ฐฉ์์ ์ด๋ฆ ๊ทธ๋๋ก ์์ฑ์๋ฅผ ์ด์ฉํ ์์ฑ ๋ฐฉ์์ ๋๋ค.
โญ FieldReflectionArbitraryIntrospector
FieldReflectionArbitraryIntrospector๋ ๋ฆฌํ๋ ์
๋ฐฉ์์ ์ด์ฉํ์ฌ ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ํ๋์ ๊ฐ์ ์ค์ ํฉ๋๋ค.
๋ฐ๋ผ์ ๊ธฐ๋ณธ ์์ฑ์์ Getter ๋๋ Setter๊ฐ ์์ด์ผ ํ๋ค๊ณ ์ค๋ช
์ด ๋์ด์์ง๋ง, ์ค์ ํ
์คํธ์ Getter, Setter ์์ด ๊ธฐ๋ณธ ์์ฑ์๋ง ์์ด๋ ์์ฑ๋ฉ๋๋ค.
๋จ, final์ด ์๋ ๊ฐ๋ณ ๊ฐ์ฒด์ ๋ํด์๋ @Getter, @Setter๊ฐ ์๋ ๊ฒฝ์ฐ๋ ์์ฑ๋๋ค๊ณ ํฉ๋๋ค!
https://github.com/naver/fixture-monkey/issues/961#issuecomment-2021906200
โญ BuilderArbitraryIntrospector
BuilderArbitraryIntrospector๋ ๋น๋ ๋ฐฉ์์ ์ด์ฉํ์ฌ ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ํ๋์ ๊ฐ์ ์ค์ ํฉ๋๋ค.
Lombok @Builder๋ก ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, Lombok์ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ builder
, build
์ด๋ฆ์ ๊ฐ๋ ๋ฉ์๋๋ฅผ ์์ฑํด ์ฃผ๋ฉด
์ ์์ ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ์ค์ ๋์ด ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ง์ Builder ๊ตฌํํ๋ ๊ฒฝ์ฐ
public class User{
// ํ๋ ์๋ต
// ...
private User(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public static class Builder {
// ํ๋ ์๋ต
// ...
// ๋ฉ์๋ ์๋ต
// ...
// ํด๋น ์ด๋ฆ(build)์ด ์๋๋ผ๋ฉด ์์ฑ๋์ง ์์.
User build() {
return new User(this.id, this.name, this.age);
}
}
// ํด๋น ์ด๋ฆ(builder)์ด ์๋๋ผ๋ฉด ์์ฑ๋์ง ์์.
static Builder builder() {
return new Builder();
}
}
โญ FailoverArbitraryIntrospector
ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ค ๋ณด๋ฉด ์์ฑ๋ ์ฝ๋์ ๊ฐ์ฒด ์์ฑ ๋ฐฉ์์ด ๋ชจ๋ ๋ฌ๋ผ ๋จ์ผ๋ก๋ ์์ฑ๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๊ทธ๋ฐ ๊ฒฝ์ฐ FailoverArbitraryIntrospector
๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ๊ฐ์ ์์ฑ ๋ฐฉ์์ ์ง์ ํ ์ ์์ต๋๋ค.
๊ธฐ์กด Fixture ์์ฑ ๋ฐฉ์์ Fixture Monkey๋ก ๋ณ๊ฒฝ
/** ๊ธฐ์กด ์ฌ์ฉ ๋ฐฉ์ */
String fixtureResponse = readJson("ui/sample/api-response/fixture.json");
List<Sample.Response> contents = Arrays.asList(om.readValue(fixtureResponse, Sample.Response.class));
/** Fixture ์ฌ์ฉ ๋ฐฉ์ */
List<Sample.Response> contents = fixtureMonkey.giveMeBuilder(Sample.Response.class)
.sampleList(size);
๊ธฐ์กด ํํธ๋์คํผ์ค ์ค์ฟผ๋์ ์์ฑ๋ ํ
์คํธ ์ฝ๋๋ test/resources
ํ์์ ๋๋ฉ์ธ๋ณ fixture์ ํด๋นํ๋ JSON ํ์ผ๋ค์ ๊ตฌ์ฑํ๊ณ ํ์ผ์ ์ฝ์ด ObjectMapper๋ฅผ ํตํด
๊ฐ์ฒด๋ก ๋ณํํ์ฌ ์ฌ์ฉํ์์ต๋๋ค.
ํ์ง๋ง JSON ํ์ผ์ ๊ฒฝ์ฐ ๋ณํํ๊ณ ์ ํ๋ ํด๋์ค์ ํ๋๊ฐ ์์ง๋ง JSON ํ์ผ์์ ํด๋น ํค๊ฐ ์๋ค๋ฉด ๋ณํ ์ ObjectMapper์์ UnrecognizedPropertyException
์ ๋ฐ์์์ผ
JSON ํ์ผ์ ๊ตฌ์กฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ํด๋น ๊ตฌ์กฐ๋ฅผ ๋ฐ์ํ๊ธฐ ์ํด ๋ชจ๋ธ ํด๋์ค๋ฅผ ์
๋ฐ์ดํธํด์ผ ํฉ๋๋ค.
ํ์ง๋ง
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
์ค์ ์ ์ถ๊ฐํ๋ฉด ๋ณํํ ์ ์์ต๋๋ค.
์ ์์ ์ฒ๋ผ Fixture Monkey๋ฅผ ์ฌ์ฉํ๋ฉด ๋ช ์ค์ ์ฝ๋๋ก Fixture์ ์์ฑ, ์ ์ ์ผ์ด์ค, ์ฃ์ง ์ผ์ด์ค์ ๋ํด์ ์ฝ๊ฒ ์์ฑ ๋ฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
โป๏ธ Utils ํด๋์ค ๋ณ๊ฒฝ ๋ฐ ์ฌ์ฌ์ฉ์ฑ ๊ตฌ์ฑ
Define complex specifications once and reuse them! Configurations of instances can be reused across multiple tests.
Fixture Monkey์ ์ฅ์ ์ค ํ๋์ธ Reusability์ ๋๋ค. ๋ฉ์ธ ํ๋ฉด์์ ๊ฐ์กฐํ๋ ๊ฒ์ฒ๋ผ ๋ณต์กํ ๊ตฌ์กฐ๋ฅผ ํ๋ฒ ์ ๋ฆฌํ์ฌ ๋ค์์ ํ ์คํธ์์ ์ฌ์ฌ์ฉํ ์ ์๋ค.
์ฒซ ๋ฒ์งธ๋ Common(Util) ํด๋์ค๋ก ๋ณ๊ฒฝํ๋ ๋ด์ฉ์
๋๋ค. Request, Response, Domain, Entity ์ฒ๋ผ ์ฉ๋์ ๋ฐ๋ผ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐฉ์์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ FixtureMonkey๊ฐ์ฒด๋ฅผ ์ฉ๋์
๋ฐ๋ผ ๋ถ๋ฆฌํ๋๊ฒ์ด ์๋ FailoverIntrospector
์ ์ฌ์ฉํ์ฌ ๊ตฌ์ฑํ๋๋ก ๋ณ๊ฒฝํ์์ต๋๋ค.
public class FixtureCommon {
/**
* ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฉ์๋์์ ํธ์ถํ์ฌ ์ฌ์ฉํ ์ ์๋๋ก static Field๋ก ๊ตฌ์ฑ
* FixtureMonkeyUtils.fixtureMonkey;`์ ๊ฐ์ด ๊ณตํต ์ค์ ์ ๋ํด์ ์ฌ์ฉํ๋๋ก ํฉ๋๋ค.
*/
public static FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.objectIntrospector(new FailoverIntrospector(
Arrays.asList(
FieldReflectionArbitraryIntrospector.INSTANCE,
BeanArbitraryIntrospector.INSTANCE,
BuilderArbitraryIntrospector.INSTANCE
)
))
// FixtureMonkey์ ๋์
๊ฐ์ null ํ์ฉํ์ง ์์.
.defaultNotNull(true)
.build();
}
๋๋ฒ์งธ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฐ์ฒด๋ค์ ๋ํ ์ ์ ์ ๋๋ค. ํด๋น ์์ ๋ ์ค์ ์ฝ๋๊ฐ ์๋ ์ํ ์ฝ๋์ ๋๋ค.
์ด๋ฆ์ ์ค๋
ธํธ์ธ๋ฐ ๋์ด๋ง 32์ด, 33์ด๋ก ๊ตฌ์ฑํ์ฌ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑ
๊ณผ ๊ฐ์ ๊ฐ๋จํ ์๋ฅผ ๋ค๊ฒ ์ต๋๋ค.
(์ค๋ช
์ ์ํด ๊ฐ๋จํ ์๋ฅผ ๋ค์์ต๋๋ค.)
public class FixtureCommon {
// ...
private static ArbitraryBuilder<User> fixName() {
return fixtureMonkey.giveMeBuilder(User.class)
.set("name", "์ค๋
ธํธ");
}
public static User age32YunNote() {
return fixName()
.set("age", 32)
.sample();
}
public static User age33YunNote() {
return fixName()
.set("age", 33)
.sample();
}
}
@Test
void FixtureMonkeyReusabilityAge32Sample() {
User actual = FixtureCommon.age32YunNote();
assertThat(actual.getName()).isEqualTo("์ค๋
ธํธ");
assertThat(actual.getAge()).isEqualTo(32);
}
@Test
void FixtureMonkeyReusabilityAge33Sample() {
User actual = FixtureCommon.age33YunNote();
assertThat(actual.getName()).isEqualTo("์ค๋
ธํธ");
assertThat(actual.getAge()).isEqualTo(33);
}
์์ ๊ฐ์ด ArbitraryBuilder๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ์ฑํ๋ ๋ถ๋ถ์ ์ฌ์ฌ์ฉํ์ฌ ํน์ ํ๋๋ค๋ง setํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๐ญ ์ ๋ฆฌ
ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ถ๋ค์ด๋ผ๋ฉด ๐FixtureMonkey
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ถ์ฒํฉ๋๋ค.
ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํ๋ค๋ ๊ฒ์ ๋ชจ๋ ์๊ณ ์์ง๋ง, ์
๋ฌด๋ฅผ ๋ณด๋ค ๋ณด๋ฉด ์๊ฐ์ด ๋ถ์กฑํ๊ฑฐ๋, ํ
์คํธ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๊ฒ์ด ๊ท์ฐฎ๊ธฐ ๋๋ฌธ์ ๋ค์์ ์์ฑํด์ผ์ง!!
๋ผ๊ณ ๋์ด๊ฐ๋ ๋ถ๋ค๋ ๋ง์ ๊ฒ์ผ๋ก ์๊ฐํฉ๋๋ค.
Fixture Monkey๋ ๋๋ถ๋ถ์ ์ฌ๋๋ค์ด ์ฝ๊ฒ ํ
์คํธ ๊ฐ์ฒด๋ฅผ ์์ฑํ ์ ์๋๋ก ๋์์ค๋๋ค. ๊ฐ๋จํ ๋ช ์ค์ ์ฝ๋๋ก
๋ค์ํ ํ
์คํธ ์ผ์ด์ค์ ๋ํด์ ์์ฑํด ๋ผ ์ ์์ด ํ
์คํธ ์ฝ๋์ ์ ๋ขฐ๋๊ฐ ์ฌ๋ผ๊ฐ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝ๋์ ์๋ ๋ง์ง ์์์ ๋ ๊น๋ํ๊ณ ๊ฐ๋
์ฑ ์๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ ์ ์๋ค๋ ํฐ ์ฅ์ ์ด ์์ต๋๋ค.
์ค์ ๋ก ํํธ๋์คํผ์ค ์ค์ฟผ๋์์ ์ฝ 500๊ฐ ์ด์์ ํ ์คํธ ์ฝ๋๋ฅผ Fixture Monkey๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑํ์ผ๋ฉฐ, ํ ์คํธ์ ํ์ํ ๊ด์ฌ ํ๋๋ค์ ๋ช ์์ ์ผ๋ก ํํํ์ฌ Fixture๋ฅผ ์์ฑํ๋ ์๊ฐ์ ์ค์ผ ์ ์์๊ณ , ์ฃ์ง ์ผ์ด์ค๋ค์ ํตํด ๋ฏธ์ฒ ๋ฐ๊ฒฌํ์ง ๋ชป ํ ์ผ์ด์ค๋ค์ ๋ํด์๋ ์ปค๋ฒํ ์ ์์์ต๋๋ค.
๊ธด ๊ธ ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค.
๐ Thanks to
- Fixture Monkey ๋์ ์ ์ํด ๊ฐ์ด๋ ํด์ฃผ์ Architect. ์ฝ๋๋ค์ด๋ฒ๋ ๊ฐ์ฌํฉ๋๋ค.
- ํจ๊ป ์ฌ์ฉํ๋ฉฐ ๊ฐ์ด๋๋ฅผ ์ก๋๋ฐ ๋์์ค ํ, ์ค์ฟผ๋ ๊ฐ๋ฐ์๋ถ๋ค๊ป ๊ฐ์ฌ๋๋ฆฝ๋๋ค.