From 88a85a707498da38b37eb4dfe760b66d9a6b7902 Mon Sep 17 00:00:00 2001
From: Yeonnies <126739852+Yeonnies@users.noreply.github.com>
Date: Mon, 3 Nov 2025 15:28:53 +0900
Subject: [PATCH 1/5] Update README.md
---
README.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/README.md b/README.md
index b86bb3b..fc4200e 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,9 @@
# 37-STUDY-iOS-TestCode
37기 iOS 테스트코드 스터디
+
+### 2주차 과제 방법
+1. `이름/main` 형식의 브랜치를 팝니다.
+2. 그 브랜치를 베이스로 `이름/week03` 브랜치를 파신 후
+3. PR을 작성하시면 끗~
+4. 베이스가 본인이 생성한 브랜치가 맞는지 확인해주세요.
+
From b4e101ff11a69739def6074ade4b1328fd3b4819 Mon Sep 17 00:00:00 2001
From: Yeonnies
Date: Fri, 26 Dec 2025 23:19:08 +0900
Subject: [PATCH 2/5] =?UTF-8?q?[feat]=20=EA=B3=84=EC=82=B0=EA=B8=B0=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../CalculatorTest.xcodeproj/project.pbxproj | 573 ++++++++++++++++++
.../contents.xcworkspacedata | 7 +
.../AccentColor.colorset/Contents.json | 11 +
.../AppIcon.appiconset/Contents.json | 35 ++
.../Assets.xcassets/Contents.json | 6 +
.../CalculatorTest/CalculatorApp.swift | 17 +
.../CalculatorTest/CalculatorModel.swift | 104 ++++
.../CalculatorTest/CalculatorView.swift | 113 ++++
.../CalculatorTest/CalculatorViewModel.swift | 87 +++
.../CalculatorTestTests.swift | 36 ++
10 files changed, 989 insertions(+)
create mode 100644 CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj
create mode 100644 CalculatorTest/CalculatorTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
create mode 100644 CalculatorTest/CalculatorTest/Assets.xcassets/AccentColor.colorset/Contents.json
create mode 100644 CalculatorTest/CalculatorTest/Assets.xcassets/AppIcon.appiconset/Contents.json
create mode 100644 CalculatorTest/CalculatorTest/Assets.xcassets/Contents.json
create mode 100644 CalculatorTest/CalculatorTest/CalculatorApp.swift
create mode 100644 CalculatorTest/CalculatorTest/CalculatorModel.swift
create mode 100644 CalculatorTest/CalculatorTest/CalculatorView.swift
create mode 100644 CalculatorTest/CalculatorTest/CalculatorViewModel.swift
create mode 100644 CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift
diff --git a/CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj b/CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..c0b71d6
--- /dev/null
+++ b/CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj
@@ -0,0 +1,573 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXContainerItemProxy section */
+ 33A5D54D2EFEB7030049557E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33A5D5372EFEB7000049557E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33A5D53E2EFEB7000049557E;
+ remoteInfo = CalculatorTest;
+ };
+ 33A5D5572EFEB7040049557E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33A5D5372EFEB7000049557E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33A5D53E2EFEB7000049557E;
+ remoteInfo = CalculatorTest;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 33A5D53F2EFEB7000049557E /* CalculatorTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CalculatorTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33A5D54C2EFEB7030049557E /* CalculatorTestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CalculatorTestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33A5D5562EFEB7040049557E /* CalculatorTestUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CalculatorTestUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 33A5D5792EFECAE20049557E /* Exceptions for "CalculatorTest" folder in "CalculatorTestTests" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ CalculatorApp.swift,
+ CalculatorModel.swift,
+ CalculatorView.swift,
+ CalculatorViewModel.swift,
+ );
+ target = 33A5D54B2EFEB7030049557E /* CalculatorTestTests */;
+ };
+ 33A5D57A2EFECAE20049557E /* Exceptions for "CalculatorTest" folder in "CalculatorTestUITests" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ CalculatorApp.swift,
+ CalculatorModel.swift,
+ CalculatorView.swift,
+ CalculatorViewModel.swift,
+ );
+ target = 33A5D5552EFEB7040049557E /* CalculatorTestUITests */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 33A5D5412EFEB7000049557E /* CalculatorTest */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 33A5D5792EFECAE20049557E /* Exceptions for "CalculatorTest" folder in "CalculatorTestTests" target */,
+ 33A5D57A2EFECAE20049557E /* Exceptions for "CalculatorTest" folder in "CalculatorTestUITests" target */,
+ );
+ path = CalculatorTest;
+ sourceTree = "";
+ };
+ 33A5D54F2EFEB7030049557E /* CalculatorTestTests */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = CalculatorTestTests;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 33A5D53C2EFEB7000049557E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33A5D5492EFEB7030049557E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33A5D5532EFEB7040049557E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 33A5D5362EFEB7000049557E = {
+ isa = PBXGroup;
+ children = (
+ 33A5D5412EFEB7000049557E /* CalculatorTest */,
+ 33A5D54F2EFEB7030049557E /* CalculatorTestTests */,
+ 33A5D5402EFEB7000049557E /* Products */,
+ );
+ sourceTree = "";
+ };
+ 33A5D5402EFEB7000049557E /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 33A5D53F2EFEB7000049557E /* CalculatorTest.app */,
+ 33A5D54C2EFEB7030049557E /* CalculatorTestTests.xctest */,
+ 33A5D5562EFEB7040049557E /* CalculatorTestUITests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 33A5D53E2EFEB7000049557E /* CalculatorTest */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 33A5D5602EFEB7040049557E /* Build configuration list for PBXNativeTarget "CalculatorTest" */;
+ buildPhases = (
+ 33A5D53B2EFEB7000049557E /* Sources */,
+ 33A5D53C2EFEB7000049557E /* Frameworks */,
+ 33A5D53D2EFEB7000049557E /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 33A5D5412EFEB7000049557E /* CalculatorTest */,
+ );
+ name = CalculatorTest;
+ packageProductDependencies = (
+ );
+ productName = CalculatorTest;
+ productReference = 33A5D53F2EFEB7000049557E /* CalculatorTest.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 33A5D54B2EFEB7030049557E /* CalculatorTestTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 33A5D5632EFEB7040049557E /* Build configuration list for PBXNativeTarget "CalculatorTestTests" */;
+ buildPhases = (
+ 33A5D5482EFEB7030049557E /* Sources */,
+ 33A5D5492EFEB7030049557E /* Frameworks */,
+ 33A5D54A2EFEB7030049557E /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 33A5D54E2EFEB7030049557E /* PBXTargetDependency */,
+ );
+ fileSystemSynchronizedGroups = (
+ 33A5D54F2EFEB7030049557E /* CalculatorTestTests */,
+ );
+ name = CalculatorTestTests;
+ packageProductDependencies = (
+ );
+ productName = CalculatorTestTests;
+ productReference = 33A5D54C2EFEB7030049557E /* CalculatorTestTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 33A5D5552EFEB7040049557E /* CalculatorTestUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 33A5D5662EFEB7040049557E /* Build configuration list for PBXNativeTarget "CalculatorTestUITests" */;
+ buildPhases = (
+ 33A5D5522EFEB7040049557E /* Sources */,
+ 33A5D5532EFEB7040049557E /* Frameworks */,
+ 33A5D5542EFEB7040049557E /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 33A5D5582EFEB7040049557E /* PBXTargetDependency */,
+ );
+ name = CalculatorTestUITests;
+ packageProductDependencies = (
+ );
+ productName = CalculatorTestUITests;
+ productReference = 33A5D5562EFEB7040049557E /* CalculatorTestUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 33A5D5372EFEB7000049557E /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1640;
+ LastUpgradeCheck = 1640;
+ TargetAttributes = {
+ 33A5D53E2EFEB7000049557E = {
+ CreatedOnToolsVersion = 16.4;
+ };
+ 33A5D54B2EFEB7030049557E = {
+ CreatedOnToolsVersion = 16.4;
+ TestTargetID = 33A5D53E2EFEB7000049557E;
+ };
+ 33A5D5552EFEB7040049557E = {
+ CreatedOnToolsVersion = 16.4;
+ TestTargetID = 33A5D53E2EFEB7000049557E;
+ };
+ };
+ };
+ buildConfigurationList = 33A5D53A2EFEB7000049557E /* Build configuration list for PBXProject "CalculatorTest" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 33A5D5362EFEB7000049557E;
+ minimizedProjectReferenceProxies = 1;
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 33A5D5402EFEB7000049557E /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 33A5D53E2EFEB7000049557E /* CalculatorTest */,
+ 33A5D54B2EFEB7030049557E /* CalculatorTestTests */,
+ 33A5D5552EFEB7040049557E /* CalculatorTestUITests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 33A5D53D2EFEB7000049557E /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33A5D54A2EFEB7030049557E /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33A5D5542EFEB7040049557E /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 33A5D53B2EFEB7000049557E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33A5D5482EFEB7030049557E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33A5D5522EFEB7040049557E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 33A5D54E2EFEB7030049557E /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33A5D53E2EFEB7000049557E /* CalculatorTest */;
+ targetProxy = 33A5D54D2EFEB7030049557E /* PBXContainerItemProxy */;
+ };
+ 33A5D5582EFEB7040049557E /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33A5D53E2EFEB7000049557E /* CalculatorTest */;
+ targetProxy = 33A5D5572EFEB7040049557E /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 33A5D55E2EFEB7040049557E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 33A5D55F2EFEB7040049557E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 33A5D5612EFEB7040049557E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = yeonnies.CalculatorTest;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 33A5D5622EFEB7040049557E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = yeonnies.CalculatorTest;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 33A5D5642EFEB7040049557E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = yeonnies.CalculatorTestTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CalculatorTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CalculatorTest";
+ };
+ name = Debug;
+ };
+ 33A5D5652EFEB7040049557E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.5;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = yeonnies.CalculatorTestTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CalculatorTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CalculatorTest";
+ };
+ name = Release;
+ };
+ 33A5D5672EFEB7040049557E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = yeonnies.CalculatorTestUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = CalculatorTest;
+ };
+ name = Debug;
+ };
+ 33A5D5682EFEB7040049557E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = yeonnies.CalculatorTestUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = CalculatorTest;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 33A5D53A2EFEB7000049557E /* Build configuration list for PBXProject "CalculatorTest" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33A5D55E2EFEB7040049557E /* Debug */,
+ 33A5D55F2EFEB7040049557E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33A5D5602EFEB7040049557E /* Build configuration list for PBXNativeTarget "CalculatorTest" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33A5D5612EFEB7040049557E /* Debug */,
+ 33A5D5622EFEB7040049557E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33A5D5632EFEB7040049557E /* Build configuration list for PBXNativeTarget "CalculatorTestTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33A5D5642EFEB7040049557E /* Debug */,
+ 33A5D5652EFEB7040049557E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33A5D5662EFEB7040049557E /* Build configuration list for PBXNativeTarget "CalculatorTestUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33A5D5672EFEB7040049557E /* Debug */,
+ 33A5D5682EFEB7040049557E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 33A5D5372EFEB7000049557E /* Project object */;
+}
diff --git a/CalculatorTest/CalculatorTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/CalculatorTest/CalculatorTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/CalculatorTest/CalculatorTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/CalculatorTest/CalculatorTest/Assets.xcassets/AccentColor.colorset/Contents.json b/CalculatorTest/CalculatorTest/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/CalculatorTest/CalculatorTest/Assets.xcassets/AppIcon.appiconset/Contents.json b/CalculatorTest/CalculatorTest/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..2305880
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,35 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/CalculatorTest/CalculatorTest/Assets.xcassets/Contents.json b/CalculatorTest/CalculatorTest/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/CalculatorTest/CalculatorTest/CalculatorApp.swift b/CalculatorTest/CalculatorTest/CalculatorApp.swift
new file mode 100644
index 0000000..88daa21
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/CalculatorApp.swift
@@ -0,0 +1,17 @@
+//
+// CalculatorApp.swift
+// CalculatorTest
+//
+// Created by 김나연 on 12/26/25.
+//
+
+import SwiftUI
+
+@main
+struct CalculatorApp: App {
+ var body: some Scene {
+ WindowGroup {
+ CalculatorView()
+ }
+ }
+}
diff --git a/CalculatorTest/CalculatorTest/CalculatorModel.swift b/CalculatorTest/CalculatorTest/CalculatorModel.swift
new file mode 100644
index 0000000..6101cd2
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/CalculatorModel.swift
@@ -0,0 +1,104 @@
+//
+// CalculatorModel.swift
+// CalculatorTest
+//
+// Created by 김나연 on 12/26/25.
+//
+
+import Foundation
+
+enum CalculatorOperation {
+ case add, subtract, multiply, divide, none
+}
+
+struct CalculatorModel {
+ private var accumulator: Double = 0
+ private var currentOperation: CalculatorOperation = .none
+ private var isNewNumber: Bool = true
+
+ var displayValue: String = "0"
+
+ // 숫자 입력 처리
+ mutating func inputNumber(_ number: String) {
+ if isNewNumber {
+ displayValue = number
+ isNewNumber = false
+ } else {
+ if number == "." && displayValue.contains(".") {
+ return // 소수점 중복 방지
+ }
+ displayValue += number
+ }
+ }
+
+ // 연산자 입력 처리
+ mutating func inputOperation(_ operation: CalculatorOperation) {
+ if let currentValue = Double(displayValue) {
+ if !isNewNumber {
+ performOperation()
+ }
+ accumulator = currentValue
+ currentOperation = operation
+ isNewNumber = true
+ }
+ }
+
+ // 계산 실행
+ mutating func performOperation() {
+ guard let currentValue = Double(displayValue) else { return }
+
+ switch currentOperation {
+ case .add:
+ accumulator += currentValue
+ case .subtract:
+ accumulator -= currentValue
+ case .multiply:
+ accumulator *= currentValue
+ case .divide:
+ if currentValue != 0 {
+ accumulator /= currentValue
+ } else {
+ displayValue = "Error"
+ reset()
+ return
+ }
+ case .none:
+ accumulator = currentValue
+ }
+
+ displayValue = formatNumber(accumulator)
+ currentOperation = .none
+ isNewNumber = true
+ }
+
+ // 초기화
+ mutating func reset() {
+ accumulator = 0
+ currentOperation = .none
+ isNewNumber = true
+ displayValue = "0"
+ }
+
+ // +/- 부호 변경
+ mutating func toggleSign() {
+ if let value = Double(displayValue) {
+ displayValue = formatNumber(-value)
+ }
+ }
+
+ // 퍼센트 계산
+ mutating func percentage() {
+ if let value = Double(displayValue) {
+ displayValue = formatNumber(value / 100)
+ }
+ }
+
+ // 숫자 포맷팅 (불필요한 소수점 제거)
+ private func formatNumber(_ number: Double) -> String {
+ if number.truncatingRemainder(dividingBy: 1) == 0 {
+ return String(format: "%.0f", number)
+ } else {
+ return String(number)
+ }
+ }
+}
diff --git a/CalculatorTest/CalculatorTest/CalculatorView.swift b/CalculatorTest/CalculatorTest/CalculatorView.swift
new file mode 100644
index 0000000..4ae609c
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/CalculatorView.swift
@@ -0,0 +1,113 @@
+//
+// CalculatorView.swift
+// CalculatorTest
+//
+// Created by 김나연 on 12/26/25.
+//
+
+import SwiftUI
+
+struct CalculatorView: View {
+ @StateObject private var viewModel = CalculatorViewModel()
+
+ let buttons: [[CalculatorButton]] = [
+ [.clear, .toggleSign, .percentage, .operation(.divide)],
+ [.digit(7), .digit(8), .digit(9), .operation(.multiply)],
+ [.digit(4), .digit(5), .digit(6), .operation(.subtract)],
+ [.digit(1), .digit(2), .digit(3), .operation(.add)],
+ [.digit(0), .decimal, .equals]
+ ]
+
+ var body: some View {
+ ZStack {
+ Color.black.ignoresSafeArea()
+
+ VStack(spacing: 12) {
+ Spacer()
+
+ // 디스플레이
+ displayView
+
+ // 버튼 그리드
+ buttonGrid
+ }
+ .padding()
+ }
+ }
+
+ // MARK: - Display View
+ private var displayView: some View {
+ HStack {
+ Spacer()
+ Text(viewModel.displayText)
+ .font(.system(size: 80, weight: .light))
+ .foregroundColor(.white)
+ .lineLimit(1)
+ .minimumScaleFactor(0.5)
+ .padding(.horizontal)
+ }
+ .frame(maxWidth: .infinity, alignment: .trailing)
+ .padding(.bottom, 20)
+ }
+
+ // MARK: - Button Grid
+ private var buttonGrid: some View {
+ VStack(spacing: 12) {
+ ForEach(buttons.indices, id: \.self) { row in
+ HStack(spacing: 12) {
+ ForEach(buttons[row], id: \.title) { button in
+ CalculatorButtonView(button: button) {
+ viewModel.buttonTapped(button)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// MARK: - Calculator Button View
+struct CalculatorButtonView: View {
+ let button: CalculatorButton
+ let action: () -> Void
+
+ var body: some View {
+ Button(action: action) {
+ Text(button.title)
+ .font(.system(size: 32, weight: .medium))
+ .foregroundColor(buttonForegroundColor)
+ .frame(width: buttonWidth, height: buttonHeight)
+ .background(buttonBackgroundColor)
+ .cornerRadius(buttonWidth / 2)
+ }
+ }
+
+ private var buttonWidth: CGFloat {
+ // 0 버튼은 2배 너비
+ if case .digit(0) = button {
+ return (UIScreen.main.bounds.width - 5 * 12) / 4 * 2 + 12
+ }
+ return (UIScreen.main.bounds.width - 5 * 12) / 4
+ }
+
+ private var buttonHeight: CGFloat {
+ return (UIScreen.main.bounds.width - 5 * 12) / 4
+ }
+
+ private var buttonBackgroundColor: Color {
+ switch button.backgroundColor {
+ case "orange":
+ return Color.orange
+ case "lightGray":
+ return Color(white: 0.8)
+ case "darkGray":
+ return Color(white: 0.3)
+ default:
+ return Color.gray
+ }
+ }
+
+ private var buttonForegroundColor: Color {
+ button.backgroundColor == "lightGray" ? .black : .white
+ }
+}
diff --git a/CalculatorTest/CalculatorTest/CalculatorViewModel.swift b/CalculatorTest/CalculatorTest/CalculatorViewModel.swift
new file mode 100644
index 0000000..f36d4bf
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/CalculatorViewModel.swift
@@ -0,0 +1,87 @@
+//
+// CalculatorViewModel.swift
+// CalculatorTest
+//
+// Created by 김나연 on 12/26/25.
+//
+
+import SwiftUI
+import Combine
+
+class CalculatorViewModel: ObservableObject {
+ @Published private(set) var displayText: String = "0"
+
+ private var model = CalculatorModel()
+
+ func buttonTapped(_ button: CalculatorButton) {
+ switch button {
+ case .digit(let number):
+ model.inputNumber(String(number))
+ case .decimal:
+ model.inputNumber(".")
+ case .operation(let op):
+ model.inputOperation(op)
+ case .equals:
+ model.performOperation()
+ case .clear:
+ model.reset()
+ case .toggleSign:
+ model.toggleSign()
+ case .percentage:
+ model.percentage()
+ }
+
+ updateDisplay()
+ }
+
+ private func updateDisplay() {
+ displayText = model.displayValue
+ }
+}
+
+// 계산기 버튼 타입 정의
+enum CalculatorButton: Equatable {
+ case digit(Int)
+ case decimal
+ case operation(CalculatorOperation)
+ case equals
+ case clear
+ case toggleSign
+ case percentage
+
+ var title: String {
+ switch self {
+ case .digit(let number):
+ return "\(number)"
+ case .decimal:
+ return "."
+ case .operation(let op):
+ switch op {
+ case .add: return "+"
+ case .subtract: return "-"
+ case .multiply: return "×"
+ case .divide: return "÷"
+ case .none: return ""
+ }
+ case .equals:
+ return "="
+ case .clear:
+ return "AC"
+ case .toggleSign:
+ return "±"
+ case .percentage:
+ return "%"
+ }
+ }
+
+ var backgroundColor: String {
+ switch self {
+ case .operation, .equals:
+ return "orange"
+ case .clear, .toggleSign, .percentage:
+ return "lightGray"
+ default:
+ return "darkGray"
+ }
+ }
+}
diff --git a/CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift b/CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift
new file mode 100644
index 0000000..d7a43e0
--- /dev/null
+++ b/CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift
@@ -0,0 +1,36 @@
+//
+// CalculatorTestTests.swift
+// CalculatorTestTests
+//
+// Created by 김나연 on 12/26/25.
+//
+
+import XCTest
+@testable import CalculatorTest
+
+final class CalculatorTestTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() throws {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ // Any test you write for XCTest can be annotated as throws and async.
+ // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
+ // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
+ }
+
+ func testPerformanceExample() throws {
+ // This is an example of a performance test case.
+ self.measure {
+ // Put the code you want to measure the time of here.
+ }
+ }
+
+}
From 5ae8fe7f120e6e9c6e424b2676cdcb7f8417dcd2 Mon Sep 17 00:00:00 2001
From: Yeonnies
Date: Fri, 26 Dec 2025 23:24:17 +0900
Subject: [PATCH 3/5] =?UTF-8?q?[refactor]=20DI=20=ED=98=95=EC=8B=9D?=
=?UTF-8?q?=EC=9C=BC=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
---
CalculatorTest/CalculatorTest/CalculatorModel.swift | 13 ++++++++++++-
.../CalculatorTest/CalculatorViewModel.swift | 7 ++++++-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/CalculatorTest/CalculatorTest/CalculatorModel.swift b/CalculatorTest/CalculatorTest/CalculatorModel.swift
index 6101cd2..4a7a198 100644
--- a/CalculatorTest/CalculatorTest/CalculatorModel.swift
+++ b/CalculatorTest/CalculatorTest/CalculatorModel.swift
@@ -11,7 +11,18 @@ enum CalculatorOperation {
case add, subtract, multiply, divide, none
}
-struct CalculatorModel {
+// Protocol for dependency injection
+protocol CalculatorModelProtocol {
+ var displayValue: String { get }
+ mutating func inputNumber(_ number: String)
+ mutating func inputOperation(_ operation: CalculatorOperation)
+ mutating func performOperation()
+ mutating func reset()
+ mutating func toggleSign()
+ mutating func percentage()
+}
+
+struct CalculatorModel: CalculatorModelProtocol {
private var accumulator: Double = 0
private var currentOperation: CalculatorOperation = .none
private var isNewNumber: Bool = true
diff --git a/CalculatorTest/CalculatorTest/CalculatorViewModel.swift b/CalculatorTest/CalculatorTest/CalculatorViewModel.swift
index f36d4bf..352d047 100644
--- a/CalculatorTest/CalculatorTest/CalculatorViewModel.swift
+++ b/CalculatorTest/CalculatorTest/CalculatorViewModel.swift
@@ -11,7 +11,12 @@ import Combine
class CalculatorViewModel: ObservableObject {
@Published private(set) var displayText: String = "0"
- private var model = CalculatorModel()
+ private var model: CalculatorModelProtocol
+
+ init(model: CalculatorModelProtocol = CalculatorModel()) {
+ self.model = model
+ updateDisplay()
+ }
func buttonTapped(_ button: CalculatorButton) {
switch button {
From 3b7faf28e6d11482a1d847b6d103d046c182627a Mon Sep 17 00:00:00 2001
From: Yeonnies
Date: Fri, 26 Dec 2025 23:37:13 +0900
Subject: [PATCH 4/5] =?UTF-8?q?[chore]=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84?=
=?UTF-8?q?=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../CalculatorTest.xcodeproj/project.pbxproj | 2 +
.../CalculatorTest/CalculatorButtonView.swift | 53 +++++++++++++++++++
.../CalculatorTest/CalculatorView.swift | 46 ----------------
3 files changed, 55 insertions(+), 46 deletions(-)
create mode 100644 CalculatorTest/CalculatorTest/CalculatorButtonView.swift
diff --git a/CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj b/CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj
index c0b71d6..1edc1b6 100644
--- a/CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj
+++ b/CalculatorTest/CalculatorTest.xcodeproj/project.pbxproj
@@ -34,6 +34,7 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
CalculatorApp.swift,
+ CalculatorButtonView.swift,
CalculatorModel.swift,
CalculatorView.swift,
CalculatorViewModel.swift,
@@ -44,6 +45,7 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
CalculatorApp.swift,
+ CalculatorButtonView.swift,
CalculatorModel.swift,
CalculatorView.swift,
CalculatorViewModel.swift,
diff --git a/CalculatorTest/CalculatorTest/CalculatorButtonView.swift b/CalculatorTest/CalculatorTest/CalculatorButtonView.swift
new file mode 100644
index 0000000..ad15411
--- /dev/null
+++ b/CalculatorTest/CalculatorTest/CalculatorButtonView.swift
@@ -0,0 +1,53 @@
+//
+// CalculatorButtonView.swift
+// CalculatorTest
+//
+// Created by 김나연 on 12/26/25.
+//
+
+import SwiftUI
+
+struct CalculatorButtonView: View {
+ let button: CalculatorButton
+ let action: () -> Void
+
+ var body: some View {
+ Button(action: action) {
+ Text(button.title)
+ .font(.system(size: 32, weight: .medium))
+ .foregroundColor(buttonForegroundColor)
+ .frame(width: buttonWidth, height: buttonHeight)
+ .background(buttonBackgroundColor)
+ .cornerRadius(buttonWidth / 2)
+ }
+ }
+
+ private var buttonWidth: CGFloat {
+ // 0 버튼은 2배 너비
+ if case .digit(0) = button {
+ return (UIScreen.main.bounds.width - 5 * 12) / 4 * 2 + 12
+ }
+ return (UIScreen.main.bounds.width - 5 * 12) / 4
+ }
+
+ private var buttonHeight: CGFloat {
+ return (UIScreen.main.bounds.width - 5 * 12) / 4
+ }
+
+ private var buttonBackgroundColor: Color {
+ switch button.backgroundColor {
+ case "orange":
+ return Color.orange
+ case "lightGray":
+ return Color(white: 0.8)
+ case "darkGray":
+ return Color(white: 0.3)
+ default:
+ return Color.gray
+ }
+ }
+
+ private var buttonForegroundColor: Color {
+ button.backgroundColor == "lightGray" ? .black : .white
+ }
+}
diff --git a/CalculatorTest/CalculatorTest/CalculatorView.swift b/CalculatorTest/CalculatorTest/CalculatorView.swift
index 4ae609c..c953c68 100644
--- a/CalculatorTest/CalculatorTest/CalculatorView.swift
+++ b/CalculatorTest/CalculatorTest/CalculatorView.swift
@@ -65,49 +65,3 @@ struct CalculatorView: View {
}
}
}
-
-// MARK: - Calculator Button View
-struct CalculatorButtonView: View {
- let button: CalculatorButton
- let action: () -> Void
-
- var body: some View {
- Button(action: action) {
- Text(button.title)
- .font(.system(size: 32, weight: .medium))
- .foregroundColor(buttonForegroundColor)
- .frame(width: buttonWidth, height: buttonHeight)
- .background(buttonBackgroundColor)
- .cornerRadius(buttonWidth / 2)
- }
- }
-
- private var buttonWidth: CGFloat {
- // 0 버튼은 2배 너비
- if case .digit(0) = button {
- return (UIScreen.main.bounds.width - 5 * 12) / 4 * 2 + 12
- }
- return (UIScreen.main.bounds.width - 5 * 12) / 4
- }
-
- private var buttonHeight: CGFloat {
- return (UIScreen.main.bounds.width - 5 * 12) / 4
- }
-
- private var buttonBackgroundColor: Color {
- switch button.backgroundColor {
- case "orange":
- return Color.orange
- case "lightGray":
- return Color(white: 0.8)
- case "darkGray":
- return Color(white: 0.3)
- default:
- return Color.gray
- }
- }
-
- private var buttonForegroundColor: Color {
- button.backgroundColor == "lightGray" ? .black : .white
- }
-}
From 2d3bad09798fe5d0c222e13392b60d6083915141 Mon Sep 17 00:00:00 2001
From: Yeonnies
Date: Fri, 26 Dec 2025 23:37:24 +0900
Subject: [PATCH 5/5] =?UTF-8?q?[test]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?=
=?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../CalculatorTestTests.swift | 90 +++++++++++++++----
1 file changed, 72 insertions(+), 18 deletions(-)
diff --git a/CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift b/CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift
index d7a43e0..ac5552c 100644
--- a/CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift
+++ b/CalculatorTest/CalculatorTestTests/CalculatorTestTests.swift
@@ -10,27 +10,81 @@ import XCTest
final class CalculatorTestTests: XCTestCase {
- override func setUpWithError() throws {
- // Put setup code here. This method is called before the invocation of each test method in the class.
+ var sut: CalculatorModel!
+
+ override func setUp() {
+ super.setUp()
+ sut = CalculatorModel()
}
-
- override func tearDownWithError() throws {
- // Put teardown code here. This method is called after the invocation of each test method in the class.
+
+ override func tearDown() {
+ sut = nil
+ super.tearDown()
}
- func testExample() throws {
- // This is an example of a functional test case.
- // Use XCTAssert and related functions to verify your tests produce the correct results.
- // Any test you write for XCTest can be annotated as throws and async.
- // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
- // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
+
+ // MARK: - Addition Tests
+
+ func test_performOperation_AddTwoNumbers_ReturnsSum() {
+ // Given: 5 + 3을 입력
+ sut.inputNumber("5")
+ sut.inputOperation(.add)
+ sut.inputNumber("3")
+
+ // When: = 버튼을 누름
+ sut.performOperation()
+
+ // Then: 결과는 8
+ XCTAssertEqual(sut.displayValue, "8")
}
-
- func testPerformanceExample() throws {
- // This is an example of a performance test case.
- self.measure {
- // Put the code you want to measure the time of here.
- }
+
+
+ // MARK: - Subtraction Tests
+
+ func test_performOperation_SubtractTwoNumbers_ReturnsDifference() {
+ // Given: 10 - 3을 입력
+ sut.inputNumber("1")
+ sut.inputNumber("0")
+ sut.inputOperation(.subtract)
+ sut.inputNumber("3")
+
+ // When: = 버튼을 누름
+ sut.performOperation()
+
+ // Then: 결과는 7
+ XCTAssertEqual(sut.displayValue, "7")
+ }
+
+
+ // MARK: - Multiplication Tests
+
+ func test_performOperation_MultiplyTwoNumbers_ReturnsProduct() {
+ // Given: 4 × 5를 입력
+ sut.inputNumber("4")
+ sut.inputOperation(.multiply)
+ sut.inputNumber("5")
+
+ // When: = 버튼을 누름
+ sut.performOperation()
+
+ // Then: 결과는 20
+ XCTAssertEqual(sut.displayValue, "20")
+ }
+
+
+ // MARK: - Division Tests
+
+ func test_performOperation_DivideTwoNumbers_ReturnsQuotient() {
+ // Given: 15 ÷ 3을 입력
+ sut.inputNumber("1")
+ sut.inputNumber("5")
+ sut.inputOperation(.divide)
+ sut.inputNumber("3")
+
+ // When: = 버튼을 누름
+ sut.performOperation()
+
+ // Then: 결과는 5
+ XCTAssertEqual(sut.displayValue, "5")
}
-
}