AI 코딩 어시스턴트 잠금 해제 : 단위 테스트를 생성합니다
이 시리즈 의이 부분에서는 AI 코딩 어시스턴트를 사용하여 Spring Boot 응용 프로그램에 대한 단위 테스트를 만들려고합니다. 목표는 단순히 작업 단위 테스트를 만드는 것이 아니라 질적 단위 테스트를 만드는 것입니다. 즐기다!
소개
AI 코딩 어시스턴트의 도움으로 기본 스프링 부트 응용 프로그램에 대한 단위 테스트를 생성하려고합니다. 응답이 평가되고 다른 기술이 적용되며, 필요한 경우 응답을 개선하는 데 사용할 수 있습니다. 이 블로그는 시리즈의 일부입니다. 이전 부분은 여기에서 읽을 수 있습니다.
기본 스프링 부팅 응용 프로그램이 이전 블로그에서 생성되었습니다. 이 응용 프로그램은 단위 테스트를 생성하는 데 사용됩니다. 출발점은 해당 게시물의 마지막 지점입니다.
작업은 Intellij Ide DevoxxGenie AI 코딩 보조원으로 실행됩니다.
이 블로그에 사용 된 소스는 Github에서 제공됩니다. 프로젝트가 어떻게 만들어 졌는지에 대한 설명은 여기에서 찾을 수 있습니다.
전제 조건
이 블로그를 읽기위한 전제 조건은 다음과 같습니다.
- 기본 코딩 지식;
- AI 코딩 비서의 기본 지식;
- DevoxxGenie에 대한 기본 지식, 자세한 내용은 이전 블로그를 읽거나 Devoxx에서 주어진 컨퍼런스 토크를 볼 수 있습니다.
컨트롤러 테스트를 생성합니다
에 대한 단위 테스트를 만들어 봅시다 CustomersController
수업.
즉각적인
명령 유틸리티 /test
다음 프롬프트로 확장됩니다.
Write a unit test for this code using JUnit.
이 프롬프트를 사용하면보다 일반적인 테스트가 생성됩니다. 이것은이 사용 사례에 유용하지 않습니다.
컨트롤러 테스트의 경우 다음 추가 요구 사항이 적용됩니다.
- webmvctest를 사용해야합니다.
- mockmvc를 사용해야합니다.
- Assertj Assertions를 사용해야합니다.
파일 열기 CustomersController
프롬프트를 입력하십시오.
Write a unit test for this code using JUnit.
Use WebMvcTest.
Use MockMvc.
Use AssertJ assertions.
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
패키지를 만듭니다 com/mydeveloperplanet/myaicodeprojectplanet/controller
에서 src/test/java
디렉토리 및 파일에 응답을 복사하십시오 CustomersControllerTest
.
일부 문제가 있습니다.
- 수입품을 추가해야하지만 Intellij는이를 도와 줄 것입니다.
- 그만큼
convertToDomainModel
.의 방법CustomersController
사용되지만 이것은 개인 방법입니다.
즉각적인
후속 프롬프트를 입력하십시오.
The convertToDomainModel method is used, but is not accessible.
Create the Customer object in a similar way as the openAPICustomer object.
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
에이 convertToDomainModel
방법이 테스트에 추가됩니다.
다시 수입품을 수정하십시오. 테스트 클래스는 지금 컴파일되며 테스트를 실행하고 성공할 수 있습니다.
테스트 자체를 살펴보면 상당히 괜찮아 보이지만 코드는 개선 될 수 있습니다.
- 그만큼
testCustomersPost
요청에 ID를 포함해서는 안됩니다. - 게시물 및 Put JSON 컨텐츠는 텍스트 블록이 될 수 있습니다.
- 어쩌면
convertToDomainModel
구현에 너무 가깝기 때문에 사용해서는 안됩니다.
테스트 품질
테스트 품질을 확인할 수 있습니까? 물론 돌연변이 테스트를 통해 테스트의 품질을 테스트 할 수 있습니다 ( pitest-maven
플러그인).
POM의 빌드 섹션에 다음 플러그인을 추가하십시오.
org.pitest
pitest-maven
${pitest-maven.version}
org.pitest
pitest-junit5-plugin
${pitest-junit5-plugin.version}
mutationCoverage
+auto_threads
-XX:+EnableDynamicAgentLoading
빌드를 실행하십시오.
디렉토리에서 target/pit-reports
보고서를 찾을 수 있습니다.
돌연변이 테스트 결과는 100% 라인 커버리지 및 86% 돌연변이 범위를 보여줍니다. controller
패키지. 이것은 꽤 좋습니다. 이것은 테스트가 여전히 개선 될 수 있음을 의미합니다.

