단위테스트에 해당하는 글 2

클린코드 7장(경계), 8장 (단위 테스트)

JAVA/클린코드|2020. 7. 1. 09:18

 

 

경계


  • Map과 같은 공개된 인터페이스를 사용할 때 2가지 문제가 있다
    1. 전달되는 map에 clear와 같은 삭제 명령어가 있어서 전달해주는 쪽에서 알지 못하고 지워지는 이슈가 있을 수 있다.

    2. 전달된 Map 형태가 generic 하게 전해 졌어도 받는 쪽에서 Map map 등으로 받아 버린다면 캐스팅을 해서 사용하는 등의 문제나 변경의 소지가 크다. 또한 Map의 사용 방식이 변경된다면 이를 해결하기 위해 모든 부분에서 수정되어야 하는 문제가 있다.

    그래서 이를 해결하기 위해서 Map에 사용 되는 Value 값의 객체에서 Map을 품어서 쓰는 방식으로 캡슐화하여 사용하기도 한다.
public classs Sensors {
	private Map<String, Sensors> = new HashMap<>();

	public Sensor getById(String id) {
		return (Sensor) sensors.get(id);
	}

}
  • 외부 라이브러리를 사용할 경우 시간 단축에 효과가 있지만 잘못 사용함에 있어서 발생하는 영향도가 얼마나 있을지 알지 못하기 때문에 학습 테스트를 통해 외부 api에 대한 테스트를 미리 진행한다.
  • 외부 라이브러리에 대한 학습 테스트를 진행하여 사용하면 해당 라이브러리의 버전이 올라갔을 때 우리가 사용하는 목적에 맞게 정상적으로 돌아가는지 쉽게 테스트를 해볼 수 있어서 좋다.
  • Adapter pattern을 이용하여 변경의 요지가 있는 인터페이스를 방어할 수 있다.
public interface Bark {
	void speak();
}

// dog는 짖으므로 Bark 인터페이스 사용가능
public Dog implements Bark {
	@Override
	public void speak() {
		..
	}
}

// 고양이 야용 인터페이스
public interface Meow {
	void speak();
}

// 고양이는 짖기 어려움
public Cat implements Meow {
	@Override
	public void speak() {
	
	}
}

// 고양이가 짖을 수 있는 adpater 생성
public BarkCat implements Bark {
	private Meow meow;

	public BarkCat(Meow meow) {
		this.meow = meow;
	}
	
	@Override
	public void speak() {
		meow.speak();
	}
	
}

 

 

 

단위 테스트


  • 단위 테스트
    1. 실패하는 단위 테스트를 작성할 때 까지 실제 코드를 작성하지 않는다.
    2. 컴파일이 실패하지 않으면서 실행이 실패하는 정도로만 단위테스트를 작성한다.
    3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

    하지만 이렇게 모두 작성하면 시간이 너무 걸려서 사실 어렵다.
  • 실제 코드가 변경될 때 마다 테스트 코드는 계속 변경의 여지가 있기에 지저분한 테스트 코드를 짜느니 없으니만 못하다. 그렇기에 실제 코드 못지 않게 중요하게 테스트 코드를 작성하라.
  • 테스트 코드 없는 실제 코드 변경은 잠정적인 버그와 같다.
  • 깨끗하고 효율성있는 테스트 코드를 작성하기 위해서는 무엇보다 중요한 건 가독성이다.
  • BUILD-OPERATE-CHECK 패턴을 사용하여 테스트 구조를 간결하게 작성한다.
  • 테스트 코드의 가독성을 위해서 실제 코드에서 지켜야 할 모든 규칙을 지킬 필요는 없다.
  • 하나의 테스트당 assert문이 많으면 좋지 않지만 그렇다고 테스트당 하나의 assert문을 사용하는건 중복코드를 양성할 수 있다. (적절하게 사용)
  • 하나의 테스트당 assert문이 많으면 좋지 않지만 그렇다고 테스트당 하나의 assert문을 사용하는건 중복코드를 양성할 수 있다. (적절하게 사용)
  • 깨끗한 테스트 코드의 FIRST 규칙
    F : 테스트는 빨라야 한다.
    I : 테스트는 서로 의존적이면 안된다.
    R : 테스트는 어떤 환경에서도 반복 가능해야 한다.
    S : 테스트는 통과 실패 두가지 검증 결과를 내보내야 한다.
    T : 단위테스트는 적시에 테스트 코드가 존재해야한다. (실제 구현 코드를 작성하기 이전에 작성되어야 한다.)

 

 

 

