Recently, I was looking for a solution to write integration tests for my Spring Boot-based application which was using PostgreSQL. I had the following requirements for this task:
- The integration tests should use the same database as in production (referring to the Twelve-Factor App I wanted to keep my environment during the tests as similar as possible to the production environment)
- The tests should not need any pre-setup before running (e.g. like manually setting up a test database)
- The tests should use my Flyway DDL scripts and create-drop (
spring.jpa.hibernate.ddl-auto
) shouldn’t be activated for my tests - Good integration with the excellent Spring tests ecosystem
For this task, I found the awesome project: Test containers. The project describes itself as the following:
“Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.”
With this blog post, we’ll use Testcontainers to write integration tests with JUnit using a real database (meaning not mocked or in-memory) for a Spring Boot application.
UPDATE: Time flies and a lot was introduced since I published this blog post. Therefore I added integration test examples for different combinations of JUnit 4 & 5 and Spring Boot versions.
Setup Testcontainers in Spring Boot project
For using this dependency you need to have Docker on your local machine/on your build server (Jenkins etc.).
With Testcontainers you can use a @ClassRule
or @Rule
on each of your integration tests and define the Docker image for your test (valid for JUnit 4.12).
For MySQL and PostgreSQL and there are already built-in solutions but you are free to use an image of your choice like the following:
<span>// generic container for self-defined Docker images</span><span>@ClassRule</span><span>public</span> <span>static</span> <span>GenericContainer</span> <span>redis</span> <span>=</span> <span>new</span> <span>GenericContainer</span><span>(</span><span>"redis:3.0.6"</span><span>).</span><span>withExposedPorts</span><span>(</span><span>6379</span><span>);</span><span>// built-in containers</span><span>@ClassRule</span><span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>().</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span><span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span><span>// generic container for self-defined Docker images</span> <span>@ClassRule</span> <span>public</span> <span>static</span> <span>GenericContainer</span> <span>redis</span> <span>=</span> <span>new</span> <span>GenericContainer</span><span>(</span><span>"redis:3.0.6"</span><span>).</span><span>withExposedPorts</span><span>(</span><span>6379</span><span>);</span> <span>// built-in containers</span> <span>@ClassRule</span> <span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>().</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span> <span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span>// generic container for self-defined Docker images @ClassRule public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(6379); // built-in containers @ClassRule public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory") .withUsername("inmemory");
Enter fullscreen mode Exit fullscreen mode
To run the integrations tests after your unit tests, simply add maven-failsafe-plugin
to your project. In addition, make sure your integration tests have IT
as a postfix:
<span><build></span><span><plugins></span><span><plugin></span><span><groupId></span>org.springframework.boot<span></groupId></span><span><artifactId></span>spring-boot-maven-plugin<span></artifactId></span><span></plugin></span><span><plugin></span><span><groupId></span>org.apache.maven.plugins<span></groupId></span><span><artifactId></span>maven-failsafe-plugin<span></artifactId></span><span><version></span>3.0.0-M4<span></version></span><span><executions></span><span><execution></span><span><goals></span><span><goal></span>integration-test<span></goal></span><span><goal></span>verify<span></goal></span><span></goals></span><span></execution></span><span></executions></span><span></plugin></span><span></plugins></span><span></build></span><span><build></span> <span><plugins></span> <span><plugin></span> <span><groupId></span>org.springframework.boot<span></groupId></span> <span><artifactId></span>spring-boot-maven-plugin<span></artifactId></span> <span></plugin></span> <span><plugin></span> <span><groupId></span>org.apache.maven.plugins<span></groupId></span> <span><artifactId></span>maven-failsafe-plugin<span></artifactId></span> <span><version></span>3.0.0-M4<span></version></span> <span><executions></span> <span><execution></span> <span><goals></span> <span><goal></span>integration-test<span></goal></span> <span><goal></span>verify<span></goal></span> <span></goals></span> <span></execution></span> <span></executions></span> <span></plugin></span> <span></plugins></span> <span></build></span><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.0.0-M4</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Enter fullscreen mode Exit fullscreen mode
Basic application integration test with Testcontainers
Using: JUnit 4.12 and Spring Boot < 2.2.6
Let’s start with the integration test each Spring Boot application contains out-of-the-box. This integration test verifies that Spring can create the context and start the application.
As our application requires a PostgreSQL to be available during startup, we can provide one using Testcontainers. Overriding the properties to use the PostgreSQL database spawned by Testcontainers is as easy as the following:
<span>@RunWith</span><span>(</span><span>SpringRunner</span><span>.</span><span>class</span><span>)</span><span>@SpringBootTest</span><span>(</span><span>webEnvironment</span> <span>=</span> <span>SpringBootTest</span><span>.</span><span>WebEnvironment</span><span>.</span><span>RANDOM_PORT</span><span>)</span><span>@ContextConfiguration</span><span>(</span><span>initializers</span> <span>=</span> <span>IntegrationTest</span><span>.</span><span>Initializer</span><span>.</span><span>class</span><span>)</span><span>public</span> <span>class</span> <span>ApplicationIT</span> <span>{</span><span>@ClassRule</span><span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>().</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span><span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span><span>public</span> <span>static</span> <span>class</span> <span>Initializer</span> <span>implements</span> <span>ApplicationContextInitializer</span><span><</span><span>ConfigurableApplicationContext</span><span>></span> <span>{</span><span>@Override</span><span>public</span> <span>void</span> <span>initialize</span><span>(</span><span>ConfigurableApplicationContext</span> <span>configurableApplicationContext</span><span>)</span> <span>{</span><span>TestPropertyValues</span> <span>values</span> <span>=</span> <span>TestPropertyValues</span><span>.</span><span>of</span><span>(</span><span>"spring.datasource.url="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getJdbcUrl</span><span>(),</span><span>"spring.datasource.password="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getPassword</span><span>(),</span><span>"spring.datasource.username="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getUsername</span><span>()</span><span>);</span><span>values</span><span>.</span><span>applyTo</span><span>(</span><span>configurableApplicationContext</span><span>);</span><span>}</span><span>}</span><span>@Ŧest</span><span>public</span> <span>void</span> <span>contextLoads</span><span>()</span> <span>{</span><span>}</span><span>}</span><span>@RunWith</span><span>(</span><span>SpringRunner</span><span>.</span><span>class</span><span>)</span> <span>@SpringBootTest</span><span>(</span><span>webEnvironment</span> <span>=</span> <span>SpringBootTest</span><span>.</span><span>WebEnvironment</span><span>.</span><span>RANDOM_PORT</span><span>)</span> <span>@ContextConfiguration</span><span>(</span><span>initializers</span> <span>=</span> <span>IntegrationTest</span><span>.</span><span>Initializer</span><span>.</span><span>class</span><span>)</span> <span>public</span> <span>class</span> <span>ApplicationIT</span> <span>{</span> <span>@ClassRule</span> <span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>().</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span> <span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span> <span>public</span> <span>static</span> <span>class</span> <span>Initializer</span> <span>implements</span> <span>ApplicationContextInitializer</span><span><</span><span>ConfigurableApplicationContext</span><span>></span> <span>{</span> <span>@Override</span> <span>public</span> <span>void</span> <span>initialize</span><span>(</span><span>ConfigurableApplicationContext</span> <span>configurableApplicationContext</span><span>)</span> <span>{</span> <span>TestPropertyValues</span> <span>values</span> <span>=</span> <span>TestPropertyValues</span><span>.</span><span>of</span><span>(</span> <span>"spring.datasource.url="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getJdbcUrl</span><span>(),</span> <span>"spring.datasource.password="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getPassword</span><span>(),</span> <span>"spring.datasource.username="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getUsername</span><span>()</span> <span>);</span> <span>values</span><span>.</span><span>applyTo</span><span>(</span><span>configurableApplicationContext</span><span>);</span> <span>}</span> <span>}</span> <span>@Ŧest</span> <span>public</span> <span>void</span> <span>contextLoads</span><span>()</span> <span>{</span> <span>}</span> <span>}</span>@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ContextConfiguration(initializers = IntegrationTest.Initializer.class) public class ApplicationIT { @ClassRule public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer().withPassword("inmemory") .withUsername("inmemory"); public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues values = TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.password=" + postgreSQLContainer.getPassword(), "spring.datasource.username=" + postgreSQLContainer.getUsername() ); values.applyTo(configurableApplicationContext); } } @Ŧest public void contextLoads() { } }
Enter fullscreen mode Exit fullscreen mode
Basic application integration test with JUnit 5 and Spring Boot > 2.2.6
If your application uses JUnit 5, you can’t use the @ClassRule
anymore. Fortunately, Testcontainers provides a solution to write tests with JUnit Jupiter:
<span><dependency></span><span><groupId></span>org.testcontainers<span></groupId></span><span><artifactId></span>junit-jupiter<span></artifactId></span><span><version></span>${testcontainers.version}<span></version></span><span><scope></span>test<span></scope></span><span></dependency></span><span><dependency></span> <span><groupId></span>org.testcontainers<span></groupId></span> <span><artifactId></span>junit-jupiter<span></artifactId></span> <span><version></span>${testcontainers.version}<span></version></span> <span><scope></span>test<span></scope></span> <span></dependency></span><dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>${testcontainers.version}</version> <scope>test</scope> </dependency>
Enter fullscreen mode Exit fullscreen mode
With this dependency and a more recent version of Spring Boot (> 2.2.6) the basic integration test looks like the following:
<span>// JUnit 5 example with Spring Boot >= 2.2.6</span><span>@Testcontainers</span><span>@SpringBootTest</span><span>(</span><span>webEnvironment</span> <span>=</span> <span>WebEnvironment</span><span>.</span><span>RANDOM_PORT</span><span>)</span><span>public</span> <span>class</span> <span>ApplicationIT</span> <span>{</span><span>@Container</span><span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>()</span><span>.</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span><span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span><span>@DynamicPropertySource</span><span>static</span> <span>void</span> <span>postgresqlProperties</span><span>(</span><span>DynamicPropertyRegistry</span> <span>registry</span><span>)</span> <span>{</span><span>registry</span><span>.</span><span>add</span><span>(</span><span>"spring.datasource.url"</span><span>,</span> <span>postgreSQLContainer:</span><span>:</span><span>getJdbcUrl</span><span>);</span><span>registry</span><span>.</span><span>add</span><span>(</span><span>"spring.datasource.password"</span><span>,</span> <span>postgreSQLContainer:</span><span>:</span><span>getPassword</span><span>);</span><span>registry</span><span>.</span><span>add</span><span>(</span><span>"spring.datasource.username"</span><span>,</span> <span>postgreSQLContainer:</span><span>:</span><span>getUsername</span><span>);</span><span>}</span><span>@Test</span><span>public</span> <span>void</span> <span>contextLoads</span><span>()</span> <span>{</span><span>}</span><span>}</span><span>// JUnit 5 example with Spring Boot >= 2.2.6</span> <span>@Testcontainers</span> <span>@SpringBootTest</span><span>(</span><span>webEnvironment</span> <span>=</span> <span>WebEnvironment</span><span>.</span><span>RANDOM_PORT</span><span>)</span> <span>public</span> <span>class</span> <span>ApplicationIT</span> <span>{</span> <span>@Container</span> <span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>()</span> <span>.</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span> <span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span> <span>@DynamicPropertySource</span> <span>static</span> <span>void</span> <span>postgresqlProperties</span><span>(</span><span>DynamicPropertyRegistry</span> <span>registry</span><span>)</span> <span>{</span> <span>registry</span><span>.</span><span>add</span><span>(</span><span>"spring.datasource.url"</span><span>,</span> <span>postgreSQLContainer:</span><span>:</span><span>getJdbcUrl</span><span>);</span> <span>registry</span><span>.</span><span>add</span><span>(</span><span>"spring.datasource.password"</span><span>,</span> <span>postgreSQLContainer:</span><span>:</span><span>getPassword</span><span>);</span> <span>registry</span><span>.</span><span>add</span><span>(</span><span>"spring.datasource.username"</span><span>,</span> <span>postgreSQLContainer:</span><span>:</span><span>getUsername</span><span>);</span> <span>}</span> <span>@Test</span> <span>public</span> <span>void</span> <span>contextLoads</span><span>()</span> <span>{</span> <span>}</span> <span>}</span>// JUnit 5 example with Spring Boot >= 2.2.6 @Testcontainers @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class ApplicationIT { @Container public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer() .withPassword("inmemory") .withUsername("inmemory"); @DynamicPropertySource static void postgresqlProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl); registry.add("spring.datasource.password", postgreSQLContainer::getPassword); registry.add("spring.datasource.username", postgreSQLContainer::getUsername); } @Test public void contextLoads() { } }
Enter fullscreen mode Exit fullscreen mode
Integration test with JUnit 5 and Spring Boot < 2.2.6
If your application makes use of JUnit 5 but is using a Spring Boot version < 2.2.6, you don’t have access to the @DynamicPropertySource
feature.
A possible integration test to verify a REST API endpoint is working as expected looks like the following:
<span>// JUnit 5 example with Spring Boot < 2.2.6</span><span>@Testcontainers</span><span>@SpringBootTest</span><span>(</span><span>webEnvironment</span> <span>=</span> <span>WebEnvironment</span><span>.</span><span>RANDOM_PORT</span><span>)</span><span>@ContextConfiguration</span><span>(</span><span>initializers</span> <span>=</span> <span>DeletePersonIT</span><span>.</span><span>Initializer</span><span>.</span><span>class</span><span>)</span><span>public</span> <span>class</span> <span>DeletePersonIT</span> <span>{</span><span>@Container</span><span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>()</span><span>.</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span><span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span><span>@Autowired</span><span>private</span> <span>PersonRepository</span> <span>personRepository</span><span>;</span><span>@Autowired</span><span>public</span> <span>TestRestTemplate</span> <span>testRestTemplate</span><span>;</span><span>public</span> <span>static</span> <span>class</span> <span>Initializer</span> <span>implements</span> <span>ApplicationContextInitializer</span><span><</span><span>ConfigurableApplicationContext</span><span>></span> <span>{</span><span>@Override</span><span>public</span> <span>void</span> <span>initialize</span><span>(</span><span>ConfigurableApplicationContext</span> <span>configurableApplicationContext</span><span>)</span> <span>{</span><span>TestPropertyValues</span> <span>values</span> <span>=</span> <span>TestPropertyValues</span><span>.</span><span>of</span><span>(</span><span>"spring.datasource.url="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getJdbcUrl</span><span>(),</span><span>"spring.datasource.password="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getPassword</span><span>(),</span><span>"spring.datasource.username="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getUsername</span><span>()</span><span>);</span><span>values</span><span>.</span><span>applyTo</span><span>(</span><span>configurableApplicationContext</span><span>);</span><span>}</span><span>}</span><span>@Test</span><span>@Sql</span><span>(</span><span>"/testdata/FILL_FOUR_PERSONS.sql"</span><span>)</span><span>public</span> <span>void</span> <span>testDeletePerson</span><span>()</span> <span>{</span><span>testRestTemplate</span><span>.</span><span>delete</span><span>(</span><span>"/api/persons/1"</span><span>);</span><span>assertEquals</span><span>(</span><span>3</span><span>,</span> <span>personRepository</span><span>.</span><span>findAll</span><span>().</span><span>size</span><span>());</span><span>assertFalse</span><span>(</span><span>personRepository</span><span>.</span><span>findAll</span><span>().</span><span>contains</span><span>(</span><span>"Phil"</span><span>));</span><span>}</span><span>}</span><span>// JUnit 5 example with Spring Boot < 2.2.6</span> <span>@Testcontainers</span> <span>@SpringBootTest</span><span>(</span><span>webEnvironment</span> <span>=</span> <span>WebEnvironment</span><span>.</span><span>RANDOM_PORT</span><span>)</span> <span>@ContextConfiguration</span><span>(</span><span>initializers</span> <span>=</span> <span>DeletePersonIT</span><span>.</span><span>Initializer</span><span>.</span><span>class</span><span>)</span> <span>public</span> <span>class</span> <span>DeletePersonIT</span> <span>{</span> <span>@Container</span> <span>public</span> <span>static</span> <span>PostgreSQLContainer</span> <span>postgreSQLContainer</span> <span>=</span> <span>new</span> <span>PostgreSQLContainer</span><span>()</span> <span>.</span><span>withPassword</span><span>(</span><span>"inmemory"</span><span>)</span> <span>.</span><span>withUsername</span><span>(</span><span>"inmemory"</span><span>);</span> <span>@Autowired</span> <span>private</span> <span>PersonRepository</span> <span>personRepository</span><span>;</span> <span>@Autowired</span> <span>public</span> <span>TestRestTemplate</span> <span>testRestTemplate</span><span>;</span> <span>public</span> <span>static</span> <span>class</span> <span>Initializer</span> <span>implements</span> <span>ApplicationContextInitializer</span><span><</span><span>ConfigurableApplicationContext</span><span>></span> <span>{</span> <span>@Override</span> <span>public</span> <span>void</span> <span>initialize</span><span>(</span><span>ConfigurableApplicationContext</span> <span>configurableApplicationContext</span><span>)</span> <span>{</span> <span>TestPropertyValues</span> <span>values</span> <span>=</span> <span>TestPropertyValues</span><span>.</span><span>of</span><span>(</span> <span>"spring.datasource.url="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getJdbcUrl</span><span>(),</span> <span>"spring.datasource.password="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getPassword</span><span>(),</span> <span>"spring.datasource.username="</span> <span>+</span> <span>postgreSQLContainer</span><span>.</span><span>getUsername</span><span>()</span> <span>);</span> <span>values</span><span>.</span><span>applyTo</span><span>(</span><span>configurableApplicationContext</span><span>);</span> <span>}</span> <span>}</span> <span>@Test</span> <span>@Sql</span><span>(</span><span>"/testdata/FILL_FOUR_PERSONS.sql"</span><span>)</span> <span>public</span> <span>void</span> <span>testDeletePerson</span><span>()</span> <span>{</span> <span>testRestTemplate</span><span>.</span><span>delete</span><span>(</span><span>"/api/persons/1"</span><span>);</span> <span>assertEquals</span><span>(</span><span>3</span><span>,</span> <span>personRepository</span><span>.</span><span>findAll</span><span>().</span><span>size</span><span>());</span> <span>assertFalse</span><span>(</span><span>personRepository</span><span>.</span><span>findAll</span><span>().</span><span>contains</span><span>(</span><span>"Phil"</span><span>));</span> <span>}</span> <span>}</span>// JUnit 5 example with Spring Boot < 2.2.6 @Testcontainers @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @ContextConfiguration(initializers = DeletePersonIT.Initializer.class) public class DeletePersonIT { @Container public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer() .withPassword("inmemory") .withUsername("inmemory"); @Autowired private PersonRepository personRepository; @Autowired public TestRestTemplate testRestTemplate; public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues values = TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.password=" + postgreSQLContainer.getPassword(), "spring.datasource.username=" + postgreSQLContainer.getUsername() ); values.applyTo(configurableApplicationContext); } } @Test @Sql("/testdata/FILL_FOUR_PERSONS.sql") public void testDeletePerson() { testRestTemplate.delete("/api/persons/1"); assertEquals(3, personRepository.findAll().size()); assertFalse(personRepository.findAll().contains("Phil")); } }
Enter fullscreen mode Exit fullscreen mode
You can find more integration test examples for this demo Spring Boot CRUD API application using PostgreSQL on GitHub.
Further integration test-related tutorials for Spring Boot:
- Spring Boot Functional Tests with Selenium and Testcontainers
- Spring Boot Integration Tests with WireMock and JUnit 5
Happy integration-testing with Spring Boot, Testcontainers and JUnit,
Phil
原文链接:Write Spring Boot integration tests with Testcontainers (JUnit 4 & 5)
暂无评论内容