본문 바로가기
SpringBoot

일단 써

by 오렌지마끼야또 2024. 12. 19.
728x90
반응형

 

 

 

 

spock 통합테스트를 하려는 과정이다.

 

 

1.

gradle로 실행하는 것과 intellij IDEA(junit) 으로 실행하는게 있다.

(sttings - gradle - run test using 에서 둘중에 하나 고르기)

하지만 gradle로 테스트하면 edit configuration에 세팅한 VM options 를 못 읽는다.

VM options에 jasypt.encryptor.password 를 세팅해놨는데 못읽으니 무용지물이다.

DB 접속정보 jasypt로 암호화해놓음.

그렇다고 build.gradle에 jasypt.encryptor.password 를 박아넣고 push 할 수는 없지 않은가..

gradle 로 하면 reporting도 해줘서 index.html로 결과를 볼 수 있으니 좋긴 하다만 어쩔 수 없다.

intellij IDEA(junit) 로 테스트하자.

이걸로도 결과 파일 뽑을 수 있다.

export test results 누르면 된다.

근데 이걸로 html 다운받으면 희한하게 springboot 로그를 같이 출력한다.. 왜 그렇게 만들었냐 JetBrains

그래서 인터넷 뒤져보다가 github에서 이 html을 만드는 xsl 파일을 찾았다. (올려주신분 압도적 감사)

그래서 그 xsl 파일을 수정해서 로그 출력 안하게 하고, 글씨 크기 바꾸고 깔끔하게 바꿨다.(한줄로 간단하게 썼지만 이 과정도 매우 오래걸렸다.. 어디서 어떻게 출력하는지 명시되어있지 않고 숨겨져 있어서 찾는데 한참 걸림)

export test results 할 때 custom, apply XSL template 선택해서 이 수정한 xsl로 뽑으면 된다.

 

 

 

 

2. (~ing)

나는 특정 조건에 따라 테스트 클래스에 @SpringBootTest, @DataJpaTest 둘 중에 하나를 적용하고 싶었다.

 

@Conditional 은 스프링 컨텍스트가 확인하는 것이기 때문에 스프링 컨텍스트가 생성된 이후에 동작한다.

@SpringBootTest, @DataJpaTest 이것들이 스프링 컨텍스트를 만드는 어노테이션인데 스프링 컨텍스트가 없는 시점에 이걸 조건적으로 적용하고 싶은거니까 @Conditional 은 못쓴다.

 


컴파일타임에 어노테이션이 고정된다(소스코드가 정해진다)
런타임에 동적으로 어노테이션을 적용할 수 없다
junit의 @ExtendWith 으로 조건적으로 액티브 프로필을 적용할 순 있다.

@ExtendWith(ConditionalTestExtension.class) 으로 쓰고

ConditionalTestExtension 파일 만들어서 다른 어노테이션의 falg를 읽던, 변수를 읽던 해서 조건 만듦.
하지만 프로필을 여러개 만들어야 하고 각각의 프로필을 사용할 테스트 클래스 또한 여러개 만들어야 한다.

(@ActiveProfiles("AAA"), @SpringBootTest 붙은 클래스 하나, @ActiveProfiles("BBB"), @DataJpaTest 붙은 클래스 하나)

똑같은 코드를 여러개 만드는건 불필요하다.

@ExtendWith는 스프링 컨텍스트가 생성되기 전에 동작하기 때문에 스프링 컨텍스트 없이도 된다.

1. 컴파일타임

     - 어노테이션 고정
2. 런타임

     -  @ExtendWith 확인
     -  정해진 프로필에 따라 실행할 테스트 클래스 결정
     -  클래스에 적용된 어노테이션에 따라 스프링 컨텍스트를 띄우거나 안띄우거나

 

컴파일타임에 어노테이션을 동적으로 처리하는 방법은 Annotation Processor 를 사용하는 것이다.

위에 썼듯 컴파일타임에 어노테이션이 고정되는 건데 Processor 가 먼저 돌아서 조건적으로 어노테이션 정하는 것

 

 

Annotation Processor 동작 과정
컴파일타임. @ABCTest를 감지해서 값에 따라 어노테이션 추가하게 작성해놓음
1. 자바 컴파일러가 소스코드를 컴파일할 때 @ABCTest 애노테이션을 발견
2. SpockTestProcessor가 활성화되어 process() 메서드 실행
3. @ABCTest 의 flag 값에 따라
   - false면 @SpringBootTest 추가
   - true면 @DataJpaTest 추가

 

런타임에는 이미 @SpringBootTest가 추가된 상태로 시작
이후 스프링 테스트 프레임워크는 추가된 @SpringBootTest를 기반으로 스프링 컨텍스트 구성

 

 

 

 

@SpringBootTest 애노테이션 처리 과정

● 컴파일 타임
1. Java 컴파일러가 바이트코드로 변환하기 전에 Annotation Processor 동작 (있으면)
2. Java 컴파일러가 소스 코드를 바이트코드로 변환
3. 애노테이션의 문법 검사만 수행
4. @SpringBootTest 등의 애노테이션은 메타데이터로 클래스 파일에 저장

● 런타임 - JUnit 테스트 실행 시
1. JUnit Platform이 테스트 클래스 로드
2. @ExtendWith(SpringExtension.class)에 의해 SpringExtension이 활성화
   - @SpringBootTest에 기본으로 적용되어 있어서 동작하는 @ExtendWith (얘도 있으면 동작)
3. Spring TestContext Framework 동작
   - TestContextManager가 테스트 컨텍스트 관리
   - @SpringBootTest 애노테이션 감지
