https://spring.io/blog/2018/03/07/testing-auto-configurations-with-spring-boot-2-0 지지난주 spring.io 에  올라온 글이다. 

스프링부트 2.0에  ApplicationContextRunner 가 추가 되었고 이에 대한 사용법을 설명하는 내용인데, 실제로 활용해보니 기존의 static class 로 Spring 설정을 slicing 하는 것보다 가독성도 좋고, 사용도 편하다.


1. SolMailAutoConfiguration 빈을 생성할때 JSR380에 의해 validate한지 확인하는 테스트

new ApplicationContextRunner()
.withConfiguration(of(SolMailAutoConfiguration.class))
.run((context) -> {
assertThat(context.getStartupFailure())
.isNotNull();
assertThat(context.getStartupFailure())
.isInstanceOf(UnsatisfiedDependencyException.class);
});


2. SolMailAutoConfiguration 빈을 생성 할 때 제대로 값이 주입되는지 확인하는 테스트

new ApplicationContextRunner()
.withConfiguration(of(SolMailAutoConfiguration.class))
.withPropertyValues(
"app.sol.mail.connection-timeout:10000",
"app.sol.mail.api-url:http://localhost/api/v1/sol/"
).run((context) -> {
SolMailProperties properties = context.getBean(SolMailProperties.class);
assertThat(properties.getApiUrl()).isEqualTo("http://localhost/api/v1/sol/");
assertThat(properties.getConnectionTimeout()).isEqualTo(10000);
});


3. 커맨드라인이나, 프로퍼티로 값을 받을때 원하는 Bean이 로드 되는지 확인하는 테스트

new ApplicationContextRunner()
.withInitializer(new ConfigFileApplicationContextInitializer())
.withConfiguration(of(FakeMailClient.class, SolMailAutoConfiguration.class))
.withPropertyValues("mail.type=fake")
.run((context) ->
assertThat(context).hasSingleBean(FakeMailClient.class));

legacy 에서는  initializers = ConfigFileApplicationContextInitializer 라고 initializers 를 정의해서 썻지만 이렇게 코드 레벨에서 사용할 수 있다. 


ApplicationContextRunner 를 before로 올려서 DRY를 준수할 수도 있지만, 그렇게 올려서 얻는  중복 코드의 제거보다, 지금 처럼 각각 초기화 하는게 더 나은 가독성과 사용성을 주는 것 같아서, 위처럼 작성함 이게 대한 좋은 글로

https://stackoverflow.com/questions/6453235/what-does-damp-not-dry-mean-when-talking-about-unit-tests

Working Effectively with Unit Tests

분류없음 2018.03.23 14:41 posted by dev.bistro

Working Effectively with Unit Tests #1


https://leanpub.com/wewut 를 읽고 정리한 내용 첫번째 단락


Unit Testing, a First Example


* Replace Loop with individual Test

- https://github.com/bistros/wewut-code#replace-loop-with-individual-tests

- 읽기 쉬운 테스트를 위한 첫 번째 단계는 반복 테스트를 각각의 테스트로 변경하는 것이다.

ex) for-each를 돌면서 여러값을 테스트 하는 것을 제거한다


* Expect Literals

- https://github.com/bistros/wewut-code#expect-literals

그 다음 가독성을 높이는 단계로는 literal 값을 예상하는 것이다. 여전히 테스트는 실패 하겠지만, value를 직접적으로 확인 할 수 있게 된다.


* Inline Setup

- https://github.com/bistros/wewut-code#inline-setup

- 실패한 테스트의 원인을 쉽게 찾을 수 있어야한다. Template Method Pattern을 이용하면, 중복을 약간 줄일 수있는 있지만 다른 장점은 없다. 그리고 오버헤드가 증가할 수도 있다. 이러한 @Before setup에서 일어나는 이러한 부분을 없앤다면, 실패 했을 경우 setup을 안봐도 된다. 즉 code search가 거의 필요없게 되는 것이다. 

코드 중복에 대해서는 밑의 Final Thoughts on our Tests 에서 책 이외의 내용을 좀 더 추가하였다.


* Replace ObjectMother with DataBuilder

- https://github.com/bistros/wewut-code#replace-objectmother-with-databuilder

- 테스트를 위해 mock instance를 만드는 ObjectMother가 제한적일때는 유리하지만, 다른 시나리오(케이스)가 필요할때는 난감하다. 이 시점에서는 ObjectMother style 보다는 new Domain Model Instance 스타일로 변경되는 것이 실용적일 것이다. 대신에 Builder 가 변경이 필요하다면, 사용되는 모든 곳의 코드를 업데이트 쳐야하는 문제가 있는데 그건 다른 방법으로 해결 할 수 있다 (주: 챕터6 TestDataBuilder를 참고하면 된다) 물론 기존의 ObjectMother 보다는 코딩해야 할 내용이 많다. 하지만 좀 더 일반적이고 독립적으로 객체를 만들어 낼 수 있다. 


* Comparing the Results

- https://github.com/bistros/wewut-code#comparing-the-results

- 쉽게 assertion 하게 하라 (좋은 테스트는 나뿐 아니라 미래의 팀원들을 위해서이기도 하다)


* Final Thoughts on our Tests

- https://github.com/bistros/wewut-code#final-thoughts-on-our-tests

- 참고1) DAMP  https://stackoverflow.com/questions/6453235/what-does-damp-not-dry-mean-when-talking-about-unit-tests

프로덕션 레벨과는 달리 유닛 테스트에서는 파일/단일 테스트 내에서만 코드가 적용된다. 이 때문에 중복 코드에 대한 minimal and obvious 다.  또한 이러한 중복을 제거해 버린다면 테스트의 가독성이 떨어진다. 그래서 DRY를 프로덕션에서 선호하고 테스트에는 DAMP를 우선하여 균형을 이뤄야 한다

