TDD/단위 테스트

[단위 테스트] 단위 테스트 구조

로파이 2022. 12. 18. 18:10

좋은 단위 테스트 작성하기

 

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

 

Googletest Primer

GoogleTest - Google Testing and Mocking Framework

google.github.io


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