Вы здесь:

Предупреждение: я знаю, что JUnit для UNIT тестов и цена им небольшая. Речь о применении JUnit в ИНТЕГРАЦИОННЫХ тестах. И с другой стороны, на тех проектах, где мне приходилось работать, интеграционное тестирование проводилось вручную. Можно это дело автоматизировать.


Сами интеграционные тесты должны быть в отдельном проекте, не вместе с проектом/проектами. Что будет использовано в самих тестах решать самим (н.п. применить JUnit для тестирования REST, используя RestTemplate https://v.perm.ru/main/index.php/34-behave-testirovanie/ ).

Примеры тестирования https://javabydeveloper.com/junit-5-with-allure-reports-example/. В примере очень подробно описана работа с Allure, с различными группировками и прочими возможностями. Что будет протестировано, Rest или что-то другое, не важно.

Проведение тестов:

git clone https://github.com/javabydeveloper/Junit-5-tutorials-master/tree/master/junit5-allure-report-example
cd junit5-allurreport-example
mvn clean test allure:report
}

 

Необходимое замечание об интеграционных тестах

JUnit для UNIT тестов и цена им небольшая. Речь о применении JUnit в ИНТЕГРАЦИОННЫХ тестах. На тех проектах, где мне приходилось работать, интеграционное тестирование проводилось вручную. Можно это дело автоматизировать. Нужно обеспечить начальное состояние теста и восстановить состояние после любого исхода теста. В самом простом случае можно установить его прямо в тесте. Или, лучше, воспользоваться средствами JUnit5:

  • @BeforeAll - выполняется ПЕРЕД выполнением ВСЕХ методов класса теста
  • @BeforeEach - выполняется ПЕРЕД выполнением КАЖДОГО метода класса теста
  • @AfterEach - выполняется ПОСЛЕ выполнением КАЖДОГО метода класса теста
  • @AfterAll - выполняется ПОСЛЕ выполнением ВСЕХ методов класса теста

О warning Allure

При генерации отчета может быть получен warning от allure типа "java.lang.NoSuchMethodError when run test with Junit 5...". Для решения нужно согласовать версию allure и JUnit. Использовать allure 2.10.0. Решение для gradle:

plugins {
...
	id 'io.qameta.allure' version '2.10.0' // version 2.10.0 WORK! NO WARNINGS for generate allure report
...
}
...
dependencies {
    ...
	testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter'
    ...
}


Работающий пример: https://github.com/cherepakhin/kotlin_in_action

Результаты тестирования

Наглядно и красиво!

Прогон конкретного теста:

./gradlew test --tests '*AssumptionsTest'
./gradlew test --tests ru.perm.v.project.AssumptionsTest
./gradlew test --tests 'ru.perm.v.project.MyTestClass*'
./gradlew test --tests '*Assumptions*'


Разные варианты тестирования:

./gradlew test --tests '*SomeTest.someSpecificFeature'
./gradlew test --tests '*SomeSpecificTest'
./gradlew test --tests '*IntegTest'
./gradlew test --tests '*IntegTest*ui*'
./gradlew test --tests '*IntegTest.singleMethod'


ВНИМАНИЕ на '*'. '*' - позволяет не указывать пакет.
Еще один момент - Gradle пропускает уже пройденные тесты, если код не изменился. В случае с тестирования внешнего сервися, сами тесты не меняются, а тестируюмая система может измениться. Нужно прогонять все тесты заново. Варианты решения:

./gradlew clean test
./gradlew cleanTest test
./gradlew test --rerun-tasks


Можно просто использовать "./gradlew test" для прогона ВСЕХ тестов, без учета предыдущих результатов. Для этого нужно настроить таск тестирования. На примере build.gradle.kts:

tasks.withType<Test> {
....
    outputs.upToDateWhen { false } // always rerun tests
....
}


Пример https://github.com/cherepakhin/camel_rest_restassured_test/blob/main/build.gradle.kts/.

