계산기기능을 구현만 한 뒤 앱으로 만들면 어떻게 될까? 내가 생각하지 못한 부분에서 에러가 나면 다시 처음부터 하나하나 고친 뒤 다시 해당 앱을 업로드 해야 할 것이다. 해결하기 전에 해당 앱을 다운로드 받은 유저는 문제를 해결한 버전을 다시 다운로드 받으면 되긴하지만 그 전에 개발자에 대한 신뢰를 잃지 않을까?
이런 불상사를 막기 위해 기능이 잘 작동하는지 확인이 필요하다. 이를 위해 존재하는 것이 바로
Unit Test이다.
유닛 테스트 하기 위에서는 아래와 같은 세팅이 필요하다.
테스트에 필요한 프로젝트의 Targets에 [우측]Unit Testing Bundle을 더 해주면 테스트파일이 생기고 유닛테스트 준비가 끝난다.
import XCTest
@testable import 테스트할 프로젝트 이름
XcodeTest를 import해야하는 것 뿐 아니라 테스트할 프로젝트 또한 import 해 와야 한다.
테스트할 앱은 우리가 만든 프로젝트와 다른 타겟설정이 되어있기 때문에...
class CalculatorTests: XCTestCase {
private var sut_inputDataValidator: InputDataValidator!
private var sut_calculator: GeneralCalculator!
private var sut_decimalCalculation: DecimalCalculation!
private var sut_binaryCalcualtion: BinaryCalculation!
sut 또는 "subject under test"를 테스트 케이스의 변수로 생성을 한뒤 테스트 하려는 클래스로 선언 해 주면 해당 클래스의 인스턴스 생성을 할 수가 있어진다.
저 같은 경우 각각의 클래스에서 필요한 메서드를 사용하기 위해 이렇게 총 4개의 계산기 클래스 타입의 변수를 생성하였습니다.
override func setUpWithError() throws {
sut_inputDataValidator = InputDataValidator()
sut_generalCalculator = GeneralCalculator()
sut_decimalCalculation = DecimalCalculation()
sut_binaryCalcualtion = BinaryCalculation()
try super.setUpWithError()
}
setUpWithError
메서드는 테스트 케이스가 시작 될 때 첫 번째 테스트가 실행되기 전에 한 번 호출 되는 메서드 입니다.
항목이 존재하거나 특정 상태가 필요한 경우 위 과정은 필수로 해야 합니다.
이번 프로젝트 같은 경우 각각 의 클래스에서 메서드를 가져와 테스트 해야 하기 때문에 인스턴스와 같이 이렇게 설정을 해주었습니다.
override func tearDownWithError() throws {
try super.tearDownWithError()
sut_inputDataValidator = nil
sut_generalCalculator = nil
sut_binaryCalcualtion = nil
sut_decimalCalculation = nil
}
tearDownWithError
메서드는 모든 개별 테스트가 실행 된 후 끝날 때 정확히 한 번 호출됩니다.
이번 테스트 같은 경우 테스트가 끝난 뒤 연산이 끝 난 데이터를 지워주어야 하기 때문에 테스트가 끝난 뒤 모든 값이 nil
로 되도록 설정 해주었습니다.
func test_elements_of_medianNotation() throws {
sut_inputDataValidator.validateData(input: "0")
sut_inputDataValidator.validateData(input: "1")
sut_inputDataValidator.validateData(input: "0")
sut_inputDataValidator.validateData(input: "1")
sut_inputDataValidator.validateData(input: "+")
sut_inputDataValidator.validateData(input: "1")
sut_inputDataValidator.validateData(input: "1")
sut_inputDataValidator.validateData(input: "1")
sut_inputDataValidator.validateData(input: "1")
XCTAssertEqual(sut_inputDataValidator.data.medianNotation, ["0101","+", "1111"])
}
연산자와 피연산자를 inputDataValidator.data.medianNotation
에 넣어 준 뒤 해당 데이터가 잘 들어 있는지 확인하는 테스트를 진행하였습니다.
XctAssertEqual()
메서드를 활용하여 데이터에 들어있는 값이 해당 인자값 왼쪽에 있는 값과 동일한지 테스트할 수 있습니다.
⚠️ 여기서 주의해야 할 점은 메서드 이름을 꼭 test로 시작 해 줘야 한다는 것 입니다.
이렇게 설정 해 주지 않으면 테스트를 할 수 없습니다. 주의 해 주세요!!
enum Error: Swift.Error {
case invalidAccess
case invalidOperation
}
@discardableResult
func calculatePostfixNotation(_ input: InputDataValidator) -> Result <String, Error> {
var operandStack = Stack<Double>()
for element in input.data.postfixNotation {
if !Operators.list.contains(element) {
guard let numbers = Double(element) else {
return .failure(.invalidAccess)
}
operandStack.push(numbers)
}
else {
guard let firstPoppedValue = operandStack.pop(),
let secondPoppedValue = operandStack.pop() else {
return .failure(.invalidAccess)
}
rightOperand = firstPoppedValue.value
leftOperand = secondPoppedValue.value
switch element {
case "*" :
operandStack.push(leftOperand * rightOperand)
case "/" :
operandStack.push(leftOperand / rightOperand)
case "+" :
operandStack.push(leftOperand + rightOperand)
case "-" :
operandStack.push(leftOperand - rightOperand)
default:
return .failure(.invalidOperation)
}
}
}
guard let peek = operandStack.peek() else {
return .failure(.invalidAccess)
}
return .success((dropDigits(peek.value)))
}
@discardableResult
: "결과를 쓰든 안쓰든 신경 쓰지마세요. unused Warning 띄우지 마세요"
결과를 사용하지 않긴 하지만 테스트를 위해서 해당 메서드의 반환 값을 명시 해 줘야 할 때 이걸 사용하면 됩니다.
그리고 위 메서드의 리턴 값을 Result<String, Error>
타입으로 설정 해 줬습니다.
이렇게 사용하면 유닛 테스트를 할 때 .failure()
또는 .success()
메서드를 리턴 해 줄 수 있습니다. 이렇게하면 유닛테스트를 진행할 때 에러처리도 가능하기에 테스트할 때 유용한 기능인 것 같습니다.
위와 같은 방법으로 하나 하나 메서드를 실행햐여 Unit Test를 진행할 수 있습니다.
프로젝트 내에 불필요한 코드가 없는지 또는 코드가 모두 잘 사용되었는지 확인하기 위해서 Code Coverage를 구현 하는 게 좋습니다. 자세한 방법은 아래 블로그를 참조하면 됩니다.↓↓
var test1 = "100000000"
var test2 = UInt(test1, radix: 2)
var test3 = "1111111101"
var test4 = UInt(test3, radix: 2)
let result = test2! + test4!
let test5 = String(result, radix: 2)
print(test5) // "10011111101"
2진수는 8자리입니다. 그런데 계산기에는 9자리까지 받더라구요...
그래서 고민 끝에 만들어 봤습니다..한 5시간 걸린것 같아요😂 🤣
로직은 이렇게 짜봤습니다.
test5
처럼 String으로 결과값을 반환하도록 했습니다.이 방식이 best는 아닌 것 같지만 그래도 해결했다는 것에 의미를~~~