4. SpringBootTestContextBootstrapper 동작
   - 스프링 부트 테스트에 필요한 설정 로드
   - ApplicationContext 생성 및 구성
5. 실제 스프링 컨텍스트 초기화



 

settings - Annotation Processors 에 Enable annotation processing 체크하래서 했는데 왜 안돼.

intellij IDEA(junit) 로 설정하면 변경된 파일만 다시 컴파일하는 증분빌드래서 Rebuild Project 도 했는데 왜 안돼.

 

 

 

 

 

런타임에 수정하기

 

@ExtendWith  은 클래스가 JVM에 로드되기 전에 실행되므로 바이트코드 수정 가능

 

Java Agent 방식은 수동으로 컴파일하고, 수동으로 jar파일 만들어줘야함. gradle이 아니라 junit으로 테스트를 실행하는 것이기 때문에.


run test using: Intellij IDEA 는 내부적으로 JUnit test runner를 사용
JUnit 테스트 러너는 컴파일하지 않음.

이미 컴파일된 클래스를 테스트하는 것.

나는 지금 spock 통합테스트를 작성하고 있기 때문에 특정 클래스를 테스트하는게 아니므로 에초에 "컴파일된 클래스" 랄게 없다.

지금까지 뻘짓했네

 

Groovy는 JVM 기반의 동적 언어이다.

Spock에서 작성된 테스트는 Groovy 언어로 작성되므로, 코드가 컴파일된 클래스 파일(바이트코드)이 아닌 소스 코드 자체에서 동적으로 해석되어 실행된다.

 

일반적으로 자바 코드는 컴파일러에 의해 바이트코드로 변환되어 JVM에서 실행된다. 하지만 Groovy는 기본적으로 런타임에 해석되는 방식이기 때문에, Groovy로 작성된 코드 자체는 바이트코드로 미리 컴파일되지 않는다. 대신 실행 시점에 Groovy가 동적으로 처리하여 JVM이 해당 코드에 대해 바이트코드를 생성한다.

 

Spock 테스트는 JVM 런타임에서 동적으로 컴파일되고 테스트가 실행된다.

 

따라서 Spock 테스트 자체에 대한 바이트코드는 존재한다. 다만, Spock 테스트 클래스가 자바와 다르게 컴파일된 코드로 미리 존재하지 않고, 실행 중에 동적으로 처리되므로, 직접적인 바이트코드를 사전에 생성하지 않는 특성이 있다.

 

 

jvm 런타임에 리플렉션을 사용하여 어노테이션 추가하기
리플렉션이나 Groovy의 AST 변환

실행 시점
1. Groovy 컴파일러 동작: Groovy 컴파일러가 소스 코드를 분석하고, AST를 생성하며, 지정된 변환기를 실행합니다.
2. CANONICALIZATION 단계: 이 단계에서 코드 구조가 정리되고, 클래스 간의 상호 참조가 설정됩니다. 이때 ConditionalClassAnnotationASTTransformation이 호출됩니다.

 

실행 조건
@GroovyASTTransformation으로 선언된 클래스는 AST 변환기로 등록되어야 실행됩니다. 이를 위해서는 다음이 필요합니다:
1. 변환기 등록
META-INF/services/org.codehaus.groovy.transform.ASTTransformation 파일을 생성하고, 해당 파일에 변환기 클래스의 **FQCN(Full Qualified Class Name)**을 추가해야 합니다.

예:
META-INF/services/org.codehaus.groovy.transform.ASTTransformation 파일 내용:
com.example.ast.ConditionalClassAnnotationASTTransformation

 

 

주요 CompilePhase 설명:
 - CompilePhase.INITIALIZATION: 초기화 단계로, AST 트리의 최상위 구조가 설정됩니다. 변수 선언 등의 기본적인 구조가 이 단계에서 이루어집니다.
 -  CompilePhase.SEMANTIC_ANALYSIS: 의미 분석 단계로, 변수나 메서드의 타입과 같은 유효성 검사를 합니다. 이 시점에서는 코드가 유효한지 분석하지만, 트리의 구조가 아직 확정되지 않은 상태입니다.
 -  CompilePhase.CANONICALIZATION: 이 시점에서는 AST 트리가 정리되며, 주석, 메서드 호출, 선언된 변수가 최적화되고, 코드가 정리된 후입니다. 변환을 위해 가장 많이 사용되는 시점입니다.
 -  CompilePhase.CLASS_GENERATION: 이 단계는 실제로 바이트코드를 생성하는 시점으로, 트리가 이미 완성된 후입니다. 이 시점 이후에는 트리 구조가 변경될 수 없습니다.

 

 

Junit test runner(Intellij IDEA)로 groovy 테스트코드를 실행할 때

정적 컴파일 타임 없음

Gradle과 같은 빌드 도구로 테스트를 실행하는 것이 아닌
Junit test runner로 실행하는 것이기 때문에
별도의 컴파일타임 없음

----------------------------------------------

런타임

필요시 @GroovyASTTransformation 로 AST 조작
완료 후 Groovy 컴파일러에 의해 동적으로 바이트코드로 컴파일됨
동적 컴파일 의미 : 코드를 실행하기 전에 별도로 컴파일 과정을 거치지 않고, 
런타임 시점에 Groovy 컴파일러가 즉석에서 바이트코드를 생성한다는 것
이 후 JUnit Test Runner 실행
JVM이 Groovy 컴파일러가 생성한 .class 파일을 기반으로 바로 테스트 실행

 

 

 

 

 

728x90
반응형

댓글