서비스 테스트를 생성하십시오
에 대한 단위 테스트를 만들어 봅시다 CustomerServiceImpl
수업.
즉각적인
열기 CustomerServiceImpl
파일을 제출하고 프롬프트를 입력하십시오.
Write a unit test for this code using JUnit.
Use AssertJ assertions.
Use Mockito.
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
패키지를 만듭니다 com.mydeveloperplanet.myaicodeprojectplanet.service
디렉토리에서 src/test/java
응답을 파일에 복사하십시오 CustomerServiceImplTest
.
일부 문제가 있습니다.
- 수입품을 수정해야합니다.
- a를 만들 때 일부 컴파일 오류가 존재합니다
Customer
. 생성자는 ID, 이름 및 성이 필요합니다. - 그만큼
setUp
클래스에 주석을 달면 메소드가 실제로 필요하지 않습니다.@ExtendWith(MockitoExtension.class)
즉각적인
전체 프로젝트가 프롬프트 컨텍스트에 추가되면 차이가 있는지 살펴 보겠습니다.
새 채팅 창을 열고 전체 프로젝트를 프롬프트 컨텍스트에 추가하십시오. 프롬프트를 입력하십시오.
Write a unit test for class CustomerServiceImpl using JUnit.
Use AssertJ assertions.
Use Mockito.
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
이것은 크게 변하지 않았습니다. 수입 수가 줄었지 만 Customer
물체가 존재합니다.
이것을 수동으로 수정하고 테스트를 실행하십시오.
3 개의 테스트가 통과되고 2 개는 실패합니다.
시험 testCreateCustomer
다음 오류로 인해 실패합니다.
com.mydeveloperplanet.myaicodeprojectplanet.service.customerserviceimpltest.testcreateCustomer (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) “data-lang =”text/x-java “>
org.opentest4j.AssertionFailedError:
expected: "Customer{id=1, firstName="John", lastName="Doe"} (Customer@2160e52a)"
but was: "Customer{id=1, firstName="John", lastName="Doe"} (Customer@3d7cc3cb)"
Expected :Customer{id=1, firstName="John", lastName="Doe"}
Actual :Customer{id=1, firstName="John", lastName="Doe"}
at com.mydeveloperplanet.myaicodeprojectplanet.service.CustomerServiceImplTest.testCreateCustomer(CustomerServiceImplTest.java:63)
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)
도메인 객체가 구현되지 않습니다 equals
방법. 지금은 테스트에서 수정하십시오.
assertThat(result.getId()).isEqualTo(1L);
assertThat(result.getFirstName()).isEqualTo("John");
assertThat(result.getLastName()).isEqualTo("Doe");
시험 testUpdateCustomer
다음 오류로 인해 실패합니다.
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.mydeveloperplanet.myaicodeprojectplanet.service.CustomerServiceImplTest.testUpdateCustomer(CustomerServiceImplTest.java:76)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(any(), eq("String by matcher"));
For more info see javadoc for Matchers class.
at com.mydeveloperplanet.myaicodeprojectplanet.repository.CustomerRepository.updateCustomer(CustomerRepository.java:45)
at com.mydeveloperplanet.myaicodeprojectplanet.service.CustomerServiceImplTest.testUpdateCustomer(CustomerServiceImplTest.java:76)
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)
즉각적인
이것을 고치겠습니다. 프롬프트를 입력하십시오.
testUpdateCustomer fails due to the following error:
```
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.mydeveloperplanet.myaicodeprojectplanet.service.CustomerServiceImplTest.testUpdateCustomer(CustomerServiceImplTest.java:76)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(any(), eq("String by matcher"));
For more info see javadoc for Matchers class.
at com.mydeveloperplanet.myaicodeprojectplanet.repository.CustomerRepository.updateCustomer(CustomerRepository.java:45)
at com.mydeveloperplanet.myaicodeprojectplanet.service.CustomerServiceImplTest.testUpdateCustomer(CustomerServiceImplTest.java:76)
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)
```
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
수정 사항을 적용하면 이제 테스트가 성공적입니다.
테스트 품질
빌드를 실행하고 테스트 품질을 확인하십시오.
돌연변이 테스트 결과는 100% 라인 적용 범위와 100% 돌연변이 범위를 보여줍니다. service
패키지.

