From 9882f8392421ff3a35e3762adfee568a15a96047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=80=EC=A7=80?= Date: Mon, 17 Aug 2026 20:10:28 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B9=85=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20DiningViewCo?= =?UTF-8?q?ntroller=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4=20internal?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dining/Dining/DiningViewController.swift | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/Koin/Presentation/Dining/Dining/DiningViewController.swift b/Koin/Presentation/Dining/Dining/DiningViewController.swift index d288e123..b664afc9 100644 --- a/Koin/Presentation/Dining/Dining/DiningViewController.swift +++ b/Koin/Presentation/Dining/Dining/DiningViewController.swift @@ -14,8 +14,8 @@ final class DiningViewController: UIViewController { // MARK: - Properties private let viewModel: DiningViewModel - private let inputSubject: PassthroughSubject = .init() private var subscriptions: Set = [] + let inputSubject: PassthroughSubject = .init() private let refreshControl = UIRefreshControl() private var viewDidAppeared = false @@ -33,7 +33,7 @@ final class DiningViewController: UIViewController { } }() - private let diningTypeSegmentControl = UISegmentedControl().then { + let diningTypeSegmentControl = UISegmentedControl().then { $0.setBackgroundImage(UIImage(), for: .normal, barMetrics: .default) $0.setDividerImage(UIImage(), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default) $0.insertSegment(withTitle: "아침", at: 0, animated: true) @@ -62,7 +62,7 @@ final class DiningViewController: UIViewController { $0.layer.applySketchShadow(color: .appColor(.neutral800), alpha: 0.02, x: 0, y: 1, blur: 1, spread: 0) } - private let diningListCollectionView: DiningCollectionView = { + let diningListCollectionView: DiningCollectionView = { let flowLayout = UICollectionViewFlowLayout().then { $0.scrollDirection = .vertical } @@ -131,11 +131,7 @@ final class DiningViewController: UIViewController { super.viewDidAppear(true) checkAndShowBottomSheet() if viewDidAppeared { - switch diningTypeSegmentControl.selectedSegmentIndex { - case 0: inputSubject.send(.updateDisplayDateTime(nil, .breakfast)) - case 1: inputSubject.send(.updateDisplayDateTime(nil, .lunch)) - default: inputSubject.send(.updateDisplayDateTime(nil, .dinner)) - } + inputSubject.send(.updateDisplayDateTime(nil, currentDiningType)) } viewDidAppeared = true @@ -151,7 +147,7 @@ final class DiningViewController: UIViewController { private func bind() { let outputSubject = viewModel.transform(with: inputSubject.eraseToAnyPublisher()) outputSubject.receive(on: DispatchQueue.main).sink { [weak self] output in - guard let strongSelf = self else { return } + guard self != nil else { return } switch output { case let .updateDiningList(list, diningType): self?.setDiningList(list) @@ -177,7 +173,7 @@ final class DiningViewController: UIViewController { zoomedImageViewController.setImage(tappedDiningImage) self.present(zoomedImageViewController, animated: true, completion: nil) - inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuImage, .click, "\(self.getCurrentDiningType())_\(tappedPlaceText)")) + inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuImage, .click, "\(self.currentDiningType.name)_\(tappedPlaceText)")) }.store(in: &subscriptions) @@ -188,7 +184,7 @@ final class DiningViewController: UIViewController { diningListCollectionView.logScrollPublisher.sink { [weak self] _ in guard let self = self else { return } - self.inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuTime, .scroll, self.getCurrentDiningType())) + self.inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuTime, .scroll, self.currentDiningType.name)) }.store(in: &subscriptions) NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification).sink { [weak self] _ in @@ -215,7 +211,7 @@ extension DiningViewController { diningListCollectionView.addGestureRecognizer(swipeRightGesture) } - @objc private func handleSwipe(_ gesture: UISwipeGestureRecognizer) { + @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { let currentSegmentIndex = diningTypeSegmentControl.selectedSegmentIndex if gesture.direction == .left { if currentSegmentIndex < diningTypeSegmentControl.numberOfSegments - 1 { @@ -230,14 +226,9 @@ extension DiningViewController { } @objc private func refresh() { - switch diningTypeSegmentControl.selectedSegmentIndex { - case 0: inputSubject.send(.updateDisplayDateTime(nil, .breakfast)) - case 1: inputSubject.send(.updateDisplayDateTime(nil, .lunch)) - default: inputSubject.send(.updateDisplayDateTime(nil, .dinner)) - } - - inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuTime, .click, getCurrentDiningType())) - + inputSubject.send(.updateDisplayDateTime(nil, currentDiningType)) + inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuTime, .click, currentDiningType.name)) + refreshControl.endRefreshing() } @@ -284,13 +275,10 @@ extension DiningViewController { } @objc private func segmentDidChange(_ sender: UISegmentedControl) { - switch sender.selectedSegmentIndex { - case 0: inputSubject.send(.updateDisplayDateTime(nil, .breakfast)) - case 1: inputSubject.send(.updateDisplayDateTime(nil, .lunch)) - default: inputSubject.send(.updateDisplayDateTime(nil, .dinner)) - } - - inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuTime, .click, getCurrentDiningType())) + let diningType = DiningType(segmentIndex: sender.selectedSegmentIndex) ?? .dinner + + inputSubject.send(.updateDisplayDateTime(nil, diningType)) + inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.menuTime, .click, diningType.name)) } private func moveUnderLineView(diningType: DiningType) { @@ -332,12 +320,8 @@ extension DiningViewController { diningListCollectionView.setDiningList(list) } - private func getCurrentDiningType() -> String { - switch diningTypeSegmentControl.selectedSegmentIndex { - case 0: return "아침" - case 1: return "점심" - default: return "저녁" - } + private var currentDiningType: DiningType { + DiningType(segmentIndex: diningTypeSegmentControl.selectedSegmentIndex) ?? .dinner } } From 5902e88565f368b51c77d6176d47f6581bb42225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=80=EC=A7=80?= Date: Mon, 17 Aug 2026 20:14:32 +0900 Subject: [PATCH 2/7] =?UTF-8?q?add:=20=EC=84=B8=EA=B7=B8=EB=A8=BC=ED=8A=B8?= =?UTF-8?q?=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=EB=A1=9C=20DiningType=EC=9D=84?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20init=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DTOs/Decodable/Dining/DiningDto.swift | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Koin/Data/DTOs/Decodable/Dining/DiningDto.swift b/Koin/Data/DTOs/Decodable/Dining/DiningDto.swift index 56505954..40d8c0dc 100644 --- a/Koin/Data/DTOs/Decodable/Dining/DiningDto.swift +++ b/Koin/Data/DTOs/Decodable/Dining/DiningDto.swift @@ -35,7 +35,20 @@ struct DiningDto: Decodable { } func toDomain() -> DiningItem { - return .init(id: id, type: type, place: place, priceCard: priceCard, priceCash: priceCash, kcal: kcal ?? 0, menu: menu ?? [], soldoutAt: soldoutAt, changedAt: changedAt, imageUrl: imageURL, likes: likes, isLiked: isLiked, date: date + return .init( + id: id, + type: type, + place: place, + priceCard: priceCard, + priceCash: priceCash, + kcal: kcal ?? 0, + menu: menu ?? [], + soldoutAt: soldoutAt, + changedAt: changedAt, + imageUrl: imageURL, + likes: likes, + isLiked: isLiked, + date: date ) } } @@ -50,7 +63,16 @@ enum DiningType: String, Decodable { let rawValue = try container.decode(String.self) self = DiningType(rawValue: rawValue) ?? .breakfast } - + + init?(segmentIndex: Int) { + switch segmentIndex { + case 0: self = .breakfast + case 1: self = .lunch + case 2: self = .dinner + default: return nil + } + } + var name: String { switch self { case .breakfast : "아침" From cc8d618341cb87b3299b6475caf050fd31b7be26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=80=EC=A7=80?= Date: Mon, 17 Aug 2026 20:15:25 +0900 Subject: [PATCH 3/7] =?UTF-8?q?add:=20DiningType=20=EC=84=B8=EA=B7=B8?= =?UTF-8?q?=EB=A8=BC=ED=8A=B8=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- koinUnitTests/Dining/DiningTypeTests.swift | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 koinUnitTests/Dining/DiningTypeTests.swift diff --git a/koinUnitTests/Dining/DiningTypeTests.swift b/koinUnitTests/Dining/DiningTypeTests.swift new file mode 100644 index 00000000..a0f79a06 --- /dev/null +++ b/koinUnitTests/Dining/DiningTypeTests.swift @@ -0,0 +1,38 @@ +// +// DiningTypeTests.swift +// koinUnitTests +// +// Created by 이은지 on 8/17/26. +// + +import Foundation +import Testing +@testable import koin + +@Suite("DiningType - 세그먼트 인덱스 변환") +struct DiningTypeTests { + + @Test( + "세그먼트 인덱스를 시간대로 옮긴다", + arguments: [(0, DiningType.breakfast), (1, .lunch), (2, .dinner)] + ) + func 세그먼트_인덱스를_시간대로_옮긴다(index: Int, expected: DiningType) { + #expect(DiningType(segmentIndex: index) == expected) + } + + @Test( + "미선택이거나 범위 밖의 인덱스는 시간대를 특정하지 않는다", + arguments: [-1, 3, 100] + ) + func 미선택이거나_범위_밖의_인덱스는_시간대를_특정하지_않는다(index: Int) { + #expect(DiningType(segmentIndex: index) == nil) + } + + @Test( + "옮긴 시간대는 로깅에 쓰이는 한글 이름을 가진다", + arguments: [(0, "아침"), (1, "점심"), (2, "저녁")] + ) + func 옮긴_시간대는_로깅에_쓰이는_한글_이름을_가진다(index: Int, expectedName: String) { + #expect(DiningType(segmentIndex: index)?.name == expectedName) + } +} From 890d75cb83dac087819a750a5490e5c7dee0948e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=80=EC=A7=80?= Date: Mon, 17 Aug 2026 20:16:16 +0900 Subject: [PATCH 4/7] =?UTF-8?q?test:=20=EC=8B=9D=EB=8B=A8=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20Recorder?= =?UTF-8?q?=EC=99=80=20Stub=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Doubles/DiningViewModelStubs.swift | 55 ++++++++ koinUnitTests/Support/DiningFixtures.swift | 2 - .../Support/DiningLoggingTestSupport.swift | 122 ++++++++++++++++++ 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 koinUnitTests/Doubles/DiningViewModelStubs.swift create mode 100644 koinUnitTests/Support/DiningLoggingTestSupport.swift diff --git a/koinUnitTests/Doubles/DiningViewModelStubs.swift b/koinUnitTests/Doubles/DiningViewModelStubs.swift new file mode 100644 index 00000000..584cb57e --- /dev/null +++ b/koinUnitTests/Doubles/DiningViewModelStubs.swift @@ -0,0 +1,55 @@ +// +// DiningViewModelStubs.swift +// koinUnitTests +// +// Created by 이은지 on 8/17/26. +// + +import Alamofire +import Combine +import Foundation +@testable import koin + +final class StubFetchDiningListUseCase: FetchDiningListUseCase { + func execute(diningInfo: CurrentDiningTime) -> AnyPublisher<[DiningItem], ErrorResponse> { + Empty<[DiningItem], ErrorResponse>().eraseToAnyPublisher() + } +} + +final class StubDateProvider: DateProvider { + func execute(date: Date) -> CurrentDiningTime { + CurrentDiningTime(date: date, diningType: .breakfast) + } +} + +final class StubShareMenuListUseCase: ShareMenuListUseCase { + func execute(shareModel: ShareDiningMenu) {} +} + +final class StubChangeNotiUseCase: ChangeNotiUseCase { + func execute(method: Alamofire.HTTPMethod, type: SubscribeType) -> AnyPublisher { + Empty().eraseToAnyPublisher() + } +} + +final class StubChangeNotiDetailUseCase: ChangeNotiDetailUseCase { + func execute(method: Alamofire.HTTPMethod, detailType: DetailSubscribeType) -> AnyPublisher { + Empty().eraseToAnyPublisher() + } +} + +final class StubFetchNotiListUseCase: FetchNotiListUseCase { + func execute() -> AnyPublisher { + Empty().eraseToAnyPublisher() + } +} + +final class StubLogAnalyticsEventUseCase: LogAnalyticsEventUseCase { + func execute(label: EventLabelType, category: EventParameter.EventCategory, value: Any) {} + + func executeWithDuration(label: EventLabelType, category: EventParameter.EventCategory, value: Any, previousPage: String?, currentPage: String?, durationTime: String?) {} + + func logEvent(name: String, label: String, value: String, category: String) {} + + func executeWithSessionId(label: EventLabelType, category: EventParameter.EventCategory, value: Any, sessionId: String) {} +} diff --git a/koinUnitTests/Support/DiningFixtures.swift b/koinUnitTests/Support/DiningFixtures.swift index 0ceb71d9..b4bca54e 100644 --- a/koinUnitTests/Support/DiningFixtures.swift +++ b/koinUnitTests/Support/DiningFixtures.swift @@ -62,8 +62,6 @@ enum DiningFixture { ) } - /// 프로덕션 코드(`DefaultDateProvider`, `Date.formatDateToYYMMDD()`)가 `Calendar.current`와 - /// 기본 `DateFormatter`를 쓰기 때문에, 테스트도 같은 기준으로 Date를 만든다. static func date( year: Int = 2026, month: Int = 8, diff --git a/koinUnitTests/Support/DiningLoggingTestSupport.swift b/koinUnitTests/Support/DiningLoggingTestSupport.swift new file mode 100644 index 00000000..e1fa37c2 --- /dev/null +++ b/koinUnitTests/Support/DiningLoggingTestSupport.swift @@ -0,0 +1,122 @@ +// +// DiningLoggingTestSupport.swift +// koinUnitTests +// +// Created by 이은지 on 8/17/26. +// + +import Combine +import UIKit +@testable import koin + +struct DiningLoggedEvent: Equatable { + let label: String + let category: String + let value: String + + init( + _ label: String, + _ category: String, + _ value: String + ) { + self.label = label + self.category = category + self.value = value + } +} + +final class DiningInputRecorder { + + private(set) var inputs: [DiningViewModel.Input] = [] + private var cancellable: AnyCancellable? + + init(_ viewController: DiningViewController) { + cancellable = viewController.inputSubject.sink { [weak self] input in + self?.inputs.append(input) + } + } + + var loggedEvents: [DiningLoggedEvent] { + inputs.compactMap { input in + guard case let .logEvent(label, category, value) = input else { return nil } + return DiningLoggedEvent(label.rawValue, category.rawValue, "\(value)") + } + } + + var inputKinds: [String] { + inputs.map { input in + switch input { + case .updateDisplayDateTime: "updateDisplayDateTime" + case .shareMenuList: "shareMenuList" + case .determineInitDate: "determineInitDate" + case .changeNoti: "changeNoti" + case .fetchNotiList: "fetchNotiList" + case .logEvent: "logEvent" + case .logEventWithSessionId: "logEventWithSessionId" + } + } + } +} + +@MainActor +final class DiningLoggingTestBed { + + let sut: DiningViewController + let recorder: DiningInputRecorder + + init() { + sut = DiningViewController( + viewModel: DiningViewModel( + fetchDiningListUseCase: StubFetchDiningListUseCase(), + logAnalyticsEventUseCase: StubLogAnalyticsEventUseCase(), + dateProvder: StubDateProvider(), + shareMenuListUseCase: StubShareMenuListUseCase(), + changeNotiUseCase: StubChangeNotiUseCase(), + fetchNotiListUsecase: StubFetchNotiListUseCase(), + changeNotiDetailUseCase: StubChangeNotiDetailUseCase() + ) + ) + + sut.loadViewIfNeeded() + recorder = DiningInputRecorder(sut) + } + + func selectSegment(_ index: Int) { + sut.diningTypeSegmentControl.selectedSegmentIndex = index + } + + func tapSegment(_ index: Int) { + selectSegment(index) + sut.diningTypeSegmentControl.sendActions(for: .valueChanged) + } + + func swipe(_ direction: UISwipeGestureRecognizer.Direction) { + let gesture = UISwipeGestureRecognizer() + gesture.direction = direction + sut.handleSwipe(gesture) + } + + func pullToRefresh() { + sut.diningListCollectionView.refreshControl?.sendActions(for: .valueChanged) + } + + func tapMenuImage(place: String) { + sut.diningListCollectionView.imageTapPublisher.send((UIImage(), place)) + } + + func tapShareButton() { + sut.diningListCollectionView.shareButtonPublisher.send(DiningFixture.item().toShareDiningItem()) + } + + func scrollDiningList() { + sut.diningListCollectionView.logScrollPublisher.send(()) + } + + func tapCafeteriaInfoButton() { + guard let item = sut.navigationItem.rightBarButtonItem, + let action = item.action, + let target = item.target as? NSObject + else { return } + target.perform(action) + } +} From 752b1b6396433d2a2ff708d0f582a881707e98fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=80=EC=A7=80?= Date: Mon, 17 Aug 2026 20:16:30 +0900 Subject: [PATCH 5/7] =?UTF-8?q?test:=20=EC=8B=9D=EB=8B=A8=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- koinUnitTests/Dining/DiningLoggingTests.swift | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 koinUnitTests/Dining/DiningLoggingTests.swift diff --git a/koinUnitTests/Dining/DiningLoggingTests.swift b/koinUnitTests/Dining/DiningLoggingTests.swift new file mode 100644 index 00000000..f7a4d7d5 --- /dev/null +++ b/koinUnitTests/Dining/DiningLoggingTests.swift @@ -0,0 +1,102 @@ +// +// DiningLoggingTests.swift +// koinUnitTests +// +// Created by 이은지 on 8/17/26. +// + +import Foundation +import Testing +import UIKit +@testable import koin + +@MainActor +@Suite("DiningViewController - 로깅") +struct DiningLoggingTests { + + @Test( + "메뉴 이미지를 탭하면 시간대와 장소를 조합해 로깅한다", + arguments: [(1, "A코너", "점심_A코너"), (0, "B코너", "아침_B코너")] + ) + func 메뉴_이미지를_탭하면_시간대와_장소를_조합해_로깅한다(segmentIndex: Int, place: String, expectedValue: String) { + let bed = DiningLoggingTestBed() + bed.selectSegment(segmentIndex) + + bed.tapMenuImage(place: place) + + #expect(bed.recorder.loggedEvents == [DiningLoggedEvent("menu_image", "click", expectedValue)]) + } + + @Test("공유 버튼을 탭하면 menuShare 이벤트를 로깅한다") + func 공유_버튼을_탭하면_menuShare_이벤트를_로깅한다() { + let bed = DiningLoggingTestBed() + + bed.tapShareButton() + + #expect(bed.recorder.loggedEvents == [DiningLoggedEvent("menu_share", "click", "공유하기")]) + } + + @Test("공유 버튼을 한 번 탭하면 공유와 로그가 각각 한 번씩 발행된다") + func 공유_버튼을_한_번_탭하면_공유와_로그가_각각_한_번씩_발행된다() { + let bed = DiningLoggingTestBed() + + bed.tapShareButton() + + #expect(bed.recorder.inputKinds == ["shareMenuList", "logEvent"]) + } + + @Test( + "리스트를 스크롤하면 현재 시간대로 menuTime을 로깅한다", + arguments: [(0, "아침"), (1, "점심"), (2, "저녁")] + ) + func 리스트를_스크롤하면_현재_시간대로_menuTime을_로깅한다(segmentIndex: Int, expectedValue: String) { + let bed = DiningLoggingTestBed() + bed.selectSegment(segmentIndex) + + bed.scrollDiningList() + + #expect(bed.recorder.loggedEvents == [DiningLoggedEvent("menu_time", "scroll", expectedValue)]) + } + + @Test( + "당겨서 새로고침하면 현재 시간대로 menuTime을 로깅한다", + arguments: [(0, "아침"), (1, "점심"), (2, "저녁")] + ) + func 당겨서_새로고침하면_현재_시간대로_menuTime을_로깅한다(segmentIndex: Int, expectedValue: String) { + let bed = DiningLoggingTestBed() + bed.selectSegment(segmentIndex) + + bed.pullToRefresh() + + #expect(bed.recorder.loggedEvents == [DiningLoggedEvent("menu_time", "click", expectedValue)]) + } + + @Test("학생식당 정보 버튼을 탭하면 cafeteriaInfo 이벤트를 로깅한다") + func 학생식당_정보_버튼을_탭하면_cafeteriaInfo_이벤트를_로깅한다() { + let bed = DiningLoggingTestBed() + + bed.tapCafeteriaInfoButton() + + #expect(bed.recorder.loggedEvents == [DiningLoggedEvent("cafeteria_info", "click", "학생식당정보")]) + } + + @Test("세그먼트를 전환하면 변경된 시간대로 menuTime을 로깅한다") + func 세그먼트를_전환하면_변경된_시간대로_menuTime을_로깅한다() { + let bed = DiningLoggingTestBed() + bed.selectSegment(0) + + bed.tapSegment(1) + + #expect(bed.recorder.loggedEvents == [DiningLoggedEvent("menu_time", "click", "점심")]) + } + + @Test("스와이프로 전환해도 동일하게 로깅한다") + func 스와이프로_전환해도_동일하게_로깅한다() { + let bed = DiningLoggingTestBed() + bed.selectSegment(1) + + bed.swipe(.left) + + #expect(bed.recorder.loggedEvents == [DiningLoggedEvent("menu_time", "click", "저녁")]) + } +} From 15c88453baa675d1a51a3729beb0063b2bd7cf2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=80=EC=A7=80?= Date: Mon, 17 Aug 2026 20:16:33 +0900 Subject: [PATCH 6/7] =?UTF-8?q?chore:=20=EB=A1=9C=EA=B9=85=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20koinUnitTest?= =?UTF-8?q?s=20=ED=83=80=EA=B2=9F=EC=97=90=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- koin.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/koin.xcodeproj/project.pbxproj b/koin.xcodeproj/project.pbxproj index a0a0f5bc..7aec8453 100644 --- a/koin.xcodeproj/project.pbxproj +++ b/koin.xcodeproj/project.pbxproj @@ -736,6 +736,10 @@ AA20000000000000000004 /* DateProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA10000000000000000004 /* DateProviderTests.swift */; }; AA20000000000000000005 /* FetchDiningListUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA10000000000000000005 /* FetchDiningListUseCaseTests.swift */; }; AA20000000000000000006 /* ShareMenuListUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA10000000000000000006 /* ShareMenuListUseCaseTests.swift */; }; + AA20000000000000000007 /* DiningLoggingTestSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA10000000000000000007 /* DiningLoggingTestSupport.swift */; }; + AA20000000000000000008 /* DiningViewModelStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA10000000000000000008 /* DiningViewModelStubs.swift */; }; + AA20000000000000000009 /* DiningLoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA10000000000000000009 /* DiningLoggingTests.swift */; }; + AA20000000000000000010 /* DiningTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA10000000000000000010 /* DiningTypeTests.swift */; }; B438AEE42E756AB600E889C4 /* OrderImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B438AEE32E756AB600E889C4 /* OrderImage.swift */; }; B46B8CC22E76CB7300A8E797 /* ShopSummaryDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46B8CC12E76CB7300A8E797 /* ShopSummaryDto.swift */; }; B46B8CC42E76CC6300A8E797 /* FetchOrderShopSummaryFromShopUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B46B8CC32E76CC6300A8E797 /* FetchOrderShopSummaryFromShopUseCase.swift */; }; @@ -1744,6 +1748,10 @@ AA10000000000000000004 /* DateProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateProviderTests.swift; sourceTree = ""; }; AA10000000000000000005 /* FetchDiningListUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchDiningListUseCaseTests.swift; sourceTree = ""; }; AA10000000000000000006 /* ShareMenuListUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareMenuListUseCaseTests.swift; sourceTree = ""; }; + AA10000000000000000007 /* DiningLoggingTestSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiningLoggingTestSupport.swift; sourceTree = ""; }; + AA10000000000000000008 /* DiningViewModelStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiningViewModelStubs.swift; sourceTree = ""; }; + AA10000000000000000009 /* DiningLoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiningLoggingTests.swift; sourceTree = ""; }; + AA10000000000000000010 /* DiningTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiningTypeTests.swift; sourceTree = ""; }; B438AEE32E756AB600E889C4 /* OrderImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderImage.swift; sourceTree = ""; }; B46B8CC12E76CB7300A8E797 /* ShopSummaryDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopSummaryDto.swift; sourceTree = ""; }; B46B8CC32E76CC6300A8E797 /* FetchOrderShopSummaryFromShopUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchOrderShopSummaryFromShopUseCase.swift; sourceTree = ""; }; @@ -4532,6 +4540,7 @@ children = ( AA10000000000000000001 /* PublisherTestSupport.swift */, AA10000000000000000002 /* DiningFixtures.swift */, + AA10000000000000000007 /* DiningLoggingTestSupport.swift */, ); path = Support; sourceTree = ""; @@ -4540,6 +4549,7 @@ isa = PBXGroup; children = ( AA10000000000000000003 /* SpyDiningRepository.swift */, + AA10000000000000000008 /* DiningViewModelStubs.swift */, ); path = Doubles; sourceTree = ""; @@ -4550,6 +4560,8 @@ AA10000000000000000004 /* DateProviderTests.swift */, AA10000000000000000005 /* FetchDiningListUseCaseTests.swift */, AA10000000000000000006 /* ShareMenuListUseCaseTests.swift */, + AA10000000000000000009 /* DiningLoggingTests.swift */, + AA10000000000000000010 /* DiningTypeTests.swift */, ); path = Dining; sourceTree = ""; @@ -6628,6 +6640,10 @@ AA20000000000000000004 /* DateProviderTests.swift in Sources */, AA20000000000000000005 /* FetchDiningListUseCaseTests.swift in Sources */, AA20000000000000000006 /* ShareMenuListUseCaseTests.swift in Sources */, + AA20000000000000000007 /* DiningLoggingTestSupport.swift in Sources */, + AA20000000000000000008 /* DiningViewModelStubs.swift in Sources */, + AA20000000000000000009 /* DiningLoggingTests.swift in Sources */, + AA20000000000000000010 /* DiningTypeTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 8a6daf6c4e347899c12a7121f8be53f634d48172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9D=80=EC=A7=80?= Date: Mon, 17 Aug 2026 20:33:48 +0900 Subject: [PATCH 7/7] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=EC=97=90=20=EB=A7=9E=EC=B6=B0=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=ED=83=AD=20arguments=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- koinUnitTests/Dining/DiningLoggingTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koinUnitTests/Dining/DiningLoggingTests.swift b/koinUnitTests/Dining/DiningLoggingTests.swift index f7a4d7d5..4229a5c7 100644 --- a/koinUnitTests/Dining/DiningLoggingTests.swift +++ b/koinUnitTests/Dining/DiningLoggingTests.swift @@ -16,7 +16,7 @@ struct DiningLoggingTests { @Test( "메뉴 이미지를 탭하면 시간대와 장소를 조합해 로깅한다", - arguments: [(1, "A코너", "점심_A코너"), (0, "B코너", "아침_B코너")] + arguments: [(0, "A코너", "아침_A코너"), (1, "B코너", "점심_B코너"), (2, "C코너", "저녁_C코너")] ) func 메뉴_이미지를_탭하면_시간대와_장소를_조합해_로깅한다(segmentIndex: Int, place: String, expectedValue: String) { let bed = DiningLoggingTestBed()