통합 테스트를 위한 내장 Tomcat, tomcat-bed

통합 테스트 안에서 내장 Tomcat을 띄워 JSP 렌더링까지 검증하는 tomcat-bed 예제를 설명합니다. Spring 테스트 컨텍스트에 bean으로 등록하면 여러 테스트가 WAS 하나를 공유하고, 포트가 이미 사용 중이면 떠 있는 서버를 그대로 씁니다.

Note

이 글은 2026년에 예전 예제 저장소들을 정리하면서 작성했습니다. benelog/tomcat-bed 저장소를 이 블로그 저장소의 examples/tomcat-bed로 병합했고, 발행 날짜는 원본 저장소의 마지막 커밋 날짜(2013-06-29)에 맞췄습니다. 본문도 그 시점의 기술 기준으로 서술했습니다.

tomcat-bed는 통합 테스트 안에서 내장 Tomcat을 띄우는 작은 유틸리티입니다. Spring 테스트 컨텍스트에 bean으로 등록하면 테스트가 시작될 때 WAS를 올리고 끝나면 내립니다. 별도의 배포 작업 없이 JSP 렌더링 결과까지 검증하는 UI 테스트를 JUnit 안에서 실행하려고 만들었습니다.

왜 만들었나

웹 계층을 검증하는 방식은 크게 세 가지입니다.

방식 장점 한계

배포된 서버 대상 UI 테스트

실제 운영과 같은 환경을 검증

테스트 전에 배포 작업이 필요하고, Test Coverage 측정이 어려움

Spring MVC Test (MockMvc)

서블릿 컨테이너 없이 빠르게 실행

Controller가 반환한 view 이름까지만 검증. JSP가 실제로 어떻게 렌더링되는지는 알 수 없음

내장 WAS + UI 테스트 (tomcat-bed)

배포 없이 JSP 렌더링까지 검증. EclEmma로 Eclipse에서 바로 커버리지 확인 가능

실제 Tomcat이 시작되므로 `MockMvc`보다 느림

Spring 3.2에 포함된 Spring MVC Test는 서블릿 컨테이너 없이 DispatcherServlet 계층을 검증하는 좋은 도구입니다. 다만 JSP 렌더링 결과는 검증 범위 밖입니다. 화면까지 확인하려면 실제 서블릿 컨테이너가 필요한데, 매번 배포해서 테스트하기는 번거롭습니다. 그래서 테스트 프로세스 안에서 Tomcat을 직접 띄우는 방식을 시도했습니다. Local 개발환경에서 WAS를 띄우는 여러가지 방법에서 다뤘던 내장 WAS 활용법을 테스트로 가져온 셈입니다.

핵심 클래스 WebApplicationServer

핵심은 WebApplicationServer.java 클래스 하나입니다. Tomcat 6의 org.apache.catalina.startup.Embedded API로 Engine, Host, Context를 조립합니다. 주요 부분만 옮기면 아래와 같습니다.

WebApplicationServer.java
public WebApplicationServer(String contextPath, int port, String appBase) {
	this.port = port;
	this.container = new Embedded();
	this.container.setName("TomcatEmbeddedServer");
	this.appContext = createAppContext(contextPath);
	Host localHost = createHost(appBase, appContext);
	Engine engine = createEngine(localHost);
	this.container.addEngine(engine);

	Connector connector = this.container.createConnector(
			localHost.getName(), port, false);
	this.container.addConnector(connector);
}

@PostConstruct
public void start() throws IOException, LifecycleException {
	log.info("Starting Tomcat... (port={})", port);

	if (isEmptyPort()) {
		this.container.start();
		this.running = true;
		this.servletContext = appContext.getServletContext();
	} else {
		log.info("The port({}) is already occupied.", port);
	}
}

