cf) JDK7에서 새롭게 소개된 Invokedynamic. 자바는 static type 언어라고 불리며, 이는 컴파일 타임에서 이미 멤버 변수들이나 함수 변수들의 타입이 반드시 명시적으로 지정돼야 함을 의미한다. 그에 반해 루비나 자바스크립트는 이른바 ‘duck-typing’이라고 하는 타입 시스템을 사용함으로써 컴파일 타임에서의 타입을 강제하지 않는다. Invokedynamic은 이러한 duck-typing을 JVM레벨에서 기본적으로 지원하면서 자바 외에 다른 언어들이 JVM이라는 플랫폼 위에서 최적화된 방식으로 실행될 수 있는 토대를 제공한다.
Mock
def"creating example mocks"() { given:List list =Mock(List)List list2 =Mock() // preffered waydef list3 =Mock(List) }
Dummy 객체 자체를 테스트하기보다 여러 인터페이스들이 연결되어 있는 특정 메서드를 체크하는게 더 관심있을 때 Mock이나 Spy를 쓴다.
cf) Stub vs Mock Stub 은 테스트 과정에서 일어나는 호출에 대해 지정된 답변을 제공하고, 그 밖의 테스트를 위해 별도로 프로그래밍 되지 않은 질의에 대해서는 대게 아무런 대응을 하지 않는다.
Mock Object 는 검사하고자 하는 코드와 맞물려 동작하는 객체들을 대신하여 동작하기 위해 만들어진 객체이다. 검사하고자 하는 코드는 Mock Object 의 메서드를 부를 수 있고, 이 때 Mock Object는 미리 정의된 결과 값을 전달한다.
cf) 토비의 스프링에서 Stub과 Mock 비교 Stub은 테스트 대상 Object의 의존객체로 존재하면서 테스트 동안에 코드가 정상적으로 수행할 수 있도록 돕는다. 때론 테스트 대상 Object가 의존 Object에게 출력한 값에 관심이 있거나, 의존 Object를 얼마나 사용했는가 하는 커뮤니케이션 행위 자체에 관심이 있을 수 있다. 문제는 이 정보를 테스트에서는 직접 알 수가 없기 때문에 목 객체를 만들어서 사용해야 한다.
Spy
Stub이나 Mock과는 다르게 Spy는 Dummy 객체가 아니다. Spy는 실제 일반 객체를 감싼것이다. Spy를 만들 때는 interface로 만들지 않고 class로 만들어야 한다.
def"interface로 Spy 만들면 안된다."() { given:UserService service =Spy(UserService) expect: service.save(newUser(name: 'Norman')) }결과 : Cannot invoke real method on interfacebased mock object
아래의 예제는 Transaction 객체를 생성자 인수로 받는 UserServiceImpl 클래스를 Spy한다.
cf) 참고자료에서는 이렇게 하면 Spy객체가 만들어진다고 했는데 나는 에러가 발생함. cglib 의존성 추가해주니 Spy 객체 생성됌.
org.spockframework.mock.CannotCreateMockException: Cannot create mock for class spock.basic.UserServiceImpl.
Mocking of non-interface types requires the CGLIB library. Please put cglib-nodep-2.2 or higher on the class path.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
올랑(Hollandaise) 소스를 만들기 위해서는 cooking temperature를 매우 정밀하게 조절해야 한다.
그래서 올랑(Hollandaise) 소스를 위해 애플리케이션에서 temperature monitoring 하는 system을 만든다고 해보자. HollandaiseTemperatureMonitor 클래스(production code)는 다음과 같다.
@ServicepublicclassHollandaiseTemperatureMonitor { /** Maximum hollandaise cooking temperature in degree celsius */privatestaticfinalint HOLLANDAISE_MAX_TEMPERATURE_THRESHOLD =80; /** Minimum hollandaise cooking temperature in degree celsius */privatestaticfinalint HOLLANDAISE_MIN_TEMPERATURE_THRESHOLD =45;privatefinalThermometer thermometer; @AutowiredpublicHollandaiseTemperatureMonitor(Thermometer thermometer) {this.thermometer= thermometer; }publicbooleanisTemperatureOk() {int temperature =thermometer.currentTemperature();boolean belowMinimumThreshold = temperature < HOLLANDAISE_MIN_TEMPERATURE_THRESHOLD;boolean aboveMaximumThreshold = temperature > HOLLANDAISE_MAX_TEMPERATURE_THRESHOLD;boolean outOfLimits = belowMinimumThreshold || aboveMaximumThreshold;return!outOfLimits; }}
Spock을 이용한 단위 테스트
classHollandaiseTemperatureMonitorSpecextendsSpecification {@Unrolldef"returns #temperatureOk for temperature #givenTemperature"() { given: "a stub thermometer returning given givenTemperature"Thermometer thermometer =Stub(Thermometer) thermometer.currentTemperature() >> givenTemperature and: "a monitor with the stubbed thermometer"HollandaiseTemperatureMonitor watchman =newHollandaiseTemperatureMonitor(thermometer) expect: watchman.isTemperatureOk() == temperatureOk where: givenTemperature || temperatureOk0||false100||false80||true45||true60||true-10||false }}
cf) @Unroll : Indicates that iterations of a data-driven feature should be made visible as separate features to the outside world(IDEs, reports, etc.) 테스트 구현에 영향을 미치지 않음.
통합 테스트
@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)classApplicationSpecWithoutAnnotationextendsSpecification { @AutowiredWebApplicationContext context def "should boot up without errors"() { expect:"web application context exists" context !=null }}
@SpringApplicationConfiguration 대신에 @SpringBootTest를 사용한다. 그러나 아직 Spring Boot 1.4는 Spock과 호환되지 않는다. spock-spring 플러그인이 @SpringBootTest를 인식하지 못한다. 그래서 @SpringBootTest 이전에 @ContextConfiguration이나 @ContextHierarchy를 추가해줘야 한다. cf) @DataJpaTest,@WebMvcTest도 아직 지원안한다.
@ContextConfiguration// not mentioned by docs, but had to include this for Spock to startup the Spring context@SpringBootTest(webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)classSpringBootSpockTestingApplicationSpecITextendsSpecification {@AutowiredWebApplicationContext contextdef"should boot up without errors"() { expect: "web application context exists" context !=null }}
@TestpublicvoidtestSomeMethod() {SomeService service =mock(SomeService.class);MyComponent component =newMyComponent(service);// setup mock and class component methods}
마지막으로 random port로 돌릴 수도 있고 @WebIntegrationTest(randomPort=true) 추가적인 프로퍼티 설정도 있다. @IntegrationTest("myprop=myvalue") or @TestPropertySource(properties="myprop=myvalue")
SpringRunner는 기존의 SpringJUnit4ClassRunner의 새로운 이름이다. 그리고 @SpringBootTest로 심플해졌다. webEnvironment속성은 테스트에서 Mock 서블릿 환경 또는 진짜 HTTP server(RANDOM_PORT or DEFINED_PORT)를 설정할 수 있다.
만약 specific configuration을 load하고 싶으면 @SpringBootTest의 classes속성을 사용하면 된다. classes속성을 생략하면 inner-classes에서 @Configuration을 제일 먼저 load하려 시도하고, 없다면 @SpringBootApplication class를 찾는다.
위의 예에서 VehicleDetailsService Mockito mock을 만들고 ApplicationContext에 빈으로 주입시켰다. 그리고 setup 메서드에서 Stubbing behavior를 했다. 최종적으로 mock을 호출할 테스트를 만들었다. Mock들은 테스트마다 자동적으로 리셋되기 때문에 @DirtiesContext가 필요없다. (@DirtiesContext 사용은 applicationContext의 제어를 받지 않고 직접 통제하겠다는 것)
spy도 유사하다. @SpyBean을 통해 ApplicationContext에 존재하는 빈을 spy로 감싼다.
@DataJpaTest는 1. Configure an in-memory database. 2. Auto-configure Hibernate, Spring Data and the DataSource. 3. Perform an @EntityScan 4. Turn on SQL logging
TestEntityManager는 Spring Boot에서 제공한다. standard JPA EntityManager를 대신한다.
[용어정리]
Mock Object Mock Object 는 검사하고자 하는 코드와 맞물려 동작하는 객체들을 대신하여 동작하기 위해 만들어진 객체이다. 검사하고자 하는 코드는 Mock Object 의 메서드를 부를 수 있고, 이 때 Mock Object는 미리 정의된 결과 값을 전달한다. MockObject는 자신에게 전달된 인자를 검사할 수 있으며, 이를 테스트 코드로 전달할 수도 있다.
stub Stub 은 테스트 과정에서 일어나는 호출에 대해 지정된 답변을 제공하고, 그 밖의 테스트를 위해 별도로 프로그래밍 되지 않은 질의에 대해서는 대게 아무런 대응을 하지 않는다. 또한 Stub은 email gateway stub 이 '보낸' 메시지를 기억하거나, '보낸' 메일 개수를 저장하는 것과 같이, 호출된 내용에 대한 정보를 기록할 수 있다. Mock은 Mock 객체가 수신할 것으로 예상되는 호출들을 예측하여 미리 프로그래밍한 객체이다.
example
1.Behavior verify//Let's import Mockito statically so that the code looks clearerimportstaticorg.mockito.Mockito.*;//mock creationList mockedList =mock(List.class);//using mock objectmockedList.add("one");mockedList.clear();//verificationverify(mockedList).add("one");verify(mockedList).clear();2.Stubbing//You can mock concrete classes, not only interfacesLinkedList mockedList =mock(LinkedList.class);//stubbingwhen(mockedList.get(0)).thenReturn("first");when(mockedList.get(1)).thenThrow(newRuntimeException());//following prints "first"System.out.println(mockedList.get(0));//following throws runtime exceptionSystem.out.println(mockedList.get(1));//following prints "null" because get(999) was not stubbedSystem.out.println(mockedList.get(999));//Although it is possible to verify a stubbed invocation, usually it's just redundant//If your code cares what get(0) returns then something else breaks (often before even verify() gets executed).//If your code doesn't care what get(0) returns then it should not be stubbed. Not convinced? See here.verify(mockedList).get(0);