Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 31 additions & 12 deletions AIProject/iCoTests/ImageProcessTestHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,45 @@ import UIKit
@testable import iCo

// MARK: - Helper Methods
final class ImageProcessTestHelpers {
static func createTestImage(with text: String) -> UIImage {
let size = CGSize(width: 300, height: 100)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
final class ImageProcessTestHelpers {
static func createTestImage(with text: String) -> CGImage? {
let width = 300
let height = 100
let colorSpace = CGColorSpaceCreateDeviceRGB()

let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.white.cgColor)
context?.fill(CGRect(origin: .zero, size: size))
// RGBA 8비트 구성으로 CGContext 생성
guard let context = CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: width * 4,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else {
return nil
}

// 배경 흰색
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(x: 0, y: 0, width: width, height: height))

// UIKit 없이 텍스트를 직접 Core Graphics로 그리려면 NSAttributedString을 활용해야 함
let textRect = CGRect(x: 10, y: 40, width: 280, height: 50)
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 20),
.foregroundColor: UIColor.black
]

text.draw(at: CGPoint(x: 10, y: 40), withAttributes: attributes)

let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let attributedText = NSAttributedString(string: text, attributes: attributes)
let framesetter = CTFramesetterCreateWithAttributedString(attributedText)
let path = CGMutablePath()
path.addRect(textRect)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedText.length), path, nil)
CTFrameDraw(frame, context)

return image ?? UIImage()
// CGContext → CGImage 생성
return context.makeImage()
}

