Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- BFS
- container image #docker #layer #filesystem #content addressable
- 설계
- Spring Data Redis
- dfs
- java #jvm #reference #gc #strong reference
- springcloud
- Java
- unit
- 단위테스트
- test
- 서비스스펙
- netflix eureka
- forkandjoinpool #threadpool #jvm #async #non-blocking
- spring cloud netflix
- Dynamic Routing
- docker
- 탐색
- spring cloud netflix zuul
- zuul
- api-gateway
- code refactoring
- reactive
- unittest
- microservice architecture
- netflix
- spring cloud
- Eureka
- spring cloud netflix eureka
Archives
- Today
- Total
phantasmicmeans 기술 블로그
유닛테스트 3장- 단위 테스트 구조 본문
3.1 단위 테스트 구성 방법
AAA 패턴 사용
우리가 흔히 사용하는 give/when/then 과 동일하고, 모든 테스트가 단순하고 균일한 구조를 갖는 데 도움이 된다.
- 준비 구절: SUT 와 해당 의존성을 원하는 상태로 만듬
- 실행 구절: SUT 메소드 호출 및 준비된 의존성 전달
- 검증 구절: 결과 검증 (반환 값이나 협력자의 최종 상태, SUT 가 협력자에 호출한 메소드 등으로 표시)
여러개의 준비, 실행, 검증 구절을 피하라
- 여러 개의 준비, 실행, 검증 구절은 테스트가 너무 많은 것을 한 번에 검증한다는 의미이다.
- 각 동작을 고유의 테스트로 나눠라
- 통합테스트의 범주 - 통테에서는 속도를 높이기 위해 이렇게 해도 괜찮다.
테스트 내 if 문 피해라
- if 문은 테스트가 한 번에 너무 많은 것을 표시
각 구절 사이즈
준비 구절
- 일반적으로 가장 크다. 코드 재사용해라 (오브젝트 마더, 테스트 데이터 빌더 패턴)
실행 구절
- 실행 구절은 보통 코드 한 줄, 2줄 이상인 경우 테스트 대상인 public API의 설계 문제가 아닐지 확인해라.
- 캡슐화를 지켜서, 불변 위반을 제거하고 -> 실행 구절은 한줄로 하라 (유틸성 빼고)
검증 구절
- 단위테스트 단위는
동작
- 단일 동작 단위는 여러 결과를 낼 수 있으니, 하나의 테스트로 모든 결과를 평가하는게 좋다. (잘 이해가 안감) - 그렇다고 하더라도 검증 구절이 너무 커지는건 경계하라 -> 동등 멤버 (equals 같은)를 정의하여 줄여라.
- 단위테스트 단위는
종료 구절
- 단위테스트에는 따로 필요 없어야한다. 프로세스 외부 종속적이지 않아야 사이드 이펙트가 없다.
- 통합테스트의 영역이다.
테스트 대상 시스템 구별
- 테스트 내에서의 SUT의 변수명을
sut
로 하라
준비, 실행, 검증 주석
- 주석을 달거나, 빈 줄로 분리하라 > 빈 줄은 대부분의 단위 테스트에서 간결성, 가독성 등 균형있다.
- 테스트가 커지면 뭐 마음대로 하라... 구절 주석을 유지하든지.. 제거하든지..
3.2 xUnit
- 자랑 time
3.3 테스트 간 테스트 픽스처 재사용
테스트 픽스처
- 테스트의 실행 대상 객체
- 정규 의존성, 즉 SUT로 전달되는 인수다. (ex. repository 등)
테스트에서 언제 어떻게 코드를 재사용하는지 아는 것이 중요하다. 준비 구절에서 코드 재사용은 테스트를 단순화하기 좋은 방법.
- 테스트 픽스처 재사용 방법은 2개
A. 생성자(setup)류 - 테스트 픽스처 재사용 방법
- 테스트 생성자 혹은 init과 같은 setup 특성 메소드에서 픽스처 초기화
- 준비 구절 코드는 대부분 제거할 수 있으나 단점 존재
- 테스트 간 결합도가 높아짐
- 테스트 가독성이 떨어짐
using Book.Chapter2.Listing1;
using Xunit;
namespace Book.Chapter3.CustomerTests_3
{
public class CustomerTests
{
private readonly Store _store;
private readonly Customer _sut;
public CustomerTests()
// 클래스 내 각 테스트 이전에 호출
{
_store = new Store();
_store.AddInventory(Product.Shampoo, 10);
_sut = new Customer();
}
[Fact]
public void Purchase_succeeds_when_enough_inventory()
{
bool success = _sut.Purchase(_store, Product.Shampoo, 5);
Assert.True(success);
Assert.Equal(5, _store.GetInventory(Product.Shampoo));
}
[Fact]
public void Purchase_fails_when_not_enough_inventory()
{
bool success = _sut.Purchase(_store, Product.Shampoo, 15);
Assert.False(success);
Assert.Equal(10, _store.GetInventory(Product.Shampoo));
}
}
}
테스트 간의 높은 결합도는 안티 패턴이다
- 위 A 예시에서는 테스트 아래 처럼 준비 로직 수정시 모든 테스트에 영향을 미친다.
_store.AddInventory(Product.Shampoo, 10); -> _store.AddInventory(Product.Shampoo, 15);
- 테스트를 수정해도 다른 테스트에 영향을 주어서는 안된다.
그러므로 테스트 클래스에 공유 상태를 두지 말아야한다.
- 아래 필드들이 공유 상태 예시
private readonly Store _store;
private readonly Customer _sut;
테스트 가독성을 떨어뜨리는 생성자
- 생성자로 준비코드 추출시 테스트 가독성을 떨어트린다. 테스트만 보고 전체 그림을 볼 수 없다.
- 준비코드가 별로 없더라도 테스트 메소드로 바로 옮기는게 좋다.
- 독립적인 테스트는 애매모호한 불확실성을 두지 않는다. (헷갈리게 하지 않는다.)
B. 비공개 팩토리 메소드 - 테스트 픽스처 재사용 방법
테스트 클래스에 비공개 팩토리 메소드를 두어라
- 공통 초기화 코드를 비공개 팩토리 메소드로 추출하여, 테스트 전체 맥락을 유지하라
- 아래처럼 읽기 쉽고 재사용 가능하게 테스트 픽스처를 생성하라
Store store = CreateStoreWithInventory(Product.Shampoo, 10)
- 이렇게 비공개 메소드를 간편하게 일반화 하는것도 쉽지 않은 것 같은데.. 어느정도는 자유롭게 풀어 쓰는것도 괜찮다고 생각
- 테스트 코드도 코드긴하나, 현실적으로 하나하나 챙기면서 가면 지칠거같다.
- 비공개 메소드도 생성자랑 동일하게 변경 될 수 있다..
생성자든 뭐든 결국 이 글이 말하고자 함은 공유 상태의 컨트롤(공유 상태를 테스트 내에서 공유하지 말자)
측면으로 이해된다.
namespace Book.Chapter3.CustomerTests_4
{
public class CustomerTests
{
[Fact]
public void Purchase_succeeds_when_enough_inventory()
{
Store store = CreateStoreWithInventory(Product.Shampoo, 10);
Customer sut = CreateCustomer();
bool success = sut.Purchase(store, Product.Shampoo, 5);
Assert.True(success);
Assert.Equal(5, store.GetInventory(Product.Shampoo));
}
private Store CreateStoreWithInventory(Product product, int quantity)
{
Store store = new Store();
store.AddInventory(product, quantity);
return store;
}
}
}
3.4 단위 테스트 명명법
- [테스트 대상 메서드][시나리오][예상결과]
- 동작 대신 구현 세부 사항에 집중하게끔 부추기기에 도움이 되지 않는다.
초반에 정확히 이런식으로 많이 작성했었다. 지금 와서 보면 뭐하는 테스트인지 알수가 없음 ㅋㅋ..
- 그래도 케바케로 가끔 예상 결과를 디테일하게 적어놓는것은 도움이 되더라.
- 특정 에러가 떨어지는것을 보장해야 할 경우 - onErrorResume 등 특정 에러만을 복구하게끔 코드 구성
다음 지침을 따르자.
- 엄격한 명명 정책을 따르지 말자. 복잡한 동작에 대한 높은 수준의 설명을 이러한 정책의 좁은 상자 안에 넣을 수 없다. 표현의 자유 허용
- 문제 도메인에 익숙한 비개발자들에게 시나리오를 설명하는 것처럼 테스트 이름을 짓자. 도메인 전문가나 비즈니스 분석가가 좋은 예다.
- 단어를 밑줄 표시로 구분한다.
테스트 클래스 이름
- [클래스명]Tests 패턴
- 단위 테스트의 단위는
동작
임을 잊지 말고.. - 그래도 어딘가에선 시작해야하니,
[클래스명]Tests
를 동작 단위로 검증할 수 있는 진입점으로 보자
예제: 지침에 따른 테스트 이름 변경
- 잘못된 날짜의 배송을 올바르게 식별하는지 검증하는 테스트
- isDeliveryValid_InvalidDate_ReturnsFalse()
- Delivery_with_invalid_date_should_be_considered_invalid()
- ...
- 수정 결과: Delivery_with_a_past_date_is_invalid()
실제 예시
- https://oss.navercorp.com/MediaPlatform/scs-plutus/blob/master/scs-plutus-businesses/scs-plutus-business/src/test/java/com/naver/scs/pay/service/coupon/CouponEventDynamicConditionalProcessorModeTest.java#L116
- 지급조건에_부합하는경우_쿠폰을_지급한다
- GIVABLE_외_쿠폰은_지급대상이아니다
- 회수조건에_부합하는경우_쿠폰을_회수한다
- 회수조건에_부합하지않는경우_회수하지않는다
- POSESSION_NON_GIVABLE_외_쿠폰은_회수대상쿠폰이아니다
- 지급조건_회수조건_모두_충족해도_GIVABLE쿠폰은_지급로직을탄다
3.5 매개변수화된 테스트 리팩토링
- 보통 테스트 하나로 동작 단위를 완전하게 설명하기 충분지 않다.
- 동작이 충분히 복잡하면 이를 설명하는데 테스트 수가 급격히 증가, 관리가 어려워질 수 있다.
Test 대상: 배송일이 오늘로부터 이틀 후가 되도록 작동하는 배송 기능
- 지난 날짜: Delivery_with_a_past_date_is_invalid()
- 오늘 날짜: Delivery_with_for_today_is_invalid()
- 내일 날짜: Delivery_with_for_tomorrow_is_invalid()
- 이틀 후 날짜 : The_soonest_delivery_date_is_two_days_from_now()
- else: ...
유일한 차이는 배송 날짜뿐이나 테스트가 여러가지 나옴 -> 이러한 테스트를 하나로 묶자
- junit의 @ParameterizedTest
- 예제 코드처럼 작성시 테스트 메서드가 나타내는 사실을 파악하기 어려움
절충안
- 긍정적인 테스트 케이스만 고유한 테스트로 도출한다.
- 코드 28~59 line
그러나, 동작이 너무 복잡하면 매개변수화 테스트를 조금도 사용하지 말아라, 긍정/부정 모두 각각 고유의 메소드로 나타내라.
3.6 검증문 라이브러리를 통해 가독성 향상 (Feat.Fluent Assertions)
- 이야기 형태로 정보를 흡수, 오 좋은데..?
- [주어] [행동] [목적어]
- https://github.com/AcornPublishing/unit-testing/blob/main/Book/Chapter3/FluentAssertions_1/CalculatorTests.cs
namespace Book.Chapter3.FluentAssertions_1
{
public class CalculatorTests
{
[Fact]
public void Sum_of_two_numbers()
{
double first = 10;
double second = 20;
var sut = new Calculator();
double result = sut.Sum(first, second);
result.Should().Be(30);
}
}
public class Calculator
{
public double Sum(double first, double second)
{
return first + second;
}
}
}
'Tech' 카테고리의 다른 글
BFS로 서비스 스펙 나름 재밌게 친 썰 (0) | 2022.07.05 |
---|---|
약 500건의 unit test를 작성하고 느낀점 (0) | 2022.07.05 |
MongoDB CDC, Change Streams (0) | 2021.11.12 |
Neo4j memory 구성 (0) | 2020.07.01 |
Kafka (0) | 2020.06.03 |
Comments