Skip to content

Commit 3c2d639

Browse files
committed
[chore] #171 성능 테스트 코드 작성
1 parent db702b4 commit 3c2d639

File tree

3 files changed

+268
-6
lines changed

3 files changed

+268
-6
lines changed

Projects/Feature/FeatureContentCard/Sources/ContentCard/ContentCardFeature.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ public struct ContentCardFeature {
4848
case 메타데이터_조회
4949
}
5050

51+
@CasePathable
5152
public enum InnerAction: Equatable {
5253
case 메타데이터_조회_수행_반영(String)
5354
case 즐겨찾기_API_반영(Bool)
5455
}
5556

57+
@CasePathable
5658
public enum AsyncAction: Equatable {
5759
case 메타데이터_조회_수행
5860
case 즐겨찾기_API
@@ -167,10 +169,7 @@ private extension ContentCardFeature {
167169
return .run { [content = state.content] _ in
168170
let request = ThumbnailRequest(thumbnail: content.thumbNail)
169171

170-
try await contentClient.썸네일_수정(
171-
contentId: "\(content.id)",
172-
model: request
173-
)
172+
try await contentClient.썸네일_수정("\(content.id)", request)
174173
}
175174
}
176175
}
Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,89 @@
11
import ComposableArchitecture
22
import XCTest
3+
import Domain
4+
import CoreKit
5+
import DSKit
6+
import Util
37

48
@testable import FeatureContentCard
59

