210๊ฐ์ ํ ์คํธ ์ฝ๋์ 99% ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง
์์ ์์ฝ ํ๋ซํผ์ ๊ฐ๋ฐํ๋ฉฐ ์ด 210์ฌ ๊ฐ์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ณ , 99% ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ฌ์ฑํ์ต๋๋ค. ํ์ง๋ง ์ฝ๋์ ๋ณ๊ฒฝ ์ฌํญ์ด ์์ ๋๋ง๋ค ์ ์ฒด ํ ์คํธ๋ฅผ ์๋์ผ๋ก ์คํํด์ผ ํ๊ณ , ์ด๋ ์ ์ ๋ถํธํ ์์ ์ด ๋์์ต๋๋ค.
๊ทธ๋์ CI/CD ํด์ธ GitHub Actions๋ฅผ ๋์ ํ์ฌ push ๋๋ pull_request๊ฐ ๋ฐ์ํ ๋ ์๋์ผ๋ก ํ ์คํธ๊ฐ ์คํ๋๋๋ก ์ค์ ํ์ต๋๋ค.
GitHub Actions๋ฅผ ์ ํํ ์ด์
GitHub Actions๋ฅผ ์ ํํ ์ด์ ๋ ๋จ์ํฉ๋๋ค.
- Jenkins์ฒ๋ผ ๋ณ๋์ ์๋ฒ๋ฅผ ๊ตฌ์ถํ ํ์๊ฐ ์์ผ๋ฉฐ,
- ๋จ์ํ .github/workflows ๋๋ ํ ๋ฆฌ์ YAML ํ์ผ๋ง ์์ฑํ๋ฉด ๋ฐ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ๋ํ Groovy์ ๊ฐ์ ์๋ก์ด ๋ฌธ๋ฒ์ ๋ฐฐ์ธ ํ์ ์์ด ๋น ๋ฅด๊ฒ ์๊ตฌ ์ฌํญ์ ์ถฉ์กฑํ ์ ์์์ต๋๋ค.
์ฆ, ๊ฐ๋จํ ์ค์ ํ๊ณ ๋ฐ๋ก ์คํํ ์ ์์์ต๋๋ค.
GitHub Actions์์ ์ ์ฒด ํ ์คํธ ์คํจ
๋ก์ปฌ ํ๊ฒฝ์์ ํ ์คํธ๋ฅผ ์คํํ์ ๋๋ ๋ชจ๋ ํ ์คํธ๊ฐ ์ฑ๊ณตํ์ง๋ง, GitHub Actions์์๋ ์ ์ฒด ํ ์คํธ ์ค 70๊ฐ๋ ์คํจํ์ต๋๋ค.
๊ธฐ์กด์ ./gradlew build๋ฅผ ์คํํ์ ๋ GitHub Actions์์ ์ถ๋ ฅ๋๋ ๋ก๊ทธ๋ ํ ์ค์ฉ ๊ฐ๋จํ ํ์๋์ด ๋ฌธ์ ์ ์์ธ์ ํ์ ํ๊ธฐ์ ๋ถ์กฑํ์ต๋๋ค. ๊ทธ๋์ ./gradlew test --stacktrace ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํด ๋ ๋ง์ ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ์ฌ ๋ฌธ์ ๋ฅผ ๋ถ์ํ์ต๋๋ค.
๋ฌธ์ 1. Embedded Redis ์ด์
์์ธ ๋ถ์
./gradlew test --stacktrace๋ก ๋ก๊ทธ๋ฅผ ํ์ธํ ๊ฒฐ๊ณผ, ๋ค์๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
Caused by: org.springframework.boot.context.config.ConfigDataResourceNotFoundException at ConfigDataResourceNotFoundException.
ํ ์คํธ์ฉ DB๋ก๋ ๋ด์ฅ H2์ Embedded Redis๋ฅผ ์ฌ์ฉํ๊ณ ์์๋๋ฐ, ์ผ๋ถ ํ ์คํธ๋ง ์คํจํ์ผ๋ฉฐ, ๊ทธ์ค Redis ๊ด๋ จ ํ ์คํธ๋ง ์คํจํ ๊ฒ์ผ๋ก ๋ณด์ ๋ฌธ์ ๋ H2๊ฐ ์๋ Redis์ ์๋ ๊ฒ์ผ๋ก ํ๋จํ์ต๋๋ค.
Embedded Redis๋ฅผ ํ ์คํธ ํ๊ฒฝ์์ ์ฌ์ฉํ๋๋ก ์ค์ ํ์ง๋ง, ์ ๋๋ก ์ ์ฉ๋์ง ์์ ๊ฒ์ผ๋ก ๋ณด์์ต๋๋ค. ์ด์ Embedded Redis ์ค์ ์ ๋ค์ ์ ๊ฒํ๊ณ ์์ ํ๊ธฐ๋ก ํ์ต๋๋ค.
๋ฐ์ ์์ธ
ํ ์คํธ ํ๊ฒฝ์์ ์ฌ์ฉํ๋ application-test.yml ํ์ผ์ ์๋ชป๋ ์ค์ ์ด ํฌํจ๋์ด ์์์ต๋๋ค:
data:
redis:
port: 6379
host: localhost
์ด ์ค์ ์ผ๋ก ์ธํด, build.gradle ์ Embedded Redis ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ฃ์์์๋, Embedded Redis๊ฐ ์๋ ๋ก์ปฌ ์ปดํจํฐ์ ๋์ปค๋ก ๋์์ง ๊ฐ๋ฐ์ฉ Redis๋ฅผ ์ฐธ์กฐํ๊ณ ์์์ต๋๋ค. ๋ก์ปฌ์์๋ Docker Redis๊ฐ ์คํ ์ค์ด์๊ธฐ ๋๋ฌธ์ ํ ์คํธ๊ฐ ์ฑ๊ณตํ์ง๋ง, GitHub Actions์์๋ Redis๊ฐ ์์ด์ ์คํจํ ๊ฒ์ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
1. application-test.yml ์์
- ํ ์คํธ์์ Embedded Redis๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก test.yml ์์ Redis ์ค์ ์ ์ ๊ฑฐํ์ต๋๋ค.
2. Embedded Redis ์ค์ ์ถ๊ฐ
- Embedded redis ์ค์ ๊ด๋ จ ์ ๋ช ํ ๊ธ์ธ https://jojoldu.tistory.com/297 ์ฐธ๊ณ ํด์ ์์ฑํฉ๋๋ค. ์ฝ๋์ ๋ํ ์ค๋ช ์ ํฅ๋ก๋์ ๊ธ์ ์ฐธ๊ณ ํด์ฃผ์ธ์!
@Slf4j
@TestConfiguration
public class EmbeddedRedisConfig {
private final int port = 6379;
private RedisServer redisServer;
@PostConstruct
public void postConstruct() throws IOException {
int port = isRedisRunning() ? findAvaliablePort() : this.port;
this.redisServer = new RedisServer(port);
redisServer.start();
}
@PreDestroy
public void preDestroy() throws IOException {
if (redisServer != null) {
redisServer.stop();
}
}
private int findAvaliablePort() throws IOException{
for (int port = 10000; port <= 65535; port++) {
Process process = executeGrepProcessCommand(port);
if (!isRunning(process)) {
return port;
}
}
throw new IllegalArgumentException("Not Found Available port: 10000 ~ 65535");
}
private boolean isRedisRunning() throws IOException{
return isRunning(executeGrepProcessCommand(this.port));
}
private boolean isRunning(Process process) {
String line;
StringBuilder pidInfo = new StringBuilder();
try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
while ((line = input.readLine()) != null) {
pidInfo.append(line);
}
} catch (Exception e) {}
return StringUtils.hasLength(pidInfo.toString());
}
// mac, linux ์ ์ฉ
private Process executeGrepProcessCommand(int port) throws IOException {
String command = String.format("netstat -nat | grep LISTEN|grep %d", port);
String[] shell = {"/bin/sh", "-c", command};
return Runtime.getRuntime().exec(shell);
}
// window ์ ์ฉ
private Process executeGrepProcessCommand(int port) throws IOException {
String command = String.format("netstat -nao | find \"LISTEN\" | find \"%d\"", port);
String[] shell = {"cmd.exe", "/y", "/c", command};
return Runtime.getRuntime().exec(shell);
}
}
ํฅ๋ก๋์ ์๋ณธ ๊ธ๊ณผ ๋ฌ๋ฆฌ ์ ๋ @TestConfiguration ์ด๋ ธํ ์ด์ ์ ๋ถ์์ต๋๋ค.
@TestConfiguration ์ ํ ์คํธ์๋ง ์ ์ฉ๋๋ ์ค์ ์ด๋ ธํ ์ด์ ์ ๋๋ค. ํ์ง๋ง, ์ด @TestConfiguration ์ @Configuration ๊ณผ ๋ค๋ฅด๊ฒ ์ปดํฌ๋ํธ ์ค์บ์ ๋์์ด ๋์ง ์๊ธฐ ๋๋ฌธ์ ์๋ฒ ๋๋ ๋ ๋์ค ์ค์ ์ ์ฌ์ฉํ๋ ๊ณณ์์ @Import ๋ฅผ ํด์ค์ผ ํฉ๋๋ค.
* ์ฐธ๊ณ
- stackoverflow: Spring Boot: @TestConfiguration Not Overriding Bean During Integration Test
- why prefering @TestConfiguration than @Configuration in src/test
์ถ๊ฐ๋ก, @Configuration์ ํ ์คํธ ์์๋ ์ปดํฌ๋ํธ ์ค์บ์ ๋์์ด ๋์ด ์๋์ผ๋ก ๋น์ ๋ฑ๋ก๋ฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก Redis ์ค์ ํด๋์ค์๋ @Configuration์ ๋ถ์ด๋๋ฐ, ์ด๋ ํ ์คํธ ํ๊ฒฝ์์๋ ๋น์ผ๋ก ๋ฑ๋ก๋๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํ ์คํธ์ฉ ๋ด์ฅ Redis๋ฅผ ์ค์ ํ๋ @TestConfiguration์ ๋น๊ณผ ์ค์ Redis ์ค์ ํด๋์ค์ ๋น ์ด๋ฆ์ด ์ค๋ณต๋๋ค๋ฉด ๋น์ ์ด๋ฆ ์ค๋ณต ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ด๋ฅผ ๊ฐ๋จํ ํด๊ฒฐํ๋ ค๋ฉด, ์ค์ Redis ์ค์ ํด๋์ค์ @Profile("!test")๋ฅผ ์ถ๊ฐํด ํ ์คํธ ํ๊ฒฝ์์๋ ํด๋น ์ค์ ์ด ์ ์ฉ๋์ง ์๋๋ก ์ค์ ํ๋ฉด ๋ฉ๋๋ค
๋ฌธ์ 2. ์ธ๋ถ ์ค์ ์ด์ (@Configuration ์ ๋ํ ์คํด)
ํด๋น ์ค๋ฅ๋ ์ธ๋ถ ์ค์ ์ด ์๋ชป๋์ด ๋ฐ์ํ๋ ์ค๋ฅ์ ๋๋ค. (์: application.yml ์ค์ ์ค๋ฅ)
Caused by: java.lang.IllegalArgumentException at PropertyPlaceholderHelper.java:180
์ ๋ ๊ฒฐ์ API์ ์ด๋ฉ์ผ API๋ฅผ ์ฌ์ฉํ๋ฉด์, ์ธ๋ถ์ ๋ ธ์ถ๋์ด์๋ ์ ๋๋ ๊ฐ๋ค์ ๊ด๋ฆฌํ๊ธฐ ์ํด application-secret.yml ํ์ผ์ ๋ณ๋๋ก ์์ฑํด ํ ์คํธ์ ์ฌ์ฉํ์ต๋๋ค. ์ด๋ฌํ ๊ฐ๋ค์ ์ฐธ์กฐํ๋ ๊ณณ์ @Configuration ์ด๋ ธํ ์ด์ ์ด ๋ถ์ ์ค์ ํด๋์ค์์ผ๋ฉฐ, ํด๋น ํด๋์ค๋ ํ ์คํธ ์์๋ ๋น์ผ๋ก ๋ฑ๋ก๋ฉ๋๋ค.
๊ทธ๋์, ํ ์คํธ๋ฅผ ํจ์๋ @Autowired ๋ก ์ฃผ์ ๋ฐ์ ๋น๋ค์ @Configuration ์ ์ํด secret์ธ๋ถ ์ค์ ํ์ผ์ ๊ฐ์ ์ฝ๊ฒ ๋๋ ๊ฒ์ด์์ต๋๋ค. ๋ก์ปฌ์ ํ ์คํธ ํ๊ฒฝ์์๋ ๋ฌธ์ ๊ฐ ์์์ง๋ง, Github Actions์ ํ ์คํธ ํ๊ฒฝ์์๋ ํ์ํ ์ธ๋ถ ์ค์ ๊ฐ์ด ๋๋ฝ๋๊ฒ ๋์์ต๋๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด GitHub Actions์ secret ๋ณ์๋ฅผ ํ์ฉํ์ต๋๋ค. application-secret.yml ํ์ผ์ ๋ด์ฉ์ secret ๋ณ์๋ก ๋ฑ๋กํ ๋ค, ./gradlew test๋ฅผ ์คํํ๊ธฐ ์ ์ GitHub Actions์ ์คํ ํ๊ฒฝ(Virtual Machine)์ ํด๋น ํ์ผ์ ๋์ ์ผ๋ก ์์ฑํ๋๋ก ์ค์ ํ์ต๋๋ค.
- name: Gradle ํ
์คํธ ์ํ
run: |
echo "${{ secrets.APPLICATION_SECRET_PROPERTIES }}" > ./src/main/resources/application-secret.yml
./gradlew test --stacktrace
๋ฌธ์ 3. Spring Rest Docs ์ด์
Gradle์์ ํน์ ํ์คํฌ(Task)๊ฐ ์คํ๋์ง ๋ชปํ๊ณ ์คํจํ๋ ๊ฒฝ์ฐ ๋ฐ์ํ๋ ์์ธ์ ๋๋ค. ์ ๊ฒฝ์ฐ, ์คํจํ ํ์คํฌ๋ :test๋ก, ํ ์คํธ ์คํ๊ณผ ๊ด๋ จ๋ ์์ ์ด์์ต๋๋ค.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':test'.
์ ๋ API ๋ฌธ์๋ฅผ ์ํด Spring Rest Docs๋ฅผ ์ฌ์ฉํ๊ณ ์์์ต๋๋ค. build.gradle ํ์ผ์๋ Gradle ํ ์คํธ๊ฐ ์ฑ๊ณตํ๋ฉด ์ค๋ํซ(snippet)์ ์์ฑํ๊ณ , ์ด๋ฅผ ํ์ฉํด API ๋ฌธ์์ธ .html ํ์ผ์ ํน์ ๋๋ ํ ๋ฆฌ๋ก ๋ณต์ฌํ๋ ์์ ์ ์ถ๊ฐํด๋์์ต๋๋ค.
configurations {
compileOnly {
extendsFrom annotationProcessor
}
asciidoctorExt
}
ext {
snippetsDir = file('build/generated-snippets') // ํ
์คํธ ์ฑ๊ณต ํ, ์์ฑ๋ ์ค๋ํซ ์ ์ฅ ๊ฒฝ๋ก
test {
useJUnitPlatform()
outputs.dir snippetsDir // REST Docs ํ
์คํธ ๊ฒฐ๊ณผ๋ฌผ์ snippetsDir์ ์ํด ์ง์ ๋ ๊ฒฝ๋ก์ ์ ์ฅ
}
asciidoctor {
dependsOn test
inputs.dir snippetsDir // REST Docs์์ ์์ฑํ ์ค๋ํซ(build/generated-snippets)์ ์
๋ ฅ์ผ๋ก ์ฌ์ฉ
configurations 'asciidoctorExt'
sources {
include("**/index.adoc") // index.adoc ํ์ผ๋ง ๋ฌธ์ํ ๋์์ ํฌํจ
}
baseDirFollowsSourceFile() // ๋ค๋ฅธ adoc ํ์ผ์ include ํ ๋ ๊ฒฝ๋ก๋ฅผ baseDir๋ก ๋ง์ถ๋ค
}
task createDocument(type: Copy) {
dependsOn asciidoctor
from file("build/docs/asciidoc") // ๋ฌธ์๊ฐ ์์ฑ๋ ๊ฒฝ๋ก
into file("src/main/resources/static") // ๋ฌธ์๋ฅผ ๋ณต์ฌํ ๊ฒฝ๋ก
}
// ์์ฑ๋ API ๋ฌธ์๋ฅผ jar ๋ก ๋ณํํ๋ ๊ณผ์ , jar๋ก ๋ฐฐํฌ ์ํ ์ ํ์ ์์
bootJar {
dependsOn createDocument
from("${asciidoctor.outputDir}") {
into 'static/docs'
}
}
์ ์ค์ ์์ test ํ์คํฌ ์คํ ์ค ์คํจํ๋๋ฐ, ./gradlew test --stacktrace ๋ช ๋ น์ด๋ง์ผ๋ก๋ ์์ธ์ ์๊ธฐ ์ด๋ ค์ ์ต๋๋ค. ๊ทธ๋์ ๋ ์์ธํ ๋ก๊ทธ๋ฅผ ํ์ธํ๊ธฐ ์ํด ./gradlew test --stacktrace --info๋ฅผ ์คํํ ๊ฒฐ๊ณผ, ์ค๋ฅ ์์ธ์ ์ฐพ์ ์ ์์์ต๋๋ค. ๋ฌธ์ ๋ ์๋์ ๊ฐ์ต๋๋ค.
๋ฌธ์ ์์ธ
snippets๊ฐ ์ ์ฅ๋ ๊ฒฝ๋ก์ธ /stay/stay-image-update ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ค๋ ์ ์ด ์ฃผ์ ์์ธ์ด์์ต๋๋ค. ๋ก์ปฌ ํ๊ฒฝ์์๋ ํด๋น ๊ฒฝ๋ก๊ฐ ์ ์์ ์ผ๋ก ์์ฑ๋์์ง๋ง, GitHub Actions ํ๊ฒฝ์์๋ ์์ฑ์ ์คํจํ์ต๋๋ค.
๊ฒฝ๋ก ์ค์ ๊ณผ์
์ ๋ ๋๋ฉ์ธ๋ณ๋ก ์ค๋ํซ์ ๊ตฌ๋ถํ๊ธฐ ์ํด /stay/stay-image-update ์ ๊ฐ์ ํด๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค๊ณ ์ ํ์ต๋๋ค.
์ฆ, Gradle์ build.gradle์์ ์ค์ ํ ext.snippetsDir ๋ณ์์ ํจ๊ป ์ค๋ํซ์ ๊ฒฝ๋ก๋
build/generated-snippets/stay/stay-image-update๋ก ์ง์ ๋๊ธฐ๋ฅผ ๋ฐ๋ฌ์ต๋๋ค.
@DisplayName("์์ ์ด๋ฏธ์ง ์
๋ฐ์ดํธ API")
@Test
void changeStayImage() throws Exception {
//given
...
// when then
mockMvc.perform(
RestDocumentationRequestBuilders.put("/stay/{stayId}/image", stayId)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(imageUrls))
.sessionAttr(LOGIN_MEMBER, 1L)
.cookie(new Cookie("JSESSIONID", "ACBCDFD0FF93D5BB"))
).andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("0200"))
.andExpect(jsonPath("$.status").value("OK"))
.andExpect(jsonPath("$.message").value("OK"))
.andExpect(jsonPath("$.data").exists())
.andDo(document("/stay/stay-image-update", << โญ์ฃผ์ํด์ ๋ณผ ๋ถ๋ถ!!!!โญ
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(
attributes(key("url").value("/stay/{stayId}/image")),
parameterWithName("stayId").description("์ด๋ฏธ์ง ์
๋ฐ์ดํธํ ์์ Id")
),
requestFields(
fieldWithPath("[]").type(JsonFieldType.ARRAY).description("์
๋ฐ์ดํธํ ์์ ์ด๋ฏธ์ง(์ต์ 5๊ฐ)")
),
responseFields(
beneathPath("data"),
fieldWithPath("stayId").type(JsonFieldType.NUMBER).description("์์ ID"),
fieldWithPath("hostId").type(JsonFieldType.NUMBER).description("์์ ์ฃผ์ธID"),
...
fieldWithPath("imageUrls").type(JsonFieldType.ARRAY).description("์์ ์ด๋ฏธ์ง url")
)
));
}
์ฒ์์๋ Github Actions VM์์ build ๋๋ ํ ๋ฆฌ๊ฐ ์์ด์!? ์ถ์ธกํด์ ์๋ํ์ง๋ง, ์ ํจํ์ง ์์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ค, CircleCI ์ฌ์ฉ์์ ์ด์์ธ Spring Java 8 project, REST DOCS, Failed to create directory ๋ฅผ ์ฐธ๊ณ ํ์ฌ /stay/stay-image-update๋ฅผ ์๋ ๊ฒฝ๋ก์ธ stay/stay-image-update๋ก ์์ ํ์ต๋๋ค.
์ "/" ๋ฅผ ์ ๊ฑฐํ๋ ๋ฐฉ๋ฒ์ด ์ ํจํ์ง ์์ธ์ ์ฐพ์๋ณด์์ต๋๋ค.
์์ธ ๋ถ์: ์ด์ ์ฒด์ ์ ํ์ผ ์์คํ ๊ณผ ๊ถํ ๊ด๋ฆฌ ๋ฐฉ์์ ์ฐจ์ด
- ๋ก์ปฌ ํ๊ฒฝ (Windows)
- /๋ก ์์ํ๋ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ๋๋ผ๋, Windows ์์๋ ํน์ ๋๋ผ์ด๋ธ(C:\ ๋ฑ)๋ ์ฌ์ฉ์ ๋๋ ํ ๋ฆฌ๋ก ๋งคํ๋์ด ๋๋ ํ ๋ฆฌ๊ฐ ์์ฑ๋ฉ๋๋ค.
- ๋ฐ๋ผ์ ๋ก์ปฌ ํ๊ฒฝ์์ /stay/stay-image-update๋ ์ฌ์ฉ์ ๋๋ ํ ๋ฆฌ ํ์์ ์์ฑ๋ ์ ์์์ต๋๋ค.
- GitHub Actions ํ๊ฒฝ (Linux/Ubuntu ๊ธฐ๋ฐ)
- /๋ก ์์ํ๋ ๊ฒฝ๋ก๋ ๋ฃจํธ ๋๋ ํ ๋ฆฌ๋ก ์ธ์๋ฉ๋๋ค.
- ์ผ๋ฐ ์ฌ์ฉ์๋ ๋ฃจํธ ๋๋ ํ ๋ฆฌ์ ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ ๊ถํ์ด ์์ต๋๋ค. ๋ฐ๋ผ์ GitHub Actions์์๋ ๋๋ ํ ๋ฆฌ ์์ฑ์ด ์คํจํ์ต๋๋ค
- ์ ๋ build/snippets ํ์์ ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ๋ ค ํ์ง๋ง, ๊ฒฝ๋ก๋ฅผ "/" ๋ก ์์ํ๋ /stay/stay-image-update ๋ฃจํธ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก๋ก ์ง์ ํ๋ฉด์ Linux ํ๊ฒฝ์์๋ ๋ฃจํธ ๋๋ ํ ๋ฆฌ๋ก ์๋ชป ํด์๋ ๊ฒ์ด์์ต๋๋ค.
์ด๋ ์ ๋ ๊ฒฝ๋ก์ ์๋ ๊ฒฝ๋ก์ ๋ํ ์ดํด ๋ถ์กฑ์์ ๋น๋กฏ๋ ์ค๋ฅ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ , ์์ ํ ๊ฒฐ๊ณผ ๋๋์ด Github Actions ์์ ./gradlew test ๋ฅผ ์ฑ๊ณตํ ์ ์๊ฒ ๋์์ต๋๋ค.
์ถ๊ฐ ํ์ธ: Linux ํ๊ฒฝ์์์ ๊ถํ ์ ํ
๋ฃจํธ ๋๋ ํ ๋ฆฌ ์์ฑ ๊ถํ ํ์ธ
WSL(Windows Subsystem for Linux)์ ์ฌ์ฉํด ์ค์ ๋ก ์ผ๋ฐ ์ฌ์ฉ์๊ฐ ๋ฃจํธ ๋๋ ํ ๋ฆฌ์ ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋์ง ํ
์คํธํ์ต๋๋ค.
Windows ํ๊ฒฝ
- ํน์ ๋๋ ํ ๋ฆฌ์์ /use ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ๋ C:\ ๋๋ผ์ด๋ธ์ ๋๋ ํ ๋ฆฌ๊ฐ ์์ฑ๋์ต๋๋ค.
WSL (Linux ํ๊ฒฝ)
- ๋์ผํ ์์ ์ WSL์์ ์ํํ๋ฉด ๋ฃจํธ ๋๋ ํ ๋ฆฌ(/)์ ํด๋๋ฅผ ์์ฑํ๋ ค๋ ๊ฒ์ผ๋ก ์ธ์๋ฉ๋๋ค.
- ์ผ๋ฐ ์ฌ์ฉ์๋ ๋ฃจํธ ๋๋ ํ ๋ฆฌ์ ํด๋ ์์ฑ ๊ถํ์ด ์๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ๋๋ ํ ๋ฆฌ ์์ฑํ ์ ์์์ต๋๋ค.
๋ง์น๋ฉฐ
ํ๋ฃจ์ข ์ผ 10์๊ฐ ๋์ ์ฝ์งํ๋ฉฐ ๋ค์๊ณผ ๊ฐ์ ๋ง์ ๊ฒ์ ๋ฐฐ์ฐ๊ณ ์ฑ์ฅํ ์ ์์์ต๋๋ค.
- @TestConfiguration๊ณผ @Configuration์ ์ฐจ์ด ๋ฐ ๋์ ์๋ฆฌ
- Spring Boot ์ ์ธ๋ถ ์ค์ ์ฐ์ ์์
- ์ด์ ์ฒด์ ์ ํ์ผ ์์คํ ๊ณผ ๊ถํ ๊ด๋ฆฌ ๋ฐฉ์
- ํ์ผ ๊ฒฝ๋ก์ ์ ๋ ๊ฒฝ๋ก/์๋ ๊ฒฝ๋ก์ ๋ํ ์ดํด
- GitHub Actions์์ ๋ก๊ทธ ํ์ธ ๋ฐ ๋๋ฒ๊น ๋ฐฉ๋ฒ
๋ฌผ๋ก , GitHub Actions์ ๋ก๊ทธ๊ฐ ์์๋ค๋ฉด ์ ๋๋ก ํด๊ฒฐํ ์ ์์์ ๋ฌธ์ ๋ค์ด์์ต๋๋ค..
(๋ฌผ๋ก , ./gradlew test --info ๋ช
๋ น์ด๋ก ๋ฌดํ ์คํฌ๋กค ์์ค์ ๋ก๊ทธ๋ฅผ ํ์ธํ๋ ๋ฐ๋ ์ธ๋ด๊ฐ ํ์ํ์ง๋ง์...)
์์ฆ ๋ค์ํ ํธ๋ฌ๋ธ์ํ
์ ๊ฒช์ผ๋ฉด์ ๋๋ผ๋ ์ ์ด ์์ต๋๋ค.
์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋์ง ์์ธ์ ์ ์ํ๊ณ ๋ถ์ํด์ผ๋ง, ๋ค์์ ๋น์ทํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๋ ์์ฉํ ์ ์๋ค๋ ์ฌ์ค์
๋๋ค.
์์ผ๋ก๋ ํญ์ "์ ๋ฐ์ํ๋์ง"๋ฅผ ๊น์ด ๋ถ์ํ๋ฉฐ, ์ฒด๊ณ์ ์ผ๋ก ํธ๋ฌ๋ธ์ํ ์ ํด๊ฒฐํด ๋๊ฐ๋ ค ํฉ๋๋ค.
์ด๋ฒ ๊ฒฝํ์ด GitHub Actions๋ฅผ ์ฌ์ฉํด CI ์๋ํ ํ ์คํธ๋ฅผ ๊ตฌ์ถํ๋ ค๋ ๋ถ๋ค์ด๋, ์ ์ ๋น์ทํ ์ค๋ฅ๋ก ๊ณ ๋ฏผ ์ค์ธ ๋ถ๋ค์๊ฒ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋ผ๋ฉฐ ๋ง์น๊ฒ ์ต๋๋ค.