저장소 테스트를 생성합니다
에 대한 단위 테스트를 만들어 봅시다 CustomerRepository
수업.
즉각적인
새 채팅 창을 열고 전체 프로젝트를 프롬프트 컨텍스트에 추가하십시오. 프롬프트를 입력하십시오.
Write a unit test for class CustomerRepository using JUnit.
Use @JooqTest.
Use Testcontainers.
Use AssertJ assertions.
Use Mockito.
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
패키지를 만듭니다 com.mydeveloperplanet.myaicodeprojectplanet.repository
디렉토리에서 src/test/java
응답을 파일에 복사하십시오 CustomerRepositoryTest
.
POM에 TestContainers의 종속성을 추가하십시오.
org.testcontainers
postgresql
test
org.testcontainers
junit-jupiter
test
또한, @JdbcTest
대신 사용됩니다 @JooqTest
. 이것을 수정하십시오.
그러나 모든 테스트는 실패합니다. 오류는 데이터 소스에 문제가 있음을 보여줍니다.
즉각적인
후속 프롬프트를 입력하십시오.
the following error occurs when running the tests:
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
응답은 올바른 방향을 가리 킵니다. 그러나 해결책은 POM에 다음의 종속성을 추가하는 것입니다.
org.springframework.boot
spring-boot-testcontainers
test
다음과 같이 TestContainer 설정을 교체합니다.
@Container
@ServiceConnection
public static PostgreSQLContainer> postgreSQLContainer = new PostgreSQLContainer("postgres:latest");
이 테스트를 실행할 때만 createCustomer
테스트가 성공적입니다. 테스트가 특정 데이터가 데이터베이스에 있다고 가정하기 때문에 다른 모든 테스트는 실패합니다.
즉각적인
새 채팅 창을 열고 CustomerRepositoryTest
프롬프트를 입력하십시오.
The tests make assumptions about data being present in the database.
Ensure that these preconditions can be met.
응답
응답은 여기에서 볼 수 있습니다.
이 응답은 그다지 유용하지 않으며 데이터를 삽입합니다. setUp
메소드 및 그것은 데이터를 제거합니다 tearDown
방법, 그러나 순수한 JDBC를 통해 Jooq를 사용하지 않습니다.
테스트에 다음을 추가하십시오.
@BeforeEach
public void setUp() {
// Initialize the database schema and insert test data here if needed
dslContext.insertInto(Customers.CUSTOMERS,
Customers.CUSTOMERS.FIRST_NAME,
Customers.CUSTOMERS.LAST_NAME)
.values("Vince", "Doe")
.execute();
}
@AfterEach
public void tearDown() {
dslContext.truncateTable(CUSTOMERS).cascade().execute();
}
테스트를 실행하면 모두 성공합니다.
테스트 품질
이것은 통합 테스트이며, 돌연변이 테스트는 통합 테스트와 잘 어울리지 않습니다. 그러나 시도해 보면 돌연변이 테스트 결과가 94% 라인 커버리지를 보여줍니다 ( RuntimeException
S는 테스트되지 않았고 리포지토리 패키지에 대한 100% 돌연변이 범위. 테스트는 개선 될 수 있지만이 초기 결과는 이미 매우 좋습니다.

통합 테스트를 생성합니다
Spring Boot 응용 프로그램에 대한 통합 테스트를 작성하겠습니다.
즉각적인
전체 프로젝트를 프롬프트 컨텍스트에 추가하고 프롬프트를 입력하십시오.
Write an integration test using JUnit.
Use SpringBootTest.
Use Testcontainers.
Use WebTestClient.
Use AssertJ.
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
각 CRUD 작업에 대해 테스트가 포함되어 있으며 좋습니다. POM에 웹 플럭스 종속성을 추가해야합니다.
org.springframework.boot
spring-boot-starter-webflux
test
그럼에도 불구하고 몇 가지 문제가 있습니다.
- TestContainers는 사용되지 않습니다.
- 다시, 설정이 비어 있습니다.
testCustomersGet
그리고testCustomersIdGet
컴파일하지 마십시오.
즉각적인
후속 프롬프트를 입력하십시오.
testCustomersGet does not compile, the `first` method does not exist.
testCustomersIdGet does not compile, the `satisfies` method cannot be invoked.
Fix these issues.
응답
응답은 여기에서 볼 수 있습니다.
응답을 적용하십시오
응답은 도움이되지 않습니다. 수동으로 수정하십시오.
@Test
public void testCustomersGet() {
webTestClient.get()
.uri("/customers")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBodyList(Customer.class)
.hasSize(1) // Assuming there is one customer in the database for testing
.consumeWith(response -> {
List customers = response.getResponseBody();
assertThat(customers).isNotNull();
assertThat(customers).hasSize(1);
Customer customer = customers.get(0);
assertThat(customer.getId()).isEqualTo(1L);
assertThat(customer.getFirstName()).isEqualTo("Vince");
assertThat(customer.getLastName()).isEqualTo("Doe");
});
}
그리고 testCustomersIdGet
.
@Test
public void testCustomersIdGet() {
webTestClient.get()
.uri("/customers/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(Customer.class)
.consumeWith(response -> {
Customer customer = response.getResponseBody();
assertThat(customer).isNotNull();
assertThat(customer.getId()).isEqualTo(1L);
assertThat(customer.getFirstName()).isEqualTo("Vince");
assertThat(customer.getLastName()).isEqualTo("Doe");
});
}
또한 TestContainers 및 the를 추가하십시오 setUp
그리고 tearDown
방법과 마찬가지로 CustomerRepositoryTest
.
테스트를 실행하십시오. 모든 테스트는 제외하고 성공합니다 testCustomersIdGet
그리고 testCustomersGet
. 이것은 삽입 된 데이터가 setUp
메소드에는 1과 같은 ID가 포함되어 있습니다. 이것은 사실이 아닙니다.
ID를 변수로 저장하여 수동으로 수정하십시오. insertedId
그에 따라 테스트를 수정하십시오.
완전히 옳지 않은 또 다른 것은 com.mydeveloperplanet.myaicodeprojectplanet.model.Customer
대신 com.mydeveloperplanet.myaicodeprojectplanet.openapi.model.Customer
. 또한 수동으로 수정하십시오.
그리고 테스트는 임의의 포트에서 실행해야합니다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
테스트를 실행하면 모두 성공합니다. 아마도 개선 될 수 있지만 골격이 정확합니다.
테스트 품질
전체 테스트 품질은 크게 향상되지 않았지만 이는 통합 테스트의 목표는 아닙니다.

결론
생성 장치 (통합) 테스트는 매우 잘 작동합니다. LLM에 어떤 프레임 워크, 종속성 등을 알려야합니다. 많은 사람들이 사용할 수 있으며 명확하게 지정하지 않으면 LLM은 당신을 위해 하나만 선택합니다. 때로는 몇 가지 문제를 해결하기 위해 수동 개입이 필요합니다.
Post Comment