[테스트 코드] Mock을 마주하는 자세

말하는 감자·2024년 11월 30일
0
post-thumbnail

[Practical Testing: 실용적인 테스트 가이드]

섹션 7. Mock을 마주하는 자세

📌 실습 오류 해결

실습을 따라하다가 테스트를 실행하니 갑자기 오류가 발생했다.

길고 긴 오류 내용...
  org.springframework.dao.InvalidDataAccessApiUsageException: For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters
	at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:368)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:335)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
	at jdk.proxy2/jdk.proxy2.$Proxy181.findOrdersBy(Unknown Source)
	at sample.cafekiosk.spring.api.service.order.OrderStatisticsService.sendOrderStatisticsMail(OrderStatisticsService.java:23)
	at sample.cafekiosk.spring.api.service.order.OrderStatisticsServiceTest.sendOrderStatisticsMail(OrderStatisticsServiceTest.java:83)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.IllegalStateException: For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters
	at org.springframework.data.jpa.repository.query.QueryParameterSetterFactory.lambda$1(QueryParameterSetterFactory.java:136)
	at java.base/java.util.Optional.orElseThrow(Optional.java:403)
	at org.springframework.data.jpa.repository.query.QueryParameterSetterFactory.getRequiredName(QueryParameterSetterFactory.java:136)
	at org.springframework.data.jpa.repository.query.QueryParameterSetterFactory.findParameterForBinding(QueryParameterSetterFactory.java:127)
	at org.springframework.data.jpa.repository.query.QueryParameterSetterFactory$BasicQueryParameterSetterFactory.create(QueryParameterSetterFactory.java:249)
	at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createQueryParameterSetter(ParameterBinderFactory.java:146)
	at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createSetters(ParameterBinderFactory.java:135)
	at org.springframework.data.jpa.repository.query.ParameterBinderFactory.createQueryAwareBinder(ParameterBinderFactory.java:102)
	at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.createBinder(AbstractStringBasedJpaQuery.java:143)
	at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.createBinder(AbstractStringBasedJpaQuery.java:139)
	at org.springframework.data.util.Lazy.getNullable(Lazy.java:135)
	at org.springframework.data.util.Lazy.get(Lazy.java:113)
	at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.doCreateQuery(AbstractStringBasedJpaQuery.java:130)
	at org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:243)
	at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:129)
	at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:92)
	at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:152)
	at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:140)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:169)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:148)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138)
	... 12 more

엥... 뭔데 이게...

이번에도 폭풍 서칭...

프로젝트 > Properties > Java Compiler > Store information about method parameters (usable via reflection) 체크 > Apply

하면 오류가 사라진다.

📌 Test Double

스턴트맨(대역)을 영어로 Stunt Double이라고 한다.

Stunt Double에서 차용된 단어가 Test Double이다.

📑 마틴 파울러의 글

https://martinfowler.com/articles/mocksArentStubs.html
👍 테스트 더블의 정의와 종류가 잘 설명되어 있다.

📍 Test Double의 특징

Dummy

: 아무 것도 하지 않는 깡통 객체

Fake

: 단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체 (ex. FakeRepository)

Stub

: 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다.

Spy

: Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체. 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다.

Mock

: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체

💡 Stub과 Mock

가짜 객체이고 기댓값을 미리 정의하는 것은 비슷하나
검증하려는 목적이 다르다.

  • Stub : 상태 검증 (State Verification)
  • Mock : 행위 검증 (Behavior Verification)

📌 BDDMockito

: 행동주도개발

// given
Mockito.when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
	.thenReturn(true);
    	
BDDMockito.given(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
	.willReturn(true);

given절에서 when을 사용하면 어색하다.

그래서 Mockito를 상속받고 있는 BDDMockito를 사용하여 given으로 표시한다.
BDDMockitoMockito와 기능은 동일하나 이름만 바꾼 것이다.

📌 Classicist VS. Mockist

  • Mockist : 모든 걸 Mocking위주로 하자!단위 테스트 시 잘 되었으니 통합 테스트 시에는 기능 보장된 것들은 mocking처리를 해서 우리가 잘라서 테스트를 간단하게 하자!
  • Classicist : 다 Mocking해버리면 프로덕션 코드가 동작할 때 진짜 객체들이 협업을 하는데 어떻게 보장하느냐! 진짜 객체로 최대한 테스트를 해봐야 한다! 꼭 필요한 경우에만 Mocking하자!

❓ 실제 프로덕션 코드에서 런타임 시점에 일어날 일을 정확하게 Stubbing 했다고 단언할 수 있는가?

❗ Classicist 강사님 : 리스크를 안고 갈 바에는 비용을 조금이라도 더 들여서 통합 테스트에서 최대한 넓은 범위의 실제 객체/구현체를 불러와서 테스트하는 것이 훨씬 낫다!


📑 출처

profile
나는 말하는 감자다

0개의 댓글