ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TDD가 뭔지 한번 해보기
    Apple🍎/Test 2023. 12. 15. 23:15

    TDD 워크 플로우 - Red, Green, Refactor

    Red

    • 요구사항에 대해 실패하는 테스트를 작성하는 것으로 시작
    • 원하는 기능과 엣지 케이스 대해 아직 구현하지 않은 상태
    • 돌려서 IDE에서 빨간불 확인하기
    • 작성한 테스트가 처음에는 실패하는지 확인하는 것은 테스트가 정상적으로 작동하는지를 확인하는 중요한 절차이다.
    • 항상 성공하는 의미없는 테스트거나 이미 원하는 기능이 구현되어 있지는 않은지 확인하는 절차.

    Green

    • 테스트를 간신히 통과할 수 있을 정도로만 간단하게 코드를 작성.
    • 이 단계에서는 코드의 퀄리티를 신경쓰지 않고 일단 기능 구현에만 집중.
    • IDE에서 파란불 확인할 때까지 진행.
    • 테스트 통과가 가능한 가장 간단한 코드를 작성함으로써 기능 구현을 위해 진짜 필요한 부분만 작성하게 된다.
    • 읽기 쉽고, 이해가 어렵지 않으며, 수정하기에 쉬운 코드 -> 새로 온 동료가 봐도 뭘 하려고 했는지 알 정도로
    • 원하는 기능이 모두 구현되지 않았는데 테스트가 통과되었다. -> 별도의 다른 테스트가 필요하다.

    Refactor

    • Green 단계에서 모든 테스트를 통과했다면 이제 코드를 다듬는 작업이 필요하다.
    • 중복제거, 공통 로직 추출 등등 코드를 개선한다.
    • 테스트의 존재는 코드를 다듬는 동안 구현한 기능이 망가지지 않게 도와준다.

    TDD는 이런식으로 진행된다.

    • 블로깅 앱을 만든다고 가정해보자.
    • 요구사항: 새로운 포스트를 쓸 때 제목의 모든 단어의 첫글자는 대문자로 시작해야한다.
    • TDD 워크 플로우의 첫 단계는 RED로 실패 케이스가 필요하다.
    • 실패케이스를 작성하기 위해서는 다음 사항을 고려해보자.

    실패케이스 작성시 고려사항

    • Precondition : 해당 메서드를 호출하기 전에 시스템의 상태가 어떠한지를 고려하자.
    • Invocation: 메서드의 시그니쳐가 어떻게 되야할지? 메서드의 파라미터는 어떻게 될지를 고려하자.
    • Assertion : 메서드 호출결과로 기대되는 값이 무엇이 될지를 고려하자.
    • 블로깅 제목 작성 메서드에대해서 실패케이스 고려
    • Precondition: X
    • Invocation: String을 받아 String을 반환한다. 메서드 이름으로는 makeHeadLine가 적당할것이다.
    • Assertion : 반환되는 String은 모든 단어의 첫글자가 대문자이어야한다.

    makeHeadline - Red

    1. FirstDemoTests 폴더 | FirstDeomTests.swift 파일 | FirstDeomTests 클래스에 테스트를 작성해보자.
    • Give_When_Then 으로 테스트 메서드 이름 작성: 상황X _ 제목을 작성할때 _ 입력된 단어들의 첫문자가 대문자여야한다.
    // FirstDeomTests.swift
        func test_makeHeadLine_shouldCaptialisePassedInString( ) {
    
        }
    • 블로그 제목을 만들려면 일단 블로그를 작성하는 블로거가 있어야지.
    // FirstDeomTests.swift
        func test_makeHeadLine_shouldCaptialisePassedInString( ) {
            let blogger = Blogger( )
        }
    • 컴파일러가 Cannot find 'Blogger' in scope를 띄운다.
    1. Blogger.swift를 main Target에 작성한다.
    • 테스트를 작성하다보니 Blogger 객체가 필요하므로 Blogger struct를 만들자.

    // Blogger.swift
    import Foundation
    struct Blogger {
    }
    1. 블로거 객체를 만들었니 블로거가 제목을 만드는 동작을 테스트에 추가한다.
    // FirstDeomTests.swift
        func test_makeHeadLine_shouldCaptialisePassedInString( ) {
            let blogger = Blogger( )
            let headline = blogger.makeHeadline(from : "the Accessibility inspector")
        }
    • 컴파일러가 Value of type 'Blogger' has no member 'makeHeadline'을 띄운다.
    1. makeHeadline 메서드를 Blogger struct에 추가한다.
    • 테스트를 작성하다보니 Blogger struct에 makeHeadline 메서드가 필요하니 추가한다.
      • 메서드 시그니쳐만 작성

    테스트 코드 작성 과정으로부터 필요한 것들을 메인코드에 작성할 때는 딱 컴파일러가 불평 안 할 정도로만 추가한다.

    // Blogger.swift
    import Foundation
    struct Blogger {
        func makeHeadline(from input: String) -> String {
            return ""
        }
    }

    앞선 과정들에서 느낄 수 있다시피 TDD 방식의 개발은 다음과 같이 진행된다.
    요구사항을 검증하기 위한 테스트코드를 먼저 작성한다.
    테스트코드를 작성하는 과정에서 기능구현을 위해 필요한 것들이 명확해진다. 이를 바탕으로 프로덕션 코드를 작성한다.

    1. 검증하려는 동작(makeHeadline)까지 작성완료했으므로 해당 동작시 기대하는 결과와 같은지를 검증하는 코드를 추가한다.
    • Test 메서드에 XCTAssertEqual func 을 추가한다.
    // FirstDeomTests.swift
        func test_makeHeadLine_shouldCaptialisePassedInString( ) {
            let blogger = Blogger( )
            let headline = blogger.makeHeadline(from : "the Accessibility inspector")
            XCTAssertEqual(headline, "The Accessibilityinspector")
        }
    • COMMAND + U를 이용해 테스트를 돌리면 당연히 Fail이 나온다.
    • 현재 makeHealine 메서드는 String을 받아 “” 을 반환하고 있으므로 예상 값과 당연히 다르다.
    • 여기까지 해서 실패케이스를 작성하는 RED 단계를 완료했다.

    makeHeadline - Green

    • Red에서 확인한 것 처럼 현재는 “the Accessibility inspector”를 넣으면 빈문자열이 나온다.
    • 현재 테스트를 통과하기 위해서는 “the Accessibility inspector”를 넣으면 “The Accessibility Inspector”가 나와야한다.
    • TDD에 Green 단계에서는 간신히 테스트만 넘길정도로만 코드를 짠다.
    func makeHeadline(from input: String) -> String {
        return "The Accessibility Inspector"
    }
    • 위와 같이 makeHeadline 코드를 수정하면 “뭔 저런 짓을 하고 있나” 싶지만 어쨋든 테스트는 통과가 가능하다. -> 멍청해도 간단했다!!
    • 우리가 원하는 기능(각 단어 첫글자 대문자로 만들기)은 수행하지 못하고 테스트만 간신히 통과하는 쓸모없는 코드 이지만 이 코드를 작성하는 것을 통해 우리는 다른 테스트가 더 필요하다는 것을 알 수 있다.

    makeHeadline - Refactor

    • 원하는 기능을 구현하기 위한 다음 테스트를 작성하기전에 먼저 현재까지 짠 코드를 리팩터링하는 시간을 갖자.
    • 리펙터링 전
    // FirstDeomTests.swift
        func test_makeHeadLine_shouldCaptialisePassedInString( ) {
            let blogger = Blogger( )
            let headline = blogger.makeHeadline(from : "the Accessibility inspector")
            XCTAssertEqual(headline, "The Accessibilityinspector")
        }
    • 리팩터링 후
      • input, result, expected 변수를 도입, 변수의 이름을 통해 이 테스트의 목적을 명확히 할 수 있다.
    func
    test_makeHeadline_shouldCapitalisePassedInString() {
      let blogger = Blogger()
      let input = "the Accessibility inspector"
      let result = blogger.makeHeadline(from: input)
      let expected = "The Accessibility Inspector"
      XCTAssertEqual(result, expected)
    }
    • 리펙터링을 한 후에도 여전히 테스트는 통과하는지를 확인한다.

    테스트가 더 필요한지 확인하기.

    • 현재 원하는 기능이 전혀 구현되어 있지만 테스트가 통과하고 있다. -> 다른 테스트가 필요하다.
    • TDD에서 테스트 작성의 첫번째는 실패케이스 작성이다. -> 빨간불이 먼저 들어와야한다.
    • 그러면 원하는 기능에 맞는 테스트를 위해 실패케이스를 먼저 만들자.
    • makeHeadline을 return 값을 다음과 같이 수정하면
    func makeHeadline(from input: String) -> String {
        return "The Accessibility"
    }
    • 같은 테스트가 통과하지 못한다. (Inspector 부분이 없음)
    func
    test_makeHeadline_shouldCapitalisePassedInString() {
      let blogger = Blogger()
      let input = "the Accessibility inspector"
      let result = blogger.makeHeadline(from: input)
      let expected = "The Accessibility Inspector"
      XCTAssertEqual(result, expected)
    }
    • 실패 케이스를 확인했으므로 새로운 테스트를 작성한다.

    makeHeadline2

    • 이전에 작성한 makeHeadline 메서드는 하나의 문자열에 대해서만 테스트 통과가 가능하다.
    • 우리가 원하는 기능은 가능한 모든 문자열에 대해서 단어의 첫글자가 대문자가 되게 하는 것이다.

    makeHeadline2 - RED

    • 문자열을 바꾼 새로운 테스트 작성
    • makeHeadline 메서드를 바꾸지 않았으므로 테스트 돌렸을 때 당연히 통과 X
    // FirstDemoTests.swift
    func test_makeHeadline_shouldCapitalisePassedInString_2() {
        let blogger = Blogger()
        let input = "The contextual action menu"
        let result = blogger.makeHeadline(from: input)
        let expected = "The Contextual Action Menu"
         XCTAssertEqual(result, expected)
    }

    makeHeadline2 - Green

    • Blogger.swift에 makeHeadline 메서드 수정하기
    // Blogger.swift
    func makeHeadline(from input: String) -> String {
      let words = input.components(separatedBy: " ")
      var headline = ""
      for var word in words {
        let firstCharacter = word.remove(at: word.startIndex)
        headline +=  "\(String(firstCharacter).uppercased())\(word) "
      }
      headline.remove(at: headline.index(before: headline.endIndex))
      return headline
    }
    • words: [String] : “ ” (빈칸)을 기준으로 문자열을 쪼개서 String 배열로 만듬 -> 단어 단위로 자르기
    • headline : 반환할 문자열 변수
    • for var word in words : words에 있는 단어 하나씩 꺼내서 word로 가져온다으멩
    • word.remove(at: word.startIndex) : word의 첫번째 문자 POP
    • healdline += ~~~ : POP한 첫번째 문자 대문자로 만든다음에 word랑 다시 합치기
    • headline.index(before: headline.endIndex) : endIndex (마지막 인덱스 + 1) , before 하나전 -> 마지막 공백은지우기
    • makeHeadline을 수정함으로써 새로운 테스트가 통과하였다.

    makeHeadline2 - Refactor

    • 현재 FirstDemoTests 클래스에는 두개의 테스트 메서드가 있다.
    • 두 테스트 코드 모두 Blogger 객체를 중복해서 사용하고 있다.

    • Blogger를 클래스의 프로퍼티로 빼고 각 메서드는 해당 프로퍼티를 접근해서 사용하도록하자.
    • 테스트에 필요한 인스턴스 Blogger의 주입과 정리는 setUpWithError와 tearDownWithError를 통해 한다. ( 각 테스트가 시작하기전, 종료한 후 호출되는 메서드)
    • makeHeadline(from: ) 코드를 objective - C 스타일에서 swift 스타일로 바꾸기
    • input.capitalized : 문자열의 각 단어 첫번째 글자 대문자로 변경해줌
    // Blogger.swift
    func makeHeadline(from input: String) -> String {
      return input.capitalized
    }

    TDD는 각 단계에서 해야할 일이 명확하고 쉽다. 따라서 각 단계에서 “이제는 뭘 해야하지?” 하면서 하는 시간낭비를 줄일 수 있다. Red, Green, Refactor 3단계만 기억하면 된다.

    Xcode에서 테스트 관련한 정보 찾기

    Test navigator

    • 화면 왼쪽에서 다이몬드를 찾거나 command + 6으로 Test naviagtor를 열수 있다.
    • Test navigator에서는 프로젝트 또는 워크페이스의 모든 테스트를 볼 수 있다.
    • Test navigator의 아래쪽에서는 필터나 검색어를 통해 원하는 테스트만을 볼 수 잇다.

    Test overview

    • Report navigator에서는 이전에 수행했던 테스트의 목록을 볼 수 있다.

    Test 돌리는 방법

    하나만 지정해서 돌리기

    • 테스트 코드 옆에 다이아몬드를 누르면 프로덕션 코드를 컴파일하고 시뮬레이터 또는 디바이스를 실행해서테스트 코드를 실행 시킬 수 있다.
    • 테스트 네비게이터 옆에 있는 다이아몬드로 개별 테스트를 실행할 수 있다.

    테스트 그룹으로 실행하기

    • 일정한 그룹을 지정해서 해당 그룹에 대해서만 테스트를 실행하고자할 때는
    • Edit Scheme에 들어가서
    • 테스트하고자 하는 것만 체크하기

    테스트코드 디버깅하기

    • 때때로 테스트를 디버깅 해야할 때가 있을 수도 있다.
    • 일반코드와 마찬가지로 브레이킹 포인트를 찍으면 디버거는 해당 지점에서 실행을 멈춘다.
    • 빠진게 있나 , 진짜로 잘 작동하나 확인하고자 할때는 브레이킹 포인트를 찍어서 확인할 수 있다.
    • 이전 코드를 예로 들어서 디버깅을 어떻게 하는지 봐보자.
    • l을 I 로 수정해서 테스트가 실패하게 만들었다.
    • 해당 지점에 브레이킹 포인트를 찍고 다시 테스트를 실행하면

    • 콘솔창에 디버거가 나타나고
    • ( lldb) 로 시작하는 입력창이 나온다.
    • 여기에 po (print object) 명령어를 이용해 원하는 변수의 객체 상태를 볼 수 있다.
    • 파란색 브레이킹 포인트를 다시 누르면 브레이킹 포인트 해제가 가능하다.

    더 많은 디버거 명령어에 대해서 알려고 할 대 애플 문서에 lldb 키워드로 검색해보자.

     

     

    GitHub - PacktPublishing/Test-Driven-iOS-Development-with-Swift-Fourth-Edition: Test-Driven iOS Development with Swift - Fourth

    Test-Driven iOS Development with Swift - Fourth Edition, published by Packt - GitHub - PacktPublishing/Test-Driven-iOS-Development-with-Swift-Fourth-Edition: Test-Driven iOS Development with Swift ...

    github.com

     

    댓글

Designed by Tistory.