참고2) http://blog.jayfields.com/2006/05/dry-code-damp-dsls.html

DRY를 DAMP로 바꾸더라도 크게 라인수가 늘어나지는 않음을 보여줬고, 대신 가독성 측면 등에서 가치를 높일 수 있었다.

Should You Put Several Event Types in the Same Kafka Topic?

링크 : https://www.confluent.io/blog/put-several-event-types-kafka-topic/ 에서 필요한 부분만 정리

카프카를 쓸 때 가장 중요한 것 중 하나는 토픽을 어떻게 쓸 것인가이다. 만약 여러 이벤트 묶음이 있다면 한 토픽에 넣을 것인가, 여러 토픽에 넣을 것인가? 극단적으로 하나의 토픽에 넣는것은 좋은 생각이 아니다. 컨슈머가 관심있는 이벤트를 선택해서 소비할 수 있는 방법이 없기 때문이다. 반대로 너무 많은 것도 좋은 건 아니다.

사실 성능관점에서 중요한것은 파티션의 갯수이다. 경험에 비추어 보면 레이턴시가 중요하다면 브로커 노드당 수백의 토픽-파티션을 가질 것이고, 아마 많을 수록 레이턴시 관점에서는 대기 시간이 줄어들 것이다.


Topic = collection of events of the same type?

일반적으로는 같은 타입의 이벤트는 같은 토픽을 사용하고, 다른 타입은 다른 토픽을 사용 하는 것이다.

Confluent 스키마 레지스트리는 이 패턴을 사용한다. topic의 모든 메시지에 대해서 동일한 Avro 스키마를 사용하도록 구너장하기 떄문이다. (옵션을 추가해서) 호환성을 유지할 수는 있지만, 궁극적으로 모든 메시지는 레코드 타입을 준수해야 한다. 일반적으로 이러면 되는데, 이벤트 소싱이나 MSA에서 데이터 교환같은 목적으로 사용 하는 사람들도 있다.

이때에는 topic이 같은 스키마를 가지는것이 덜 중요해질수는 있다 그것보다는 topic-partition 내에서 순서를 보장하는게 더 중요하다.

예를 들어 고객이 주소변경/새신용카드추가/조회/지불/계정파기... 이런것들은 순서가 중요하다. 이 순서는 '같은 파티션'을 사용함으로서 보장받을 수 있고, 고객 ID를 파티션 키로 사용한다면 동일한 topic-partiton에 들어갈 것이다.



Ordering problems

customerCreated, customerAddressChanged, and customerInvoicePaid 를 서로 다른 토픽으로 사용한다면 컨슈머는 순서의 의미없는 이벤트라 볼 것이다. 예를 들어 컨슈머는 존재하지 않는 고객에 대해서 주소 변경 이벤트를 받을 수 있을 것이다. 만약 이렇게 다른 토픽을 사용한다면 이벤트를 시간 동기화 처리를 해서 사용할 수 있겠지만 악몽이다. 


When to split topics, when to combine

몇가지 제안 할 것이다.

1. 가장 중요한것은 '고정된 순서로 머물러야 하는 모든 이벤트는' 동일한 토픽을 사용해야 한다.

2. 한 엔티티가 다른 엔티티에 의존하거나, 함께 자주 변경이 된다면 같은 토픽을 사용할수도 있다. (예를 들어 주소가 고객에게 속하는 것처럼) 한편으로 다른 팀과 관련이 없다거나, 다른 팀에서 관리하는 경우에는 토픽을 분리하라 또 하나의 엔티티 타입이 과도하게 비율이 높다면 별도로 분리하는 것을 고민하라 (다른 이벤트 타입들을 위해서이다)

3. 이벤트에 여러 엔티티가 관련된 경우 (예를 들어 구매-제품-고객) 처음에는 이벤트를 원자 단위로 기록하고, 여러 토픽으로 분할 하지 마라. 가능한 받은 그대로 이벤트를 기록하라 스트림을 이용해서 분리할 수 있지만 어려워질 것이다.

4. 컨슈머를 살펴보고 여러 토픽을 구독한다면 그 토픽들은 합쳐야 될 수도 있다. 그럴 경우 원치 않은 이벤트를 소비할 수도 있지만 그것은 큰 문제가 아니다. 이벤트를 소비하는 것은 매우 싸기 때문에 이벤트의 절반을 무시해도 큰 문제는 아니다. 대부분의 이벤트를 무시할 경우만 (99프로) 분리를 고민해라.

5. 카프카스트림이 사용하는 changelog topic은 다른 토픽에서 분리되어야 한다.


Schema management

정적 스키마가 없는 json으로 인코딩을 한다면, 쉽게 다양한 이벤트 타입을 하나의 토픽에 넣을 수 있다. 하지만 avro 를 사용할 경우 여러 이벤트 타입을 처리하는데 좀 더 많은 작업을 해야한다. 위에 언급한 것처럼 현재의 컨플런트 스키마 레지스트리는 각 토픽마다 하나의 스키마가 있다고 가정한다. 새 버전의 스키마를 등록할 수 있고, 호환되는지 체크한다. 좋은 점은 서로 다른 스키마 버전을 동시에 사용하는 프로듀서/컨슈머를 가질 수 있고, 서호 호환 가능하다는 점이다. 

컨플런트 Avro 시리얼라이저에 key.subject.name.strategy 를 추가하였으니 이걸 좀 더 참고하면 된다.




티스토리 툴바