설계 포인트는 세 가지입니다.

  • 배포 없는 실행 : `appBase`를 `src/main`으로 지정하면 war를 만들지 않고 소스 트리의 webapp 폴더를 그대로 서비스합니다.

  • 포트 점유 확인 : 지정한 포트가 이미 사용 중이면 내장 Tomcat을 시작하지 않습니다. PC에 개발용 WAS가 떠 있으면 그 서버를 대상으로 같은 테스트가 그대로 동작합니다.

  • 수명 주기 관리 : @PostConstruct/`@PreDestroy`로 시작과 종료를 선언해서, Spring 컨테이너가 bean의 수명에 맞춰 WAS를 올리고 내립니다.

Spring 테스트와 통합

`WebApplicationServer`를 테스트용 컨텍스트에 bean으로 등록합니다.

applicationContext-ui-test.xml
<util:properties id="testServer" location="classpath:testServer.properties" />

<bean id="was" class="net.benelog.tomcatbed.WebApplicationServer"
	c:contextPath="#{testServer.contextPath}"
	c:port="#{testServer.port}"
	c:appBase="src/main"/>

Spring TestContext 프레임워크는 같은 설정을 쓰는 테스트끼리 `ApplicationContext`를 캐싱해서 공유합니다. 덕분에 여러 테스트 클래스를 함께 실행해도 WAS는 한 번만 시작됩니다.

서버 접속 정보는 별도 속성 파일로 분리했습니다.

testServer.properties
contextPath=/
port=8080
baseUrl=http://localhost:8080

`baseUrl`을 개발 서버나 Staging 서버 주소로 바꾸면 같은 테스트를 다른 환경을 대상으로도 실행할 수 있습니다. 그 경우 해당 포트가 이미 사용 중이 아니므로 내장 Tomcat만 로컬에서 뜨지 않을 뿐, 테스트 코드는 동일합니다.

JWebUnit으로 화면 검증

테스트에서는 JWebUnit으로 실제 렌더링된 화면을 검증합니다. ZipCodePageTest.java는 폼 입력과 전송, 응답 화면 내용까지 확인합니다.

ZipCodePageTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext-ui-test.xml"})
public class ZipCodePageTest {
	@Value("#{testServer.baseUrl}")
	String baseUrl;

	@Before
	public void prepare() {
		setBaseUrl(baseUrl);
		setScriptingEnabled(false);
	}

	@Test
	public void zipCodeForm() {
		beginAt("/zipCodeForm");
		assertFormPresent();
		assertSubmitButtonPresent();

		setTextField("zipCode", "121-270");
		submit();

		assertResponseCode(HttpStatus.OK.value());
		assertTitleEquals("Address");
		assertMatch("서울특별시 마포구 상암동");
	}
}

`@ContextConfiguration`으로 위의 테스트 컨텍스트를 지정하기만 하면, 테스트가 실행될 때 WAS가 준비됩니다. JSP가 렌더링한 제목과 본문 텍스트를 그대로 단언할 수 있다는 점이 `MockMvc`와의 차이입니다.

같은 예제 프로젝트에 두 방식을 나란히 두었습니다. HomeMvcTest.java는 Spring MVC Test로 Controller 계층을, HomePageTest.java는 내장 Tomcat 위에서 같은 화면을 검증합니다. 빠른 피드백은 `MockMvc`로, 화면까지 포함한 검증은 tomcat-bed로 나눠 쓰는 구성입니다.

한계

  • 실제 Tomcat이 시작되므로 Spring MVC Test보다 시작 속도가 느립니다.

  • 태그 라이브러리 선언 파일(*.tld)을 수동으로 등록해야 인식됩니다. 예제에서도 WEB-INF/tld에 JSTL과 Spring 태그의 tld 파일을 직접 넣었습니다.

  • Tomcat 6의 Embedded API를 사용했는데, Tomcat 7부터는 더 간결한 org.apache.catalina.startup.Tomcat 클래스가 이 역할을 대신합니다. 새로 시작한다면 그쪽이 출발점으로 적합합니다.

참고 자료


이 포스트는 Claude Code와 정상혁이 함께 작성했습니다.

Android에서 @Inject, @Test Spring Loaded로 Tomcat 재시작 없이 수정한 클래스 반영하기