6-
final class FeatureContentCardTests: XCTestCase {
7-
func test() {
10+
final class SendTests: XCTestCase {
811

12+
func test_primeTest() async {
13+
let count = 10
14+
var sharedAverage: CFAbsoluteTime = 0.0
15+
for _ in 0..<count {
16+
await test_shared_메서드_적용(&sharedAverage)
17+
}
18+
19+
var noSharedAverage: CFAbsoluteTime = 0.0
20+
for _ in 0..<count {
21+
await test_shared_메서드_미적용(&noSharedAverage)
22+
}
23+
24+
print(
25+
"shared_메서드_적용 \(count)번 테스트 평균 소요시간",
26+
sharedAverage / CFAbsoluteTime(count)
27+
)
28+
print(
29+
"shared_메서드_미적용 \(count)번 테스트 평균 소요시간",
30+
noSharedAverage / CFAbsoluteTime(count)
31+
)
32+
}
33+
34+
@MainActor
35+
func test_shared_메서드_적용(_ average: inout CFAbsoluteTime) async {
36+
let store = TestStore(initialState: ContentCardFeature.State(
37+
content: ContentBaseResponse.mock(id: 0).toDomain()
38+
)) {
39+
ContentCardFeature()
40+
} withDependencies: {
41+
$0[ContentClient.self] = .testValue
42+
let parseOGImageURL: @Sendable (
43+
_ url: URL
44+
) async throws -> String? = { _ in
45+
"https://i.ytimg.com/vi/wtSwdGJzQCQ/maxresdefault.jpg"
46+
}
47+
48+
$0[SwiftSoupClient.self].parseOGImageURL = parseOGImageURL
49+
}
50+
51+
let start = CFAbsoluteTimeGetCurrent()
52+
await store.send(.view(.메타데이터_조회))
53+
await store.receive(\.inner.메타데이터_조회_수행_반영) {
54+
$0.content.thumbNail = "https://i.ytimg.com/vi/wtSwdGJzQCQ/maxresdefault.jpg"
55+
}
56+
let end = CFAbsoluteTimeGetCurrent()
57+
average += end - start
58+
}
59+
60+
@MainActor
61+
func test_shared_메서드_미적용(_ average: inout CFAbsoluteTime) async {
62+
let store = TestStore(initialState: LegacyContentCardFeature.State(
63+
content: ContentBaseResponse.mock(id: 0).toDomain()
64+
)) {
65+
LegacyContentCardFeature()
66+
} withDependencies: {
67+
$0[ContentClient.self] = .testValue
68+
let parseOGImageURL: @Sendable (
69+
_ url: URL
70+
) async throws -> String? = { _ in
71+
"https://i.ytimg.com/vi/wtSwdGJzQCQ/maxresdefault.jpg"
72+
}
73+
74+
$0[SwiftSoupClient.self].parseOGImageURL = parseOGImageURL
75+
}
76+
77+
let start = CFAbsoluteTimeGetCurrent()
78+
await store.send(.view(.메타데이터_조회))
79+
await store.receive(\.async.메타데이터_조회_수행)
80+
await store.receive(\.inner.메타데이터_조회_수행_반영) {
81+
$0.content.thumbNail = "https://i.ytimg.com/vi/wtSwdGJzQCQ/maxresdefault.jpg"
82+
}
83+
await store.receive(\.async.썸네일_수정_API)
84+
let end = CFAbsoluteTimeGetCurrent()
85+
average += end - start
986
}
1087
}
88+
89+
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//
2+
// LegacyContentCardFeature.swift
3+
// FeatureContentCardTests
4+
//
5+
// Created by 김도형 on 1/17/25.
6+
//
7+
8+
import SwiftUI
9+
10+
import ComposableArchitecture
11+
import Domain
12+
import CoreKit
13+
import DSKit
14+
import Util
15+
16+
@Reducer
17+
public struct LegacyContentCardFeature {
18+
/// - Dependency
19+
@Dependency(SwiftSoupClient.self)
20+
private var swiftSoupClient
21+
@Dependency(\.openURL)
22+
private var openURL
23+
@Dependency(ContentClient.self)
24+
private var contentClient
25+
/// - State
26+
@ObservableState
27+
public struct State: Equatable, Identifiable {
28+
public let id = UUID()
29+
public var content: BaseContentItem
30+
31+
public init(content: BaseContentItem) {
32+
self.content = content
33+
}
34+
}
35+
36+
/// - Action
37+
public enum Action: FeatureAction, ViewAction {
38+
case view(View)
39+
case inner(InnerAction)
40+
case async(AsyncAction)
41+
case scope(ScopeAction)
42+
case delegate(DelegateAction)
43+
44+
@CasePathable
45+
public enum View: Equatable {
46+
case 컨텐츠_항목_눌렀을때
47+
case 컨텐츠_항목_케밥_버튼_눌렀을때
48+
case 즐겨찾기_버튼_눌렀을때
49+
case 메타데이터_조회
50+
}
51+
52+
@CasePathable
53+
public enum InnerAction: Equatable {
54+
case 메타데이터_조회_수행_반영(String)
55+
case 즐겨찾기_API_반영(Bool)
56+
}
57+
58+
@CasePathable
59+
public enum AsyncAction: Equatable {
60+
case 메타데이터_조회_수행
61+
case 즐겨찾기_API
62+
case 즐겨찾기_취소_API
63+
case 썸네일_수정_API
64+
}
65+
66+
public enum ScopeAction: Equatable { case doNothing }
67+
68+
public enum DelegateAction: Equatable {
69+
case 컨텐츠_항목_케밥_버튼_눌렀을때(content: BaseContentItem)
70+
}
71+
}
72+
73+
/// - Initiallizer
74+
public init() {}
75+
76+
/// - Reducer Core
77+
private func core(into state: inout State, action: Action) -> Effect<Action> {
78+
switch action {
79+
/// - View
80+
case .view(let viewAction):
81+
return handleViewAction(viewAction, state: &state)
82+
83+
/// - Inner
84+
case .inner(let innerAction):
85+
return handleInnerAction(innerAction, state: &state)
86+
87+
/// - Async
88+
case .async(let asyncAction):
89+
return handleAsyncAction(asyncAction, state: &state)
90+
91+
/// - Scope
92+
case .scope(let scopeAction):
93+
return handleScopeAction(scopeAction, state: &state)
94+
95+
/// - Delegate
96+
case .delegate(let delegateAction):
97+
return handleDelegateAction(delegateAction, state: &state)
98+
}
99+
}
100+
101+
/// - Reducer body
102+
public var body: some ReducerOf<Self> {
103+
Reduce(self.core)
104+
}
105+
}
106+
//MARK: - FeatureAction Effect
107+
private extension LegacyContentCardFeature {
108+
/// - View Effect
109+
func handleViewAction(_ action: Action.View, state: inout State) -> Effect<Action> {
110+
switch action {
111+
case .컨텐츠_항목_눌렀을때:
112+
guard let url = URL(string: state.content.data) else {
113+
return .none
114+
}
115+
return .run { _ in await openURL(url) }
116+
case .컨텐츠_항목_케밥_버튼_눌렀을때:
117+
return .send(.delegate(.컨텐츠_항목_케밥_버튼_눌렀을때(content: state.content)))
118+
case .메타데이터_조회:
119+
return .send(.async(.메타데이터_조회_수행))
120+
case .즐겨찾기_버튼_눌렀을때:
121+
guard let isFavorite = state.content.isFavorite else {
122+
return .none
123+
}
124+
UIImpactFeedbackGenerator(style: .light)
125+
.impactOccurred()
126+
return isFavorite
127+
? .send(.async(.즐겨찾기_취소_API))
128+
: .send(.async(.즐겨찾기_API))
129+
}
130+
}
131+
132+
/// - Inner Effect
133+
func handleInnerAction(_ action: Action.InnerAction, state: inout State) -> Effect<Action> {
134+
switch action {
135+
case let .메타데이터_조회_수행_반영(imageURL):
136+
state.content.thumbNail = imageURL
137+
return .send(.async(.썸네일_수정_API))
138+
case .즐겨찾기_API_반영(let favorite):
139+
state.content.isFavorite = favorite
140+
return .none
141+
}
142+
}
143+
144+
/// - Async Effect
145+
func handleAsyncAction(_ action: Action.AsyncAction, state: inout State) -> Effect<Action> {
146+
switch action {
147+
case .메타데이터_조회_수행:
148+
guard let url = URL(string: state.content.data) else {
149+
return .none
150+
}
151+
return .run { send in
152+
let imageURL = try await swiftSoupClient.parseOGImageURL(url)
153+
guard let imageURL else { return }
154+
await send(.inner(.메타데이터_조회_수행_반영(imageURL)))
155+
}
156+
case .즐겨찾기_API:
157+
return .run { [id = state.content.id] send in
158+
let _ = try await contentClient.즐겨찾기("\(id)")
159+
await send(.inner(.즐겨찾기_API_반영(true)), animation: .pokitDissolve)
160+
}
161+
case .즐겨찾기_취소_API:
162+
return .run { [id = state.content.id] send in
163+
try await contentClient.즐겨찾기_취소("\(id)")
164+
await send(.inner(.즐겨찾기_API_반영(false)), animation: .pokitDissolve)
165+
}
166+
case .썸네일_수정_API:
167+
return .run { [content = state.content] _ in
168+
let request = ThumbnailRequest(thumbnail: content.thumbNail)
169+
170+
try await contentClient.썸네일_수정("\(content.id)", request)
171+
}
172+
}
173+
}
174+
175+
/// - Scope Effect
176+
func handleScopeAction(_ action: Action.ScopeAction, state: inout State) -> Effect<Action> {
177+
return .none
178+
}
179+
180+
/// - Delegate Effect
181+
func handleDelegateAction(_ action: Action.DelegateAction, state: inout State) -> Effect<Action> {
182+
return .none
183+
}
184+
}

0 commit comments

Comments
 (0)