세 가지 스타일
- 출력 기반 테스트: 입력을 주고 반환된 출력만 검증하는 방식으로, 부작용이 없는 코드(수학적 함수)에 적합하다.
import Testing
struct Product {
let name: String
}
struct PriceEngine {
func calculateDiscount(_ products: Product...) -> Decimal {
let discount = Decimal(products.count) * 0.01
return min(discount, 0.2)
}
}
@Test
func discountOfTwoProducts() {
let product1 = Product(name: "Hand wash")
let product2 = Product(name: "Shampoo")
let sut = PriceEngine()
let discount = sut.calculateDiscount(product1, product2)
#expect(discount == 0.02)
}
- 상태 기반 테스트: 동작 후 SUT, 협력자, DB/파일 등 시스템의 상태를 검증한다.
import Testing
struct Product: Equatable {
let name: String
}
struct Order {
private var products: [Product] = []
var allProducts: [Product] { products }
mutating func addProduct(_ product: Product) {
products.append(product)
}
}
@Test
func addingAProductToAnOrder() {
let product = Product(name: "Hand wash")
var sut = Order()
sut.addProduct(product)
#expect(sut.allProducts.count == 1)
#expect(sut.allProducts[0] == product)
}
- 커뮤니케이션 기반 테스트: Mock으로 SUT와 협력자 간 상호작용(메서드 호출)을 검증한다.
import Testing
protocol EmailGateway {
func sendGreetingsEmail(_ email: String)
}
struct Controller {
private let emailGateway: EmailGateway
init(_ emailGateway: EmailGateway) {
self.emailGateway = emailGateway
}
func greetUser(_ email: String) {
emailGateway.sendGreetingsEmail(email)
}
}
final class EmailGatewayMock: EmailGateway {
private(set) var receivedEmails: [String] = []
func sendGreetingsEmail(_ email: String) {
receivedEmails.append(email)
}
}
@Test
func sendingAGreetingsEmail() {
let emailGatewayMock = EmailGatewayMock()
let sut = Controller(emailGatewayMock)
sut.greetUser("user@email.com")
#expect(emailGatewayMock.receivedEmails == ["user@email.com"])
}
4대 요소로 본 비교
- 회귀 방지, 피드백 속도: 세 스타일 모두 원칙적으로 동일하나, 커뮤니케이션 기반은 남용 시 얕은 테스트가 되기 쉽다.
- 리팩토링 내성: 출력 기반이 가장 강하고, 상태 기반은 중간, 커뮤니케이션 기반은 상호작용 결합으로 가장 취약하다.
- 유지보수성: 출력 기반이 가장 짧고 간결하며, 상태 기반은 단언이 비대해지기 쉽고, 커뮤니케이션 기반은 Mock 설정, 검증으로 가장 크다.
| 구분 | 출력 기반 스타일 | 상태 기반 스타일 | 커뮤니케이션 기반 스타일 |
|---|
| 회귀 방지 | 동일 | 동일 | 동일 |
| 피드백 속도 | 동일 | 동일 | 동일 |
| 리팩토링 내성 | 낮음 | 중간 | 중간 |
| 유지보수성 | 낮음 | 중간 | 높음 |
함수형 아키텍처
- 함수형 프로그래밍은 수학적 함수(mathematical function) 을 사용한 프로그래밍이다.
func calculateDiscount(_ products: [Product]) -> Decimal {
let discount = Decimal(products.count) * 0.01
return min(discount, 0.2)
}

- 함수형 아키텍쳐는 부작용을 비즈니스 로직 동작의 끝으로 몰아서 비즈니스 로직과 부작용을 분리한다.
- 가변 셸은 모든 입력을 수집한다.
- 함수형 코어는 결정을 생성한다.
- 가변 셸은 결정을 부작용으로 변환한다.
