작성
·
66
0
강의를 보고 기존 프로젝트에 작성하지 않았던 테스트 코드를 작성하면서 공부하고 있습니다.
도메인 모듈같은 경우 어떠한 의존성도 가지고 있지 않은 POJO 형태로 코드를 작성해서 단위 테스트로 검증할 수 있었습니다.
Crawling 모듈의 경우 Jsoup이라는 라이브러리를 사용하여 특정 URL의 HTML을 불러오고, 해당 HTML에서 필요한 정보를 추출하는 코드를 가지고 있습니다.
이때, 전체적으로 Mock을 사용하여 테스트를 진행하고자 하였는데 라이브러리의 특성 때문에 stubbing을 작성하는데 불편함을 느꼈습니다.
그래서 HTML을 임의로 만들고, HTML을 불러오는 메서드만 stubbing을 적용하려고 하는게 더 효율적이지 않을까 라는 생각이 들었습니다.
외부 라이브러리를 사용하는 코드에서 테스트 작성이 미숙한 것 같아서 어떤 방향이 더 좋을지 질문드리고 싶습니다.
아래 두 코드는 동일한 테스트를 진행하고 있는 코드가 아닌점 참고바랍니다.
@Mock
private lateinit var webPageLoader: WebPageLoader<Document>
@InjectMocks
private lateinit var dietParser: DietParser
fun parse() {
// given
val mockDocument = mock(Document::class.java)
val mockRows = Elements()
val row1 = mock(Element::class.java)
val row2 = mock(Element::class.java)
val row3 = mock(Element::class.java)
mockRows.add(row1)
mockRows.add(row2)
mockRows.add(row3)
val columns1 = Elements(
mock(Element::class.java).apply { given(text()).willReturn("2024.10.23") },
mock(Element::class.java),
mock(Element::class.java),
mock(Element::class.java).apply { given(text()).willReturn("Menu 1, Menu 2") }
)
val columns2 = Elements(
mock(Element::class.java).apply { given(text()).willReturn("교직원식당") }
)
val columns3 = Elements(
mock(Element::class.java).apply { given(text()).willReturn("2024.10.24") },
mock(Element::class.java),
mock(Element::class.java),
mock(Element::class.java).apply { given(text()).willReturn("Menu 3, Menu 4") }
)
given(webPageLoader.getHTML(anyString())).willReturn(mockDocument)
given(mockDocument.select(anyString())).willReturn(mockRows)
given(row1.select(anyString())).willReturn(columns1)
given(row2.select(anyString())).willReturn(columns2)
given(row3.select(anyString())).willReturn(columns3)
// when
val result = dietParser.parse()
// then
assertThat(result).hasSize(2)
.extracting("date", "menus")
.containsExactly(
tuple(LocalDate.of(2024, 10, 23), listOf("Menu 1", "Menu 2")),
tuple(LocalDate.of(2024, 10, 24), listOf("Menu 3", "Menu 4")),
)
}
@Mock
private lateinit var webPageLoader: WebPageLoader<Document>
@InjectMocks
private lateinit var parser: DepartmentNoticeParser
fun parse() {
// given
val html = """
<table class="board-table">
<tbody>
<tr>
<td class="td-num">2</td>
<td class="td-subject"><a href="javascript:fnView('2','url','title','2')">공지 제목 2</a></td>
<td class="td-write">작성자 2</td>
<td class="td-date">2024.10.02</td>
</tr>
<tr>
<td class="td-num">1</td>
<td class="td-subject"><a href="javascript:fnView('1','url','title','1')">공지 제목 1</a></td>
<td class="td-write">작성자 1</td>
<td class="td-date">2024.10.01</td>
</tr>
</tbody>
</table>
""".trimIndent()
val document: Document = Jsoup.parse(html)
given(webPageLoader.getHTML(anyString())).willReturn(document)
parser.initialize(Major.COMPUTER_SOFTWARE_ENGINEERING)
// when
val notices = parser.parse()
// then
assertThat(notices).hasSize(2)
.extracting("number", "title", "author", "date", "url")
.containsExactlyInAnyOrder(
tuple(
2,
"공지 제목 2",
"작성자 2",
LocalDate.of(2024, 10, 2),
"https://www.dongyang.ac.kr/combBbs/2/url/2/view.do?layout=unknown"
),
tuple(
1,
"공지 제목 1",
"작성자 1",
LocalDate.of(2024, 10, 1),
"https://www.dongyang.ac.kr/combBbs/1/url/1/view.do?layout=unknown"
),
)
}
답변 2
0
안녕하세요, 기중 님!
네, parser가 하는 일이 HTML에서 특정 데이터를 추출한다는 역할을 가지고 있다고 본다면, 임의의 HTML을 가지고 테스트하는 것도 좋은 방법 같아 보입니다.
감사합니다 🙂
0
안녕하세요, 인프런 AI 인턴입니다.
기중님, 외부 라이브러리를 사용한 코드 테스트에 대해 질문해 주셨군요. 여러 가지 방법으로 접근할 수 있지만, 주어진 두 가지 코드 방식 중에서 선택하신 HTML을 임의로 작성하여 테스트하는 방법이 더 직관적이고 효율적일 수 있습니다. 그 이유는 다음과 같습니다:
명확성과 가독성: 임의의 HTML 코드를 사용하여 테스트하는 방법은 테스트 중인 HTML 구조를 쉽게 식별할 수 있도록 합니다. 이는 테스트 코드를 다른 사람이나 미래의 자신이 읽을 때 이해하기 쉽게 만듭니다.
유지보수 용이성: 실제로 HTML 구조가 변경되었을 때, Mock을 사용하는 경우 모든 설정을 다시 확인하고 수정해야 할 수 있습니다. 그러나 임의로 HTML을 작성하면 HTML 코드만 수정함으로써 테스트를 쉽게 업데이트 할 수 있습니다.
복잡한 Stubbing 피하기: 여러 객체를 Mocking하고 Stubbing하기보다는, 실제 HTML 데이터를 사용하여 Jsoup 파싱 결과를 직접 테스트할 수 있어 초기 설정이 간결해집니다.
따라서, 임의의 HTML을 사용하여 직접적으로 테스트하는 방식이 개선된 가독성과 유지보수성을 제공하며, 복잡한 stubbing을 피할 수 있어 좋은 선택이라 볼 수 있습니다.
추가적으로 Spring Boot 테스트에서 외부 REST API나 서비스를 Mock해야 하는 경우, @MockBean
을 사용하여 외부 종속성을 모의할 수 있습니다. 이는 원치 않는 외부 호출을 방지하고 테스트 속도를 향상시킬 수 있으므로 적용해 볼 수도 있습니다.
위의 방법 중 선택은 프로젝트 요구사항과 팀의 표준 및 선호도에 따라 달라질 수 있으니, 팀원들과의 논의를 통해 가장 적합한 방법을 찾는 것도 좋겠습니다.
추가적인 질문이 있으시면 언제든지 말씀해 주세요. 감사합니다!
imageURL:
originURL: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.spring-boot-applications.mocking-beans