static func createMockCoinDTO() -> CoinDTO {
Expand Down
35 changes: 2 additions & 33 deletions AIProject/iCoTests/ImageProcessViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class ImageProcessViewModelTests: XCTestCase {
// 이미지에서 글자를 찾지 못했을 때 알맞은 에러를 반환하는지?
func testImageProcessViewModel_whenNoTextIsReturned_terminateWithError() async throws {
// Given
mockImage = ImageProcessTestHelpers.createTestImage(with: "").cgImage
mockImage = ImageProcessTestHelpers.createTestImage(with: "")

// When
sut = ImageProcessViewModel()
Expand All @@ -47,42 +47,11 @@ final class ImageProcessViewModelTests: XCTestCase {
// Then
XCTAssertFalse(sut.isLoading)
XCTAssertFalse(sut.showAnalysisResultAlert)

XCTAssertTrue(sut.showErrorMessage)
XCTAssertNotNil(sut.errorMessage)
XCTAssertTrue(sut.verifiedCoinList.isEmpty)

XCTAssertEqual(sut.errorMessage, ImageProcessError.noRecognizedText.description)
}

// Alan에게서 코인 이름을 받아오지 못했을 때 알맞은 에러를 반환하는지?
func testImageProcessViewModel_whenNoConvertedSymbol_terminateWithError() async throws {
// Alan API키를 검증하고, 키가 없을 시에는 테스트 건너뛰기
guard let apiKey = Bundle.main.infoDictionary?["ALAN_API_KEY_COIN_ID_EXTRACTION"] as? String,
!apiKey.isEmpty else {
throw XCTSkip("테스트에 필요한 Alan API 키를 읽어오지 못함")
}

// Given
mockImage = ImageProcessTestHelpers.createTestImage(with: "bittcoin batcoin").cgImage

// When
sut = ImageProcessViewModel()
sut.coinList = mockCoinList
sut.processImage(from: mockImage)

if let task = sut.processImageTask {
_ = try? await task.value
}

// Then
XCTAssertFalse(sut.isLoading)
XCTAssertFalse(sut.showAnalysisResultAlert)

XCTAssertTrue(sut.showErrorMessage)
XCTAssertNotNil(sut.errorMessage)
XCTAssertTrue(sut.verifiedCoinList.isEmpty)

XCTAssertEqual(sut.errorMessage, ImageProcessError.noExtractedCoinID.description)
XCTAssertEqual(sut.errorMessage, ImageProcessError.noRecognizedText.description)
}
}
127 changes: 62 additions & 65 deletions AIProject/iCoTests/TextRecognitionHelperTests.swift
Original file line number Diff line number Diff line change
@@ -1,67 +1,64 @@
////
//// TextRecognitionHelperTests.swift
//// AIProjectTests
////
//// Created by Kitcat Seo on 8/15/25.
////
//
//import XCTest
//import Vision
//@testable import iCo
// TextRecognitionHelperTests.swift
// AIProjectTests
//
//// MARK: - TextRecognitionHelper 테스트
//final class TextRecognitionHelperTests: XCTestCase {
// var sut: TextRecognitionHelper!
//
// var mockCoinList: Set<String>!
// var mockImage: UIImage!
//
// override func setUp() async throws {
// try await super.setUp()
// mockCoinList = ["bitcoin"]
// }
//
// override func tearDown() async throws {
// sut = nil
// mockCoinList = nil
// mockImage = nil
// try await super.tearDown()
// }
//
// // test대상_작업_예상결과
// // OCR 결과 텍스트에 코인 이름이 포함돼있을 때 결과를 정상적으로 반환하는지?
// func testHandleOCR_whenTextContainsCoinNames_returnsCoinList() async throws {
// // Given
// mockImage = ImageProcessTestHelpers.createTestImage(with: "sunrise maple orbit bitcoin velvet")
// sut = TextRecognitionHelper(
// image: mockImage,
// coinNames: mockCoinList
// )
//
// // When
// let raw = try await sut.handleOCR()
// let results = raw.first!.components(separatedBy: " ")
//
// // Then
// XCTAssertFalse(results.isEmpty)
// XCTAssertTrue(results.contains("bitcoin"))
// }
//
// // OCR 결과 텍스트 중 코인 이름이 아닌 문자열에 정상적으로 마스킹이 실행되는지?
// func testHandleOCR_whenTextContainsNonCoinNamesOnly_returnsMaskingCharactorsOnly() async throws {
// // Given
// mockImage = ImageProcessTestHelpers.createTestImage(with: "bittcoin 배트코인")
// sut = TextRecognitionHelper(
// image: mockImage,
// coinNames: mockCoinList
// )
//
// // When
// let ocrResult = try await sut.handleOCR()
// let allCharacters = ocrResult.joined()
//
// // Then
// let maskingCharacters: Set<Character> = ["*", " "]
// XCTAssertTrue(allCharacters.allSatisfy { maskingCharacters.contains($0) })
// }
//}
// Created by Kitcat Seo on 8/15/25.
//

import XCTest
import Vision
@testable import iCo

// MARK: - TextRecognitionHelper 테스트
final class TextRecognitionHelperTests: XCTestCase {
var sut: TextRecognitionHelper!

var mockCoinList: Set<String>!
var mockImage: CGImage!

override func setUp() async throws {
try await super.setUp()
mockCoinList = ["bitcoin"]
}

override func tearDown() async throws {
sut = nil
mockCoinList = nil
mockImage = nil
try await super.tearDown()
}

// OCR 결과 텍스트에 코인 이름이 포함돼있을 때 결과를 정상적으로 반환하는지?
func testHandleOCR_whenTextContainsCoinNames_returnsCoinList() async throws {
// Given
guard let mockImage = ImageProcessTestHelpers.createTestImage(with: "sunrise maple orbit bitcoin velvet") else {
throw XCTSkip("이미지가 정상적으로 생성되지 않음")
}
sut = TextRecognitionHelper()

// When
let raw = try await sut.handleOCR(from: mockImage, with: mockCoinList)
let results = raw.first!.components(separatedBy: " ")

// Then
XCTAssertFalse(results.isEmpty)
XCTAssertTrue(results.contains("bitcoin"))
}

// OCR 결과 텍스트 중 코인 이름이 아닌 문자열에 정상적으로 마스킹이 실행되는지?
func testHandleOCR_whenTextContainsNonCoinNamesOnly_returnsMaskingCharactorsOnly() async throws {
// Given
guard let mockImage = ImageProcessTestHelpers.createTestImage(with: "bittcoin 배트코인") else {
throw XCTSkip("이미지가 정상적으로 생성되지 않음")
}
sut = TextRecognitionHelper()

// When
let ocrResult = try await sut.handleOCR(from: mockImage, with: mockCoinList)
let allCharacters = ocrResult.joined()

// Then
let maskingCharacters: Set<Character> = ["*", " "]
XCTAssertTrue(allCharacters.allSatisfy { maskingCharacters.contains($0) })
}
}
Loading