Пример тестирования с базой данных:
В проекте используется https://java.testcontainers.org/.
Проведение тестов :

./mvnw test


Еще один способ тестирования с заданием конфигурации бинов Spring boot (ПОДГЛЯДЕЛ в проекте https://github.com/saladlam/spring-wicket-noticeboard ):

package info.saladlam.example.spring.noticeboard.test;

import info.saladlam.example.spring.noticeboard.support.Helper;
import liquibase.integration.spring.SpringLiquibase;
import org.apache.wicket.protocol.http.WicketFilter;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterConfig;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.sql.DataSource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
class LoginTest {

	@TestConfiguration
	public static class LoginTestContextConfiguration {

		// Определение бина для теста Spring Boot
		@Bean
		public DataSource testDataSource() {
			return Helper.getEmbeddedDatabaseBuilder(LoginTest.class.getName()).build();
		}

		@Bean
		public SpringLiquibase liquibase(DataSource dataSource) {
			SpringLiquibase liquibase = new SpringLiquibase();
			liquibase.setChangeLog("classpath:db/user-test.xml");
			liquibase.setDataSource(dataSource);
			liquibase.setDropFirst(true);
			return liquibase;
		}

		@Bean
		public Boolean finishWicketFilterInit(FilterRegistrationBean<WicketFilter> filter, ServletContext context) throws ServletException {
			MockFilterConfig config = new MockFilterConfig(context, "wicket-filter");
			config.addInitParameter(WicketFilter.FILTER_MAPPING_PARAM, "/*");
			WICKET_FILTER = filter.getFilter();
			WICKET_FILTER.init(config);
			return true;
		}

	}

	private static WicketFilter WICKET_FILTER;

	@Autowired
	private MockMvc mockMvc;

	private Document getDocument(RequestBuilder requestBuilder) throws Exception {
		MvcResult result = mockMvc.perform(requestBuilder)
				.andExpect(status().isOk())
				.andReturn();
		return Jsoup.parse(result.getResponse().getContentAsString());
	}

	private void sendLogin(MockHttpServletRequestBuilder requestBuilder, String username, String password, ResultMatcher matcher) throws Exception {
		mockMvc.perform(requestBuilder.with(csrf())
				.param("username", username)
				.param("password", password)
				.contentType(MediaType.APPLICATION_FORM_URLENCODED)).andExpect(matcher);
	}

	@Test
	void loadLoginPage() throws Exception {
		Document doc = getDocument(get("/login"));
		assertThat(doc.select("input[name=username]")).hasSize(1);
		assertThat(doc.select("input[name=password]")).hasSize(1);
	}

	@Test
	void loginSuccess() throws Exception {
		sendLogin(post("/loginHandler"), "user1", "dIw8#a-$eW", redirectedUrl("/"));
	}

	@Test
	void loginFail() throws Exception {
		sendLogin(post("/loginHandler"), "user1", "password1", redirectedUrl("/login?error=true"));
	}

	@AfterAll
	static void tear() {
		WICKET_FILTER.destroy();
		WICKET_FILTER = null;
	}

}

Полезны параметризованные тесты (и не только в интеграционном тестировании):
https://www.baeldung.com/parameterized-tests-junit-5
Туториал по JUnit 5 - Аннотация @ParameterizedTest (Habr)
Пример:

@ParameterizedTest
@ValueSource(ints = [1, 2])
fun getByWithParamN(N: Long) {
	val priceTypeService = PriceTypeServiceImpl(priceTypeRepository)

	assertEquals(N, priceTypeService.getByN(N).n)
}

@ParameterizedTest
@CsvSource("1,'Normal price'", "2,'Discount price'")
fun getByWithParamFromCasv(N: Long, NAME:String) {
	val priceTypeService = PriceTypeServiceImpl(priceTypeRepository)

	assertEquals(NAME, priceTypeService.getByN(N).name)
}


Вид в Idea:

Результаты тестирования

(Пример PriceTypeServiceImplIntegrationTest.kt)