결론


개발을 하다보면 일정에 쫓기거나 테스트 코드 작성이 번거로워서 넘어가는 경우가 있는데 무조건 작성할 수 있도록 해야겠다. 그리고 테스트를 내가 알아볼 수 있도록 기능 동작에 중점을 주어서 했었는데 그게 결국 가독성과 재사용성을 떨어트려서 팀의 개발 속도를 저해 할수도 있다는 걸 알게 되어 조심해야겠다.

 

댓글()

Spring Application Test 정리

web/Spring|2018. 8. 7. 00:12

Spring에서 Junit을 통해 테스트하는데 익숙해져있다. 

이 방식과 더불어서 Spring의 MockMvc를 이용하여 Web Layer를 테스트 하는 방법에 대해 공부해보자. 

우선 테스트를 진행하기 위해서 Spring-boot-starter-test를 추가해야한다. 

Gradle

1
testCompile("org.springframework.boot:spring-boot-starter-test")
cs


그러면 테스트 클래스 파일에 @SpringBootTest와 @RunWith가 추가된다.  
@SpringBootTest는 Spring Boot에서 주 구성 클래스를 찾아서 Spring Application Context를 시작하도록 하는 어노테이션이다. 

Controller 테스트를 진행할 떄 서버를 가동시키고 TestRestTemplate를 사용하여 테스트를 진행할 수 있다.

TestRestTemplate를 이용한 web layer 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {
 
    @LocalServerPort
    private int port;
 
    @Autowired
    private TestRestTemplate restTemplate;
 
    @Test
    public void greetingShouldReturnDefaultMessage() throws Exception {
        assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
                String.class)).contains("Hello World");
    }
}
cs

하지만 서버 실행하고 테스트를 진행하면 너무 비용이 크기 때문에 이런 비용을 없에기 위해서 MockMvc를 사용할 수 있다. 

MockMvc를 이용한 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTest {
 
    @Autowired
    private MockMvc mockMvc;
 
    @Test
    public void shouldReturnDefaultMessage() throws Exception {
        this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("Hello World")));
    }
}
cs

하지만 위와같은 방식은 모든 Spring application의 context가 실행 되었기 때문에 단순히 web layer만을 사용하여 테스트 하고 싶은 경우에 @WebMvcTest 애노테이션을 설정하여 간단하게 테스트를 진행할 수 있다.


WebMvcTest 애노테이션을 이용한 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringRunner.class)
@WebMvcTest
public class WebLayerTest {
 
    @Autowired
    private MockMvc mockMvc;
 
    @Test
    public void shouldReturnDefaultMessage() throws Exception {
        this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("Hello World")));
    }
}
cs

방식은 모두 같지만 모든 context를 로드하지 않고 web layer만 로드하기에 비용이 더저렴하다. 

또한 컨트롤러에서 의존주입한 서비스는 @MockBean 어노테이션을 사용하여 inject 하여 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RunWith(SpringRunner.class)
@WebMvcTest(GreetingController.class)
public class WebMockTest {
 
    @Autowired
    private MockMvc mockMvc;
 
    @MockBean
    private GreetingService service;
 
    @Test
    public void greetingShouldReturnMessageFromService() throws Exception {
        when(service.greet()).thenReturn("Hello Mock");
        this.mockMvc.perform(get("/greeting")).andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("Hello Mock")));
    }
}
cs

위의 코드에 보면 GreetingService에 경우 GreetingController에서 주입되는 Bean이기 때문에 @MockBean 형태로 가지고와서 Mockito 형태로 사용할 수 있다.

Spring에서 단위 테스트를 할 수 있는 방식은 많다. 조금 더 비용을 줄이고 효율적으로 테스트 할 수 있는 방법을 알고 사용해보자!

댓글()