[단위 테스트] 단위 테스트 구조
좋은 단위 테스트 작성하기
1. AAA 패턴 사용하기
- Given/When/Then으로 지칭하기도 한다.
2. 테스트 코드에서 if 문은 사용하지 말라
- 테스트에서 케이스가 생기는 if문 자체가 안티 패턴이다.
3. 준비 (Arrange) 구절
- 3가지 구절에서 보통 가장 길다. 적절한 디자인 패턴으로 리팩토링을 하여 코드 줄 수를 줄인다.
4. 실행 (Act) 구절
- 실행 구절이 한 줄 인 경우를 피하라. 두 줄 이상의 코드를 호출해야한다면 API 설계가 잘 못 된 것이다.
5. 검증 (Assert) 구절
- 적절한 객체 동등비교 equaltiy 논리를 구현하고 검증 구절을 간소화 한다.
6. 검증 대상 객체 (SUT)
- SUT를 지칭하는 변수를 sut라고 작성하는 등 SUT를 구별한다.
테스트 간 테스트 픽스처 재사용
테스트 픽스처
-> 테스트를 담당하는 클래스를 의미한다.
google_test에서의 테스트 픽스처 예시
http://google.github.io/googletest/primer.html#same-data-multiple-tests
google_test의 TEST_F를 사용한 테스트 픽스처 사용 예시
-> ::testing::Test 클래스를 상속하여 작성한다.
-> TEST_F(테스트 픽스처 클래스, 테스트 케이스 이름)을 사용한다.
class CustomerTests : public ::testing::Test
{
protected:
Store _store;
Customer _sut;
public:
CustomerTests();
virtual void SetUp() override {
_store.AddInventory(Product::Shampoo, 10);
}
virtual void TearDown() override {
}
};
#include "pch.h"
#include "CustomerTests.h"
TEST_F(CustomerTests, 여유있는_인벤_구매_성공) {
bool success = _sut.Purchase(_store, Product::Shampoo, 5);
ASSERT_TRUE(success);
ASSERT_EQ(5, _store.GetInventory(Product::Shampoo));
}
TEST_F(CustomerTests, 빈약한_인벤_구매_실패) {
bool success = _sut.Purchase(_store, Product::Shampoo, 15);
ASSERT_FALSE(success);
ASSERT_EQ(10, _store.GetInventory(Product::Shampoo));
}
더 나은 테스트 픽스처 작성하기
위 시나리오에서 SetUp에서 설정된 Store가 다음과 같이 초기화 되면 두번째 시나리오가 성공하게 된다.
_store.AddInventory(Product::Shampoo, 15);
따라서 다음과 같이 각 시나리오에서 Store 및 Customer 클래스를 초기화하여 사용한다.
Store CreateStoreWithInventory(Product product, int count) {
Store store;
store.AddInventory(product, count);
return store;
}
Customer CreateCustomer() {
return Customer{};
}
TEST_F(CustomerTests, 여유있는_인벤_구매_성공) {
Store store = CreateStoreWithInventory(Product::Shampoo, 10);
Customer sut = CreateCustomer();
bool success = sut.Purchase(store, Product::Shampoo, 5);
ASSERT_TRUE(success);
ASSERT_EQ(5, store.GetInventory(Product::Shampoo));
}
TEST_F(CustomerTests, 빈약한_인벤_구매_실패) {
Store store = CreateStoreWithInventory(Product::Shampoo, 10);
Customer sut = CreateCustomer();
bool success = sut.Purchase(store, Product::Shampoo, 15);
ASSERT_FALSE(success);
ASSERT_EQ(10, store.GetInventory(Product::Shampoo));
}
단위 테스트 명명법
명명법의 예시
[테스트 대상 메서드]_[시나리오]_[예상결과]
: 테스트 하는 메서드 이름 + 시나리오 테스트 조건 + 어떤 결과를 예상하는 지
-> 엄격한 명명법을 사용하지 않는다. 읽기 쉽고 누구나 보면 이해가능한 이름으로 명명하자.
-> 비개발자가 이해할 수 있는 실제 비지니스 로직을 나타내는 이름을 생각해보자.
파라미터화된 테스트 픽스처
위 시나리오는 구매 성공/실패의 시나리오로 명백한 두 결과에 대한 테스트로 나누는 것이 좋지만,
일반적인 경우 결과가 bool 타입이 아닌 다양한 결과를 예상되는 타입에서는 여러 테스트 케이스가 존재할 수 있다.
따라서 테스트 케이스의 입력값에 따른 결과를 일반화하여 하나의 테스트 군으로 단위 테스트하는 것도 가능하다.
google_test의 TEST_P를 사용한 매개변수화된 테스트 픽스처 사용 예시
::testing::TestWithParam을 상속 받는 테스트 픽스처를 정의한다.
TEST_P(Prefix, 테스트 스위트 픽스처 클래스, 시나리오 이름)로 정의된 Test Suite는 GetParam()을 통해 넘겨진 파라미터에 대한 단위 테스트 검증을 수행한다.
/*
초기 상품 / 초기 상품 개수 / 구매 개수 / 결과
*/
class ParameterizedCustomerTests :
public ::testing::TestWithParam<std::tuple<Product, int, int, bool>>
{
};
TEST_P(ParameterizedCustomerTests, CheckCustomerPurchase) {
auto& param = GetParam();
Product product = std::get<0>(param);
int initCount = std::get<1>(param);
int purchaseCount = std::get<2>(param);
bool expected = std::get<3>(param);
int expectedCount = expected ? initCount - purchaseCount : initCount;
Store store = CreateStoreWithInventory(product, initCount);
Customer sut = CreateCustomer();
bool success = sut.Purchase(store, product, purchaseCount);
ASSERT_EQ(success, expected);
ASSERT_EQ(expectedCount, store.GetInventory(product));
}
INSTANTIATE_TEST_SUITE_P(
CustomerTest,
ParameterizedCustomerTests,
::testing::Values(
std::make_tuple(Product::Shampoo, 10, 15, false),
std::make_tuple(Product::Shampoo, 10, 5, true),
std::make_tuple(Product::Shampoo, 5, 5, true)));
요약
-> AAA 패턴으로 좋은 단위 테스트를 구성하라
-> 실행 구절은 한 줄이어야 한다. 아니면 API 설계를 잘 못 한 것이다.
-> SUT 객체의 변수 이름을 sut로 지정하라
->테스트 픽스처 초기화 코드는 팩터리 메서드로 초기화 하라
-> 테스트 명명법은 읽기 쉽도록 유연하게 작성하라
-> 일반적 테스트 스위트를 사용하고자 하면 매개 변수화된 테스트 픽스처를 이용하라
참고
Unit Testing 단위 테스트, 블라디미르 코리코프 지음
https://www.sandordargo.com/blog/2019/04/24/parameterized-testing-with-gtest#parameterizedtests
https://github.com/jinsnowy/UnitTest_Study