Spock에 대한 좋은 글이 있어서 번역해서 올립니다.

오역이 있거나 다른 의견이 있으면 댓글로 남겨주시면 감사하겠습니다.

원본 URL : https://thejavatar.com/testing-with-spock/


Spock Testing – Spock tutorial

Good practicies: Using descriptive methods inside test

 

 저는 항상 다른 사람들이나 미래의 제가 테스트 코드를 봤을 때, 무엇에 대한 테스트인지 바로 이해할 수 있을 정도로 설명적인 테스트를 만들기 위해 노력해 왔습니다.  그래서 크기가 커지기 쉬운 given 섹션을 가능하면 줄일려고 하고 있습니다. 가능하면, 이 given 섹션에서 모든 할당들을 제거하고, 모든 Stubbing을 멋지게 명명 된 메서드로 래핑하는 것을 목표로 합니다.

 다양한 종류의 쇼 티켓을 구매할 수 있는 시스템을 만든다고 가정 해 보겠습니다 :  공연, 연극 공연우리는 웹 페이지를 통해 사용자가 지정한 쇼 티켓 예약을 담당하는 BookingService가 사용자가 금지 된 작업 중 하나를 수행하면 예외를 throw하는지 여부를 테스트하려고 합니다. 이 경우는 이틀 전에 공연 티켓을 취소하려고 할 때 입니다. 공연 전 최소 3 일 전에 사용자가 공연 티켓을 취소 할 수 있도록 허용했습니다.  그 이외는 오직 관리자만 할 수 있습니다. 이 작업을 수행하는 테스트를 살펴 보겠습니다.



 여기서 놀랄만 한 첫 번째는 테스트와 관련 없어 보이는 많은 메소드에 대한 stub이 있다는 것인데, 예를 들면 user.getId () 또는 order.status () 입니다. 이것은 BookingService를 개발한 사람이 작은 컴포넌트로 나누는 것에 대해 고민하지 않고, 이 클래스에 많은 것을 담당하게 했다는 것 입니다.  conductValidation() 메서드 내에서 유효성 검사를 하고 있는지가 우리의 관심사이지만, 이 메서드( conductValidation() ) 전에 코드가 실행 되기 전에 conductPreValidation ()이 먼저 유효성을 만족해야 합니다. 그래서 우리는 테스트 로직과 관련없는 많은 메소드를 Stub 처리 해야 합니다. 여러분들도 이런 문제를 여러 번 경험했을 거라고 생각합니다.


테스트 결과에 영향을 미치는 모든 라인을 표시해 보겠습니다


 여러분도 알 수 있듯이 stub화 된 메소드들 중 절반 이상이 테스트 결과에 영향을 미치지 않습니다. 테스트 시나리오에서 구현 세부 사항을 숨기고, 동시에 given 섹션을 축소하여, 더 이상 독자의 관심을 끌지 않도록 할 것입니다. 이를 달성하기 위해서는  멋지게 명명 된 메소드에 stubbing 감싸야 합니다.



 저는 여러분이 이것보다 훨씬 더 분명하고 좋은 방법을  찾길 바랍니다. given 섹션은 짧게 함으로써 좀 더 이해가 쉬워졌고, 테스트 결과에 영향을 주는 매개 변수를 노출하는 동시에 코드 실행하기 위해 필요한 코드는 숨기지만, 테스트 로직에는 영향을 미치지 않았습니다. 제 관심사를 일으키는 한 가지는 TicketOrder 생성시에 Show 객체만을 가지므로써 테스트 로직에 영향을 미치지 않게 보인다는 것입니다. 다음 단계에서 이것을 제거 할 것입니다.


Removing disruptive code

 다음 일은 제 테스트에서 가능한 한 노이즈가 되는 코드를 제거하는 것입니다. 모든 할당을 given 섹션에서 제거합시다. 아무런 가치도 없습니다. 또한 테스트 코드 안에 있는 객체 생성을 모두 테스트 밖으로 이동하고 Show 객체와 동일한 메서드로 TicketOrder 객체를 생성합니다.


Improve method naming

 우리의 테스트가 덜 이상하게 보이도록 메소드의 이름을 조금 변경해 보겠습니다. 예를 들어 매개 변수를 사용하여 위의 예와 같이 공백없이 긴 이름을 사용하지 않도록 합니다.


Good practices: Using “and” label

 given, when과 같은 섹션 레이블과는 별도로 and 레이블이 있으며 이전 레이블을 관련 명령문 그룹으로 분리하는 데 사용할 수 있습니다. 현재 프로젝트에서는 잘못된 입력을 걸러내는 클래스에서 종종 사용하고 있습니다.



물론 메소드의 이름은 예제의 이름보다 의미가 있지만, 일반적인 생각을 가지기를 바랍니다. 테스트에서 모든  경우를 만족시키기 위해서 만족하는 케이스(모든 조건이 충족 됨)와 최소한 모든 if 문에서 부정적인 응답(isValidTrade ()가 false를 반환)을 주는 테스크 케이스를 작성합니다. 해피 케이스 테스트는 아래와 같습니다.



 테스트 이름에 모든 조건을 나열하고 싶지 않기 때문에 긍정적 인 유효성 확인을 위해 특정 시나리오에서 필요한 것을 제안하는 비즈니스 정의가 필요합니다. 이제 불만족 조건인 no. 2로 인해 실패해야하는 테스트를 작성해 보겠습니다.  누군가가 isValidBooking () 메소드 내에서 조건을 재정렬하더라도 내 테스트가 제대로 작동하게 할 것이며,  테스트에서 모든 조건을 만족하는 Booking 객체를 준비할 것입니다.  두 번째 조건은 제외하고




 bookingWillBePaidByCash () 메소드가 bookingWillBePaidByCreditCard () 메소드로 변경 되었다.  테스트의 이름에서도 알 수 있듯이 예약을 할인으로 분류하지 못하게 하고 있습니다. 이를 한 걸음 더 나아가기 위해  given 섹션을 두 그룹으로 분리하십시오.



 이제  given 섹션을 테스트중인 객체에 반대되는 두 섹션으로 명시적으로 분리했습니다. 또한 라벨에 설명을 추가하여 어떤 섹션이 어떤 방식으로 테스트 대상 객체에 영향을 주는지 명확하게 나타냅니다.


Good practices: Using maps for method parameters

 아래와 같은  BookingService가 있다고 상상해 봅시다. 특정 영화를 상영하기 위한 티켓이 충분하지 않은 경우, 예외가 던져 지는지 여부를 확인하는 테스트를 만들고 싶습니다.


 우리는 드디어 아래와 같은 테스트 코드를 작성하게 되었습니다.


 이것은 정말 나쁘지는 않지만, 이 기능이 훨씬 더 많은 stubbing등을 필요로 한다고 상상해보십시오. 이 경우 우리는 관련 Stubbing을 의미있는 메서드로 추출하고 싶을 것입니다.



 이 방법은 매개 변수가 서로 다르기 때문에, 이것도 그리 나쁘지 않습니다. 이 방법이 다음과 같이 나타날 수있는 경우가 있습니다.



 이 경우 어떻게 두 번째 매개 변수가 시나리오의 이름이고 세 번째 매개 변수가 감독이라는 것을 누군가가 알 수 있습니까? 숫자 매개 변수가 두 개인 경우, 어떤 number 매개 변수가 예약 할 티켓의 수라고 생각할 수 있습니까? 이러한 경우 저map을 사용하여 보다 이해할 수 있는 형태로 변형하려고 할 것입니다.


Parametrized tests

 또한 Spock은 테스트 구조를 한 번 정의한 다음 다른 입력 값에 대해 동일한 테스트를 실행하는 매개 변수화 된 테스트를 지원합니다. 이 주제에 대해 별도의 기사를 게시 할 예정이므로 당분간 티저를 제공해 드리겠습니다. 더 복잡한 매개 변수화 된 테스트를 여기서 찾을 수 있습니다.


Setting up Spock

 Spock 작업을 시작하려면 프로젝트에 하나의 종속성만 추가하면 됩니다. 웹 콘솔 온라인 응용 프로그램을 통해 Spock으로 실행 할 수도 있습니다. 기억할 것은 모든 Spock 테스트가 Specification 클래스를 상속받아야  한다는 것입니다.


External links


Spock에 대한 좋은 글이 있어서 번역해서 올립니다.

오역이 있거나 다른 의견이 있으면 댓글로 남겨주시면 감사하겠습니다.

원본 URL : https://thejavatar.com/testing-with-spock/


Spock Testing – Spock tutorial

Managing exceptions in tests

 이제는 예제를 통해 테스트 실행 중에 특정 유형의 예외가 발생 여부를 확인하는 방법을 배우겠습니다. 그 메소드는 throw () , notThrown () 그리고 noExceptionThrown () 메소드 입니다. Throw 된 예외에 대한 세부 사항을 확인하고 싶다면, 변수에 thrown () 메소드의 결과를 할당 한 다음에 예외 자체를 확인해야 합니다.

Extras and catches

Spock에서 테스트를 작성할 때, 알고 있어야할 몇가지 사항이 있습니다.

Assingments created outside test methods

모든 테스트 전에 클래스 수준의 할당이 평가됩니다. 이것은 각 테스트 본문에 objectUnderTest를 생성하고 싶지 않을 때 특히 편리합니다. 다른 테스트들의 동작 중에 해당 객체가 상태가 공유 되는것에 대해 걱정없이 클래스 수준에서 해결 할 수 있습니다. ( 즉, 테스트 케이스가 시작할 때마다 objectUnderTest가 재 할당된다는 의미입니다.)


 특별한 이유로  클래스 레벨에서 생성 된 객체의 상태를 공유하고 싶을 경우에는 해당 변수에 @Shared 주석을 추가하면 됩니다.



 Spock은 클래스에 지정된 테스트가 파일에 표시된 순서대로 실행된다는 것을 보증하지 않습니다.  따라서, 위 예제는 1번 테스트가 실행되기 전에 2번 테스트가 먼저 실행되면 테스트가 실패할 수 경우입니다. 클래스 파일에 작성된 순서대로 테스트 실행을 원할 경우 @Stepwise 주석을 추가해야 합니다.


Verifying interaction of a method while changing its behaviour

 가끔은 메소드가 호출되었는지 여부와 그 메소드 동작을 변경하고 싶을 때가 있습니다. 여기서 가장 자연스럽게 하는 방법은 given 섹션에서 메서드의 사용자 정의 동작을 정의하고 then 섹션에서는 메소드가 호출되었는지 확인하는 것입니다.


 위 예제에서 확인할 수 있는 것은 Spock은 메소드의 동작을 두 번이상 재정의하지 못하게 하고 있다는 것 입니다. 테스트가 실행되면 Spock은 먼저 구문을 검증하기 위해서 then 섹션을 검색합니다. size () 메서드 하나를 찾게 되면, size () 메서드가 이미 처리되었기 때문에 given 섹션의 list.size () >> 10 표현식이 무시됩니다. 이 문제를 해결하기 위해서는 두 구문을 하나로 만들고,  검증문을 배치 할 수 있는 유일한 곳인 then 섹션에 구문의 결과를 넣어야합니다.


Overriding previously specified behaviours

 Stub, Mock 또는 Spy에서 이미 재정의한 메소드의 동작을 다시 재정의하는 방법은 비슷합니다. 하지만 이것은 작동하지 않습니다.  다음 문제는 그동안 매번 저를 힘들게 했습니다. 클래스 레벨에서 테스트를 위해 공통의 Stub 객체를 만들고 특정 메소드의 동작을 정의했습니다. 제가 다른 테스트를 만들면서, 이미 만들어 놓은 사용자 정의 Stub을 잊어 버리게 됩니다. 다른 테스트를 만들 때, 메소드 레벨에서 테스트에서 필요로 하는 요건을 만족하는 매우 비슷한 메소드의 동작을 정의합니다.  30 분 안에 저는 Stub화 된 오브젝트가 예상대로 작동하지 않는 이유를 알지 못했습니다. 이번에는 해결 방법이 없으며, 이런 일이 발생할 수 있음을 명심하십시오.


That keyword

Spock은 테스트를 작성하는 방법을 향상시키고, 스토리를 보다 설명적이고 쉽게 읽을 수 있는 방법을 제공하고 있습니다. 이미 추가된 정적 함수를 위한 키워드 that이 있습니다. 이 키워드는 테스트 로직을 변경하지는 않지만, expect 섹션은 수정할 수는 있습니다.


Spock에 대한 좋은 글이 있어서 번역해서 올립니다.

오역이 있거나 다른 의견이 있으면 댓글로 남겨주시면 감사하겠습니다.

원본 URL : https://thejavatar.com/testing-with-spock/


Spock Testing – Spock tutorial

Checking interactions on Mock/Spy

 

 때로는 단위 테스트를 위해 만든 더미 객체의 특정 동작이 필요 없을 수도 있습니다. 오히려 프로그램을 실행하는 동안 특정 인터페이스의 메소드가 호출 되었는지가 더 관심이 있을 수 있습니다. Mock 또는 Spy를 통해 그 목적을 달성 할 수 있습니다. 이 섹션에서는 이 내용에 대해 논의 하겠지만, 여기에 제시된 모든 내용 또한 Spy에서도 사용 할 수 있습니다.

 Mock를 사용할 때 주의해야할 단점이 있습니다. Mock은 인터페이스와 관계를 확인하기 보다는 특정 구현을 테스트 하거나, 상호 작용을 확인하기 위해서 사용합니다. 그럼에도 불구하고, 컴포넌트간 상호 작용을 검사하는 테스트는 대규모 리팩토링을 수행하는 경우에 유용합니다. 우리는 데이터베이스에 데이터를 삽입하기 전에 중요한 유효성 검사를 놓치지 않고 싶습니다.

Creating Mock

Mock을 만들려면, Spock 테스트 코드에서 Mock () 메소드를 호출하면 됩니다.

Checking interactions with Mock object

 이제 Mock 객체를 만들었으니,  when 섹션 내부에서 코드를 실행하는 동안 객체에 어떤 변화가 있었는지 확인할 수 있습니다.


 우리는 then 섹션에서 상호 작용 여부를 확인할 수 있습니다. Stub에서 사용자 정의 동작을 정의한 것과 같이,  Mock과의 상호 작용을 확인하기 위해서는 해당 메서드 호출이 필요로 합니다. 그 전에 예상하고 있는 상호 작용의 제약 조건을 먼저 정의해야 합니다. 여기서는 Mock이 된 객체의 메소드 size ()가 테스트 중에 정확히 한 번만 호출된다고 가정했습니다. 이 테스트 케이스는 제약 조건이 잘못 됐기 때문에,  Spock은 실패 했음을 알려줄 것입니다. ( list.size()를 호출하는 부분이 없기 때문)


Matching invocations in mocks

Stub에서 했던 것과 동일 방법으로 Mock에서도 동작시키고 싶은 특정 메소드를 찾을 수 있습니다.

Specifying a cardinality of an interaction

 예제에서는 메소드가 한 번 호출되었는지에 대해서만 확인 했습니다. 물론 Spock은 훨씬 더 많은 테스트를 할 수 있습니다.

Check the order of execution

상호 작용이 발생하는 순서를 지정할 수도 있습니다. 그 방법은 여러 개의 then 섹션들을 만들어서 할 수 있습니다

이제 when 세션에서 구문을 변경하면 어떻게 되는지 봅시다.

예상대로 Spock은 메소드가 잘못된 순서로 호출되었음을 발견하고 테스트가 실패 합니다.

Spy in Spock

 정확하게 말하면, Stub이나 Mock과 다르게 Spy는 더미 개체가 아닙니다. 오히려 Spy는 일반 개체에 대한 래퍼라고 말하는 것이 맞을 것입니다. 인터페이스의 특정 메소드의 동작을 재정의 할 수 있으며, Mock처럼 Spy화된 메소드와 상호 작용을 확인할 수 있습니다. UserService 인터페이스를 가지고 Spy에 대해 논의 할 것입니다.

Creating Spy

 Spy를 만들기 위해서는 Spock 테스트 코드에서 Spy () 메소드를 호출해야합니다. 앞에서 말했던 것처럼 Spy는 객체를 감싸는 래퍼입니다. 이를 염두해두고, 인터페이스를 사용하여 Spy를 만든 다음, 다른 컴포넌트을 사용하여 Spy를 통해 메소드를 호출하려고 하면, 재정의한 메서드의 반환값으로 Error를 반환 될 것입니다.


인터페이스에서 Spy를 만드는 대신, 클래스를 사용하는 방식으로 Spy를 만들어 보겠습니다. UserServiceImpl이 Transaction 인수를 가지게 되면서, Spy를 생성하는 방법이 Mock과 Stub에 했던 방법과는 다른 형식이 되었습니다.

Verifying interactions in Spy

Mock과 같이 Spy화된 클래스의 메소드와 어떤 상호 작용이 발생했는지 확인할 수 있습니다.


좋습니다. Mock과 다른 점은 무엇입니까? 답은 정말 간단합니다. save () 메소드의 동작을 변경하지 않았다는 것을 알 수 있습니다. 따라서 콘솔을 열면 UserServiceImpl에 정의 된 메시지가 출력 되는 것을 확인 할 수 있습니다.


기본적으로, 특정 클래스의 메소드가 호출되었는지 확인하고 싶거나 여러가지 이유로 원래 구현했던 메소드가 실행하기 위해 Mock을 사용할 수 없을 때, Mock 대신 Spy를 사용해야합니다.

Specifying behaviour in Spy

Mock와 Stub과 같이 특정 메소드가 호출 될 때 수행해야 할 작업을 재정의할 수 있습니다. 다른 두 클래스(Mock, Stub)과는 달리, Spy는 명시적으로 오버라이드 할 메소드를 선택할 수 있고, 다른 메소드의 구현에는 아무런 영향을 미치지 않습니다.


위의 예제에서는 isServiceUp () 메서드가 호출 될 때마다 true 값을 반환하도록 했습니다. 그럼에도 불구하고 다른 메소드는 변경되지 않았으므로 ,일단 호출되면 콘솔에 메시지를 출력합니다. 기본적으로 테스트에서 사용하는 클래스에서 하나 또는 여러 메소드의 구현을 재정의 필요하고, 동시에 다른 메소드의 구현은 그대로 두고 싶다면 Stub 대신 Spy를 사용해야 합니다.


Spock에 대한 좋은 글이 있어서 번역해서 올립니다.

오역이 있거나 다른 의견이 있으면 댓글로 남겨주시면 감사하겠습니다.

원본 URL : https://thejavatar.com/testing-with-spock/


Spock Testing – Spock tutorial

Stubbing method calls

Spock에서는 다른 클래스나 인터페이스의 동작을 재정의할 수 있는 3 종류의 클래스를 제공하고 있습니다. : Stub, Mock, Spy

이 섹션에서는 다른 클래스를 모방만 하는 Mock과 Spy가 아닌, Stub에만 초점을 맞출 것입니다. 그렇지만 동작을 모방하는 문법은 세 클래스 모두 동일하므로 여기에 나오는 내용들 모두 Mock과 Spy에서도 동일하게 동작합니다.

Creating Stub

Stub을 생성하기 위해서는 Spock 테스트 코드 내에서 Stub () 메소드를 호출하면 됩니다.

Specifying the return value

기본적으로 Stub을 사용하여 수행하려는 작업은 Stub화된 클래스의 특정 메서드가 호출 될 때 발생해야 할 작업을 정의하는 것입니다. Stub화된 메소드의 리턴 값을 지정하고 싶으면, 오른쪽 시프트 연산자 >>를 사용하여 리턴 값을 지정하면 됩니다.

Specifying side effects

메소드 호출 결과로 특정 Side Effect를 원할 경우, 오른쪽 시프트 연산자 다음에 클로저( { } )를 넣습니다. 그렇게 하면 메소드가 테스트 중에 호출 될 때마다 클로저 내의 코드가 실행됩니다.

Throwing an exception as the result of method invocation

위에서 사용했던 Side Effect 기법을 사용하여 Exception도 throw 할 수 있습니다.

Specifying different behaviour based on invocation order

 호출 순서에 따라 Stub화 된 메소드의 동작을 다르게 정의 할 수 있습니다. 아래 예제에서 size () 메서드를 처음 호출 할 때는 1을 반환하고, 두 번째 호출에는 2가 반환되고 세 번째에는 3이 반환됩니다.  이때 사용하는 연산자는 오른쪽 시프트 연산자 3개가 붙은 >>>를 사용합니다.

이 문법은 사용자 정의 동작 구문과 혼합하여 사용할 수 있습니다. ( 아래 예제를 무슨 말인지 모르겠습니다.)

Conditional behaviour of stubbed method

  우리는 몇 가지 조건에 기반하여 Stub화된 메소드에 대해 다른 동작을 지정할 수 있습니다. 

 다음 예제에서는 updateRoleAndReturnPreviousOne () 메서드가 Role.ADMIN 매개 변수와 함께 호출 될 때마다 Exception이 throw되고,  다른 경우에는 Role.USER가 반환되는 것입니다. 이번에는 Stub 메소드에 매개 변수가 필요하므로 메소드 호출시 "_"을 매개 변수로 사용하여, updateRoleAndReturnPreviousOne (_)을 사용했습니다. 

 매개 변수를 취하는 메서드는 다음 섹션에서 다룰 예정이지만이 표기법은 기본적으로 다음과 같이 사용 할 수 있습니다. updateRoleAndReturnPreviousOne () 메서드가 하나의 매개 변수로 호출 될 때마다 : 뒤 따라 오는 작업 실행 (오른쪽 시프트 연산자 이후에 있는 클로저({}) ) 또는 값 (오른쪽 시프트 연산자 이후 값)을 반환합니다.

Stubbing methods that take parameters

Basic parameter matching

 위 예제에서 제기된 문제에 대해 좀 더 자세히 알아보겠습니다. 

 위의 예제처럼 특정 메소드를 모방하기 위해서는 코드에서 메소드를 호출하는 방식으로 사용해야 합니다. 

 매개 변수를 취하는 메서드를 모방하고 싶다면 어떻게 해야 할까요? 'stubbing' 호출도 매개 변수를 전달해야 할까요? 아니면 선언을 생략해야할까요? 

 아래 예제를 통해 후자(선언을 생략)를 선택하면 어떻게 되는지 알아 보겠습니다.

 예제에서 볼 수 있듯이, expect 섹션 내에서 발생한 호출이 given 섹션에 정의한 Stub 선언과 일치하지 않음을 알 수 있습니다.  위 예제는 given 섹션에 있는 Stub를 매개 변수가 0 인 get () 메서드가 호출 될 때마다 0을 반환하도록 수정하면 위의 예제은 정상 동작할 것입니다. ( list.get(0) >> 0 )

 이런 문법은 특정 매개 변수로 메소드가 호출 될 때 리턴 되어야 하는 것을 명시적으로 지정할 수 있기 때문에 매우 편리합니다.

Matching complex objects passed as parameters

 우리는 매개변수를 받을 수 있고, Stubbing 선언에서 매개변수 검증을 할 수 있습니다. User 객체를 매개 변수로 사용하는 save () 메서드를 가진 UserService 인터페이스가 있다고 가정 해 보겠습니다. User 클래스에는 name이라는 String 속성이 있습니다.



 UserService에서 save () 메서드가 호출되면 Exception이 throw되도록 하겠습니다. 좀 더 정교하게 만들기 위해 Michael이라는 이름의 사용자가 저장 될 때 Exception이 발생되도록 하겠습니다.

이 예제를 실행하면 stub화된 동작이 save 메소드에 전달 된 User가 실제로 Michael이라는 이름을 가진 경우에만 실행된다는 것을 확인 할 수 있습니다. 클로저 내부의 표현식에 대한 유일한 제한은 부울 값으로만 평가되어야 한다는 것입니다.


Arguments matching wildcards


Stub화된 메소드에 명시적인 값을 매개 변수로 지정하지 않으려면 와일드 카드를 사용하면 됩니다. Spock은 다음과 같은 와일드 카드를 제공합니다 : 



Spock에 대한 좋은 글이 있어서 번역해서 올립니다.

오역이 있거나 다른 의견이 있으면 댓글로 남겨주시면 감사하겠습니다.

그리고 추가로 이해를 돕기 위해 추가적인 설명이 들어간 부분도 있습니다.

원본 URL : https://thejavatar.com/testing-with-spock/


Spock Testing – Spock tutorial

What is Spock?

Spock은 Groovy 문법을 사용하여 이해하기 쉽게 테스트 케이스를 만들 수 있도록 해주는 단위 테스트 프레임워크(unit testing framework)니다. Groovy 기술이지만, Java 테스트도 할 수 있습니다. 무엇보다 가장 중요한 것은 Spock이 테스트 케이스 작성을 즐겁게 만들어 준다는 것입니다. 그리고 저 또한 전적으로 동의합니다.


Why Spock?

저는 TDD의 모든 장점을 알고 있고, 작성한 모든 코드에 대해 테스트를 해왔습니다. Spock을 사용하면서 어떻게 바뀌었을까요?  

  • Spock을 이용하여 테스트 코드를 작성하는 것은 다른 표준 테스트 프레임워크를 사용하는 것보다 시간이 덜 듭니다.(JUn it 과 Mock 프레임워크의 조합)
  • Mocking, Stubbing 그리고 Spying 작업들을 매우 간단한 코드로 할 수 있는 쉬운 문법 덕분에 테스트 코드 로직을 변질시키지 않을 수 있습니다.
  • Spock은 개발자들이 테스트를 BDD 같은 형식으로 구성할 수 있게 함으로써, 테스트를 더욱 명확하게 할 수 있습니다.
  • Groovy를 사용하여 클로저와 맵을 직접 사용할 수 있어서  테스트의 명확성을 더 높일 수 있습니다.

모든 것을 고려해 볼 때, Spock에서 작성된 테스트 케이스는 다른 개발자들에게 좀 더 많은 정보를 수 있고, 보다 더 잘 정리되게 할 수 있어서 이해하기가 쉽습니다.  게다가 멋져 보입니다. 무엇보다 중요한 것은 Spock이 테스트를 매우 즐겁고 보람있는 경험으로 바꾸어 놓은 것입니다.


Simple test in Spock

Spock으로 작성한 간단한 테스트 코드가 아래에 있습니다.


물론 위의 테스트 코드는 잘못되었기 때문에 테스트가 실패합니다. ( list에 "1" 추가하고, "2"를 가져올 수 있다고 생각하고 설계한 테스트 코드이기 때문입니다.) Spock은 무엇이 잘못 되었는지 명확하게 이해할 수 있도록 오류 메시지를 표시해 줍니다. ( 아래 코드를 보면 list에는 [1] 이 있고, 여기서 "0" index에 있는 "1"을 반환하고 있다고 알려주고 있습니다.)

Test structure

Spock에서 테스트 선언이 얼마나 명확한 지 주목하기 바랍니다. 개발자는 메서드에 대한 표준 명명 규칙을 따를 필요없이 두 개의 아포스트로피(" ") 사이에 테스트 이름을 선언 할 수 있습니다. 그렇기 때문에 테스트 코드에 대한 자세한 설명이 들어간 긴 이름으로 선언할 수 있고, 이를 통해 다른 사람들이 테스트 코드의 목적을 더 잘 이해할 수 있게 됩니다.

앞서 언급했듯이 Spock를 통해 BDD 테스트와 비슷한 방식으로 테스트 코드를 배열할 수 있습니다. 각 테스트는 세 부분으로 나눌 수 있습니다.


Given section

"given" Section에서는 우리가 테스트하고 싶은 기능을 컨텍스트에 지정합니다. 여기서는 테스트 중인 기능에 영향을 미치는 시스템 / 컴포넌트의 매개 변수를 지정합니다. Spock 초보자들은 이 섹션을 매우 큰게 작성되는 경향이 있지만, 궁극적으로는 간결하고 설명되기 쉽도로 만드는 방법을  배우게 될 것 입니다.


When Section


"when" Section은 테스트하고 싶은 것을 작성하는 곳입니다.  즉, 테스트 된 객체 / 컴포넌트와의 상호작용하는 것을 확인하는 곳입니다.


Then section

이제, "then" Section에서는 when 섹션에서 실행해서 나왔던 결과를 확인하는 곳입니다. 즉, 메소드의 결과 (black box test) 또는 Mocks와 Spies를 이용하여 내부에서 테스트된 기능(white box test)과 상호 작용한게 무엇인지 검증하는 곳입니다.


Alternative sections

Spock 문서에서 앞에 나왔던 섹션(given, when, then)에 대한 몇 가지 대안으로 setup, expect 등을 사용할 수 있습니다. 후자는 매우 작은 테스트를 만들 수 있는 매우 흥미로운 것입니다.



정규 표현식 - 개요


개발자라면 한번씩 관심을 가졌을 주제라고 생각합니다. 그 중에는 몇 번 공부하다가 포기했던 분들도 있을거라 생각합니다.

저 또한 정규 표현식을 공부를 하다가 포기를 몇 번을 했는지 모르겠습니다.  하지만 더 이상 미루지 말아야겠다는 결심을 하고, 다시 공부 하면서 이제야 조금 알 것 같습니다. 그런데 정리를 해두지 않으면 잊어버릴 것 같아서 이렇게 블로그로 남기게 되었습니다.

정규 표현식을 업무에서 많이 사용하는 개발자가 아니기에 부족한 부분이 많이 있습니다. 그래도 초보자 입장에서 가능하면 이해하기 쉽도록 설명하려고 노력했습니다.

그럼 본론으로 들어가기 전에 정규 표현식이 무엇인지? 그리고 언제 사용할 수 있는지 그 이유를 알아보자.

정규 표현식(Regular Expression, Regex)을 간단하게 정의 하면, 문자를 검색하고 치환하기 위한 언어라고 할 수 있습니다.

그럼 정규 표현식은 언제 사용할 수 있을까요?

정의처럼 문자 검색과 치환이 필요할 때 사용하면 됩니다. 


예를 들면 아래와 같은 문장이 있습니다.


Source Text

 "저는 프로그래머입니다. JAVA, C, C++, GO, Python을 이용합니다."


Source Text 문장에서 “JAVA” 라는 문자열을 검색하고 싶은 때 아래와 같은 정규 표현식을 통해 검색할 수 있습니다.

Regular Expression

 JAVA


그럼 본격적으로 정규 표현식을 배워봅시다.

 아래와 같이 총 8개의 Unit으로 진행될 예정입니다.


UNIT 1. 문자 검색

UNIT 2. 문자 집합

UNIT 3. 메타 문자

UNIT 4. 수량자(Quantifier)

UNIT 5. 앵커(Anchor)

UNIT 6. 하위 표현식(Sub Expression)

UNIT 7. 역참조(Back Reference)

UNIT 8. 전후방탐색(LookAround)


위 순서대로 학습 하기를 추천합니다

특히 정규 표현식을 처음 공부하는 경우라면, 꼭 목차 순서대로 학습해야 제대로 이해 할 수 있습니다.

그리고 정규 표현식은 예제 실행은 웹을 통해서 테스트 가능합니다.

아래 사이트에 접속해서 Source Text와 정규 표현식을 입력해 확인하면 됩니다.

http://regexr.com/

참고로 자바스크립트 기반에서 동작하기 때문에 안되는 것도 있다는 것을 염두 해 두길 바랍니다.



참고 사이트

http://sweeper.egloos.com/

https://opentutorials.org/module/1574

http://blog.eairship.kr/197

https://regexper.com/

http://tuwlab.com/25809

 

'IT > 정규표현식' 카테고리의 다른 글

정규 표현식 - 목차  (0) 2016.12.30

아래 내용은 facebook에서 제공하는 Tutorial을 번역한 것입니다. 오욕이 있거나 잘못된 부분이 있으면 말씀해 주시기 바랍니다


출처 : https://facebook.github.io/react/tutorial/tutorial.html



Tutorial: Intro To React



What We're Building


오늘, 우리는 대화 tic-tac-toe 게임을 만들 것입니다. 일단 여기서는 HTML JavaScript 익숙하다고 가정하겠지만, 이전에 사용하지 않았더라도 쉽게 따라 있습니다.

 

최종 결과물을 먼저 확인하고 싶다면 "최종 결과"를 클릭해서 확인할 있습니다 : 최종 결과. 게임을  해보십시오. 이동 목록에 있는 링크를 클릭하여 이전에 이동했던 시간으로 이동할 고, 이동 후에는 board 어떻게 보여지는지 확인 있습니다.


 

React ?


React는 사용자 인터페이스를 구축하기위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리입니다


React에는 여러 종류의 Component가 있지만, 일단 우리는 React.Component 서브 클래스로 시작하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}
 
// Example usage: <ShoppingList name="Mark" />
cs

 

잠시 후에 XML 유사한 태그를 접하게 것입니다.  당신의 Component를 사용하여 React 통해 렌더링을  것 입니다. - React는 데이터가 변경될 때마다 해당 Component 효율적으로 데이터를 업데이트하고 렌더링 것입니다.


위 예제에서 ShoppingList React component class 또는 React component type 입니다. Component props라는 매개 변수를 사용하고 render 메서드를 통해 표시 계층 구조를 반환해 줍니다. 


render 메서드는 렌더링 정보를 반환하고, React 해당 정보를 가져 와서 화면에 렌더링합니다. 특히, render React Element 반환하는데, 이것은 렌더링 내용에 대한 간단한 정보을 담고 있습니다.  대부분의 React 개발자는 JSX라는 특수 구문을 사용하여 이러한 구조를 쉽게 작성할 있습니다.  

예를 들면, <div /> 구문은 빌드 React.createElement ( 'div') 변환됩니다. 아래 예와 같습니다.


1
2
3
4
return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', ...),
  React.createElement('ul', ...)
);
cs


JSX 내부의 중괄호 안에 JavaScript 를 포함 할 수 있습니다. React Element는 변수에 저장되거나, 여러분의 프로그램 에서 전달 될 수 있는 JavaScript 객체입니다


ShoppingList component는 기본으로 제공되는 DOM component로 렌더링하지만, <ShoppingList />와 같이 사용하여 사용자 지정 React component들을 쉽게 구성 할 수 있습니다. 각 component들은 캡슐화되어 독립적으로 작동 할 수 있으므로 간단한 component들로 복잡한 UI를 구성할 수 있습니다.


 

시작하기


다음 예제로 시작하십시오 : Starter Code.

위 소스는 우리가 지금 만들려고 하는 게임의 큰 틀만 있습니다. Style(CSS)을 포함하고 있으니, JavaScript 만 신경 쓰면 됩니다

여기서 중요한 세 가지 Component 가 있습니다.

    • Square

    • Board

    • Game


Square component는 하나의 <div>를 렌더링하고, Board 9 개의 Square들을 렌더링하며, Game component는 나중에 자리를 표시할 보드를 렌더링합니다. 현 시점시점에서는 Component간에 상호작용하지 않습니다.

(JS 파일의 끝 부분에는 나중에 사용하게 될 도우미 함수 calculateWinner가 정의되어 있습니다.)


 

Props를 통한 데이터 전달


일단, React 접해 보기 위해서, Board component에서 Square component 데이터를 전달해 보도록 하겠습니다. Board component renderSquare 메소드 에서 <Square value = {i} /> 반환하도록 코드를 변경 다음 Square render 메소드의 {/ * TODO * /} 부분을 {this.props.value} 바꾸면 값이 표시됩니다.


:




변경 후: Square에 숫자가 표시되는 걸 확인할 수 있습니다.




상호작용하는 컴포넌트

 

그럼 Square component 클릭하면 "X" 채워지도록 만들어 봅시다. Square 클래스의 render () 함수에서  opening 태그 부분을 다음과 같이 변경하시기 바랍니다.


1
<button className="square" onClick={() => alert('click')}>
cs

여기서 새로운 JavaScript문법인(ES6에서 도입) Arrow function 문법을 사용했습니다. 이제 Square을 클릭하면 브라우저에서 경고가(alert) 표시될 것입니다.


React Component들은 생성자에서 this.state를 설정하여 state 정보를 가질 수 있습니다. 이 정보는 Component를 위한 Private 정보로만 사용되어야 합니다

이제 Square의 현재 값을 state에 저장하고, Square을 클릭하면 변경되도록 하겠습니다. 그럼 먼저 클래스에 생성자를 추가하여 state를 초기화합니다.


1
2
3
4
5
6
7
8
9
class Square extends React.Component {
  constructor() {
    super();
    this.state = {
      value: null,
    };
  }
  ...
}
cs



JavaScript 클래스에서는 서브 클래스의 생성자를 정의할 때, 명시적으로 super();을 호출 해야합니다.


이제 this.props.value 대신 this.state.value를 표시하도록 render 메서드를 변경하고, alert() 대신 () => this.setState ({value : 'X'})로 이벤트 처리를 변경합니다.


1
2
3
<button className="square" onClick={() => this.setState({value: 'X'})}>
    {this.state.value}
</button>
cs

 

this.setState가 호출 될 때마다 component에 대한 업데이트가 예정되어 있으므로 React는 전달받은 state를 업데이트하고 하위 component들과 함께 다시 렌더링됩니다. 

component가 렌더링될 때 this.state.value 'X'가 되어 그리드에 X가 표시됩니다.


Square을 클릭하면, Square에 'X'가 나타납니다


 

개발자 툴들


Chrome Firefox React Devtools 확장 프로그램을 사용하면 브라우저 devtools에서 React 구성 요소 트리를 검사 할 수 있습니다




도구를 사용하면 트리의 모든 Component props state 검사 있습니다


CodePen에서는 여러 프레임을 사용해서  해당 툴이 작동하지 않지만, CodePen 로그인하고 스팸 방지를 위해 전자 메일을 확인할 경우에는 Change View > Debug 이동하여 탭에서 코드를 다음 devtools 작동하면 됩니다.  지금 바로 사용할 필요는 없지만 이렇게 할 수 있다는 것만 알아도 좋습니다.



Lifting State Up


우리는 이제 tic-tac-toe 게임을 위한 기본적인 빌딩 블록을 가지게 되었습니다. 그러나 아직까지는  Square component state가 캡슐화되어 있습니다. 제대로 작동하는 게임을 되기 위해서는  플레이어가 게임에서 승리했는지 확인할 수 있고, X O 네모 칸에 표시 될 수 있어야 합니다. 누군가가 승리했는지 확인하기 위해 Square component들 모두 확인하는 것보다는 곳에서 9 개의 모든 Square 값을 가져야 할 것입니다.  


아마도 여러분은 지금 Board에서  Square 현재 state 어떤지 조사해야 한다고 생각할 수도 있습니다.  React에서 기술적으로 가능하지만, 이런 패턴은 코드를 이해하기 어렵게 만들고, 깨지기 쉬우며, 리팩토링하기가 어려워지기 때문에 추천하지 않습니다


대신에 , 여기서는 가장 좋은 해결책으로 state Square 아닌 Board component에서  저장하는 것입니다. - 그래서 Board component에서  Square 어떻게 표시해야하는지 알려줄 있게 됩니다.  


여러 하위 component들에서 데이터를 집계하거나 하위 두 component들 간에 서로 통신하도록 하기 위해서는, 상위 Component에서 state 가지게 있도록 해야합니다.  그렇게 되면 Parent props 통해 Child에게 상태를 다시 전달할 있으므로 Child Component 간에 그리고 Parent 항상 서로 동기화될 수 있습니다


이와 같이 state 위쪽으로 올리는 것은 React component 리팩토링 사용하는 일반적인 방법입니다.  이번에 활용  보도록 하겠습니다.  9 개의 Square 해당하는 9 개의 (null) 있는 배열을 포함하는 Board 초기  state 추가하십시오.


1
2
3
4
5
6
7
8
class Board extends React.Component {
  constructor() {
    super();
    this.state = {
      squares: Array(9).fill(null),
    };
  }
}
cs


나중에 배열을 채우게 되면 Board 아래와 같이 됩니다.


1
2
3
4
5
[
  'O'null'X',
  'X''X''O',
  'O'nullnull,
]
cs


Square 값은 아래와 같이 전달합니다.


1
2
3
renderSquare(i) {
  return <Square value={this.state.squares[i]} />;
}
cs



그리고 다시 Square에서 this.props.value를 다시 사용하도록 변경하십시오. 이제 Square을 클릭했을 때 발생하는 부분이 바뀌어야 합니다.  Board component는 squares 배열을 가지고 있습니다. 이 뜻은 SquareBoardstate 를 업데이트 할 수 있는 방법이 필요로 하다는 뜻입니다. ComponentstatePrivate으로 관리 되므로 Boardstate 을 직접 업데이트 할 수 없습니다. 그래서 일반적인 패턴은 Square을 클릭 할 때, Board에서 Square로 함수를 전달하는 것입니다. renderSquare를 다시 다음과 같이 변경합니다.


1
return <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />;
cs



이제 Board에서 Square로 두 개의 props를 전달합니다. : value onClick. 

후자는 Square가 호출 할 수 있는 함수입니다. Square에 있는 render 부분을 다음과 같이 변경합니다. 


1
<button className="square" onClick={() => this.props.onClick()}>
cs


위 코드의 의미는, Square을 클릭하면 Parent가 전달한 onClick 함수가 호출된다는 것입니다.  여기서는 onClick이 특별한 의미가 없지만, on으로 시작하고 handle을 사용하여 구현한 것들은 handler props 입니다. Square을 클릭 해보십시오. - 아직 handleClick을 정의하지 않았으므로 오류가 발생합니다. Board 클래스에 handleClick()을 구현해봅시다.:


1
2
3
4
5
handleClick(i) {
  const squares = this.state.squares.slice();
  squares[i] = 'X';
  this.setState({squares: squares});
}
cs


여기서, 우리는 .slice () 호출하여 기존 배열을 변경하는 대신 squares 배열을 복사했습니다.  불변성의 중요성의 이유를 배우기를 원하면, 다음 섹션을 넘어 가십시오


이제 square들을 클릭하여, squares 배열을 다시 채울  있기 때문에,  Square 아닌 Board Component state를 저장,  게임을 계속 만들 있습니다. Board state 변경 때마다 Square component가 자동으로 다시 렌더링됩니다


Square 이상 자체 state 유지하지 않습니다. Square 클릭했을 Parent인 Board 로부터 state 값을 받고, Parent에게 알립니다. 우리는 이런 Component들 controlled components”라 부릅니다.



왜 불변성이 중요한가.

 

이전 코드 예제에서 .slice () 연산자를 사용하여 squares 배열을 변경하기 전에 복사하고 기존 배열의 변형을 방지하는 것이 좋다고 했습니다. 이것이 의미하는 것이 무엇이고, 왜 그것이 중요한지에 대해 배워보도록 하겠습니다.


일반적으로 데이터를 변경하는 방법은 두 가지가 있습니다. 첫 번째 방법은 변수의 값을 직접 변경하여 데이터를 변경하는 것입니다. 두 번째 방법은 변경 내용이 포함 된 개체의 복사본을 만들어 데이터를 바꾸는 것입니다.


원본의 변경이 있는 데이터 변경


1
2
3
var player = {score: 1name'Jeff'};
player.score = 2;
// Now player is {score: 2, name: 'Jeff'}
cs



원본의 변경이 없는 데이터 변경


1
2
3
4
5
6
7
var player = {score: 1name'Jeff'};
 
var newPlayer = Object.assign({}, player, {score: 2});
// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}
 
// Or if you are using object spread, you can write:
// var newPlayer = {...player, score: 2};
cs



최종 결과는 동일하지만 원본의 변경이 없는 경우에, Component 전반적인 응용 프로그램의 성능을 향상시키는 도움이 되는 추가 이점이 있습니다.


변경 추적 


불변성이 아닌 객체가 변경되었는지 여부를 확인하는 것이 복잡한 이유는 해당 객체에서 직접 변경이 이루어지기 때문입니다. 그래서 현재 객체를 이전 복사본과 비교하여 전체 객체 트리를 비교하며,  변수의 값을 비교해야 해야 하기 때문입니다.  과정은 점점 복잡해질 있습니다


하지만 불변 객체는 변경 여부를 확인하는 방법이 매우 쉽습니다. 객체가 이전에 참조한 객체와 다르면 객체가 변경된 것입니다. 그게 전부입니다



React에서 언제 다시 렌더링할지 결정


React에서 불변성 가장 이점은 단순하고 순수한 Component 빌드 때입니다. 불변 데이터는 변경 여부를 쉽게 ​​판별 있기 때문에 Component 언제 다시 렌더링되어야 하는지를 결정하는 도움이 됩니다.


순수 컴포넌트를 빌드하는 방법을 배우려면 shouldComponentUpdate () 살펴보십시오. 또한 불변의 데이터를 엄격하게 적용하고 싶으면, Immutable.js 라이브러리를 살펴보십시오.



함수형 컴포넌트


다시 프로젝트로 돌아와, 이제 Square에서 생성자를 삭제할 있습니다. 이상 필요하지 않습니다. 실제로 React에서는 render 메서드로만 구성되는 Square 같은 component 유형들에 대해 stateless functional components라는 간단한 문법을 지원합니다. React.Component 확장하는 클래스를 정의하는 대신, 간단하게 props 사용하고 렌더링 되어야하는 것을 반환하는 함수를 작성하면 됩니다.


1
2
3
4
5
6
7
function Square(props) {
  return (
    <button className="square" onClick={() => props.onClick()}>
      {props.value}
    </button>
  );
}
cs



이제, this.props를  props 변경해야 합니다.  

여러분의 어플리케이션에서 많은 Component들을 functional component 만들 있을 것이고, React는 향후에는 이 함수형 컴포넌트들을  최적화 시킬 것입니다.



순서 가져오기


현재 우리 게임은 'X' 만 플레이 할 수 있는 결함을 가지고 있습니다.  수정해 보도록 합시다


첫 번째 이동은 'X'로 기본 설정합니다. Board 생성자에서 시작 state를 수정합니다.


1
2
3
4
5
6
7
8
class Board extends React.Component {
  constructor() {
    super();
    this.state = {
      ...
      xIsNext: true,
    };
  }
cs



이동할 때마다 xIsNext 부울 값을  토글하여 state에 저장합니다.  이제 xIsNext의 값을 토글할 수 있도록 handleClick 함수를  수정합니다. 


1
2
3
4
5
6
7
8
handleClick(i) {
  const squares = this.state.squares.slice();
  squares[i] = this.state.xIsNext ? 'X' : 'O';
  this.setState({
    squares: squares,
    xIsNext: !this.state.xIsNext,
  });
}
cs



이제야 'X' 'O'가 순서대로 사용됩니다. 다음은, 누가 다음인 출력하기 위해 Boardrender 함수에 있는  "status" 텍스트를 변경하겠습니다. 


 

승리자 선언

 

지금부터 게임에서 언제 승리하는지 보여줍시다. 9 개의 값 목록을 전달해 주는 calculateWinner (squares) 도우미 함수가 맨 아래에 제공됩니다. Boardrender function 에서 이 기능을 호출하여 게임에서 이긴 사람이 있는지 확인하고, 누군가가 이기면 상태 텍스트에 "Winner : [X / O]"가 표시되게 할 수 있습니다.:


1
2
3
4
5
6
7
8
9
10
render() {
  const winner = calculateWinner(this.state.squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
  }
  ...
}
cs

 

누군가가 이미 게임에 이겼거나, Square가 모두 채워져 있으면 끝내거나 무시하록 handleClick을 변경하겠습니다.


1
2
3
4
5
6
7
handleClick(i) {
  const squares = this.state.squares.slice();
  if (calculateWinner(squares) || squares[i]) {
    return;
  }
  ...
}
cs



축하합니다

이제 동작하는 tic-tac-toe 게임이 완성되었습니다

이를 통해 React의 기본을 알게 되었습니다. 아마 당신은 여기에서 진정한 승자 일 겁니다.



Storing a History


그럼 이제, Board에 예전 state 다시 방문하여, 이전 움직임 이후에 어떤 모습인지 확인할 있게 해보도록 하겠습니다. 이동 할 때마다  squares 배열을 만들고 있으므로 이전 보드 상태를 쉽게 저장할 있습니다. 


state에서 다음과 같이 state에 객체를 저장하려고 합니다.


1
2
3
4
5
6
7
8
9
history = [
  {
    squares: [null x 9]
  },
  {
    squares: [... x 9]
  },
  ...
]
cs


우리는 최상위에 있는 Game Component에  이동 목록을 표시하기를 원합니다. 그래서 Square에서 Board state를 올렸던 것처럼, Board에서 Game으로 다시 끌어 올려 봅시다 - 그래서 우리는 필요로 하는 모든 정보를 최상위 레벨에서 갖습니다


먼저 게임의 초기  state 설정합니다.


1
2
3
4
5
6
7
8
9
10
11
12
class Game extends React.Component {
  constructor() {
    super();
    this.state = {
      history: [{
        squares: Array(9).fill(null)
      }],
      xIsNext: true
    };
  }
  ...
}
cs



그런 다음 Board를 변경하여 생성자를 제거하고, props을 통해 squares 배열을 가져오고, 이전 Square 변경과 같이 Game 위한 특별한 onClick props을 갖도록 합니다. Square의 위치를 ​​클릭 핸들러를 통해 전달하여 클릭 한 Square을 알 수 있습니다.


1
return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
cs

 

Gamerender 는 가장 최근의 기록 항목을 볼수 있고, game status을 계산할 수 있습니다.


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const history = this.state.history;
const current = history[history.length - 1];
const winner = calculateWinner(current.squares);
 
let status;
if (winner) {
  status = 'Winner: ' + winner;
else {
  status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
...
<div className="game-board">
  <Board
    squares={current.squares}
    onClick={(i) => this.handleClick(i)}
  />
</div>
<div className="game-info">
  <div>{status}</div>
  <ol>{/* TODO */}</ol>
</div>
cs



handleClick은 새 히스토리 항목을 연결하여 새로운 히스토리 배열을 작성하여 스택에 새 항목을 푸시 할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
handleClick(i) {
  const history = this.state.history;
  const current = history[history.length - 1];
  const squares = current.squares.slice();
  if (calculateWinner(squares) || squares[i]) {
    return;
  }
  squares[i] = this.state.xIsNext ? 'X' : 'O';
  this.setState({
    historyhistory.concat([{
      squares: squares
    }]),
    xIsNext: !this.state.xIsNext,
  });
}
cs



이제 Board renderSquare render만 필요 합니다. 상태 초기화 및 클릭 핸들러는 모두 Game에 있기 때문입니다.


 

이동 표시


지금까지 Game에서 만들어진 이전 이동 보여 드리겠습니다. React 요소는 first-class  JS 객체이므로 저장하거나 전달할 있습니다. React에서 여러 항목을 렌더링하기 위해 React Element들 배열을 전달합니다. 배열을 빌드하는 가장 일반적인 방법은 데이터 배열을 매핑하는 것입니다. Game render 메서드에서 그렇게 해보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
const moves = history.map((step, move) => {
  const desc = move ?
    'Move #' + move :
    'Game start';
  return (
    <li>
      <a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
    </li>
  );
});
...
<ol>{moves}</ol>
cs



히스토리의 각 단계마다 목록 항목 <li>을 만들고 그 안에 링크가 없는 <a> (href = "#") 곧 구현할 클릭 핸들러가 있습니다. 이 코드를 사용하면 게임에서 만들어진 동작 목록과 함께 다음과 같은 경고 메시지가 표시됩니다.

 

경고 : 배열 또는 반복기의 각 Child에는 고유 한 "key" props이 있어야합니다. "Game"의 렌더링 메소드를 확인하십시오.


그럼 이 경고의 의미에 대해 이야기 봅시다.

 

Keys


아이템 목록을 렌더링하면, React 항상  아이템 대한 정보를 목록에 저장합니다. state가 있는 Component 렌더링하는 경우에는 해당 state 저장해야 하며 Component 구현하는 방법에 관계없이 React 참조를 기본 네이티브 뷰에 저장합니다


목록을 업데이트하면 React 변경된 사항을 확인해야 합니다. 목록의 항목을 추가, 제거, 재배치 또는 업데이트 있습니다.


이제 변환되는 것을 상상해보십시오.


변환


1
2
<li>Alexa: tasks left</li>
<li>Ben: tasks left</li>
cs


변환


1
2
3
<li>Ben: tasks left</li>
<li>Claudia: tasks left</li>
<li>Alexa: tasks left</li>
cs


사람의 눈에는 위 변화는 AlexaBen의 위치를 바꿔주고, Claudia가 추가된 것처럼 보일 것이다. 그러나 React는 컴퓨터 프로그램 일 뿐이고 의도 한 바를 모릅니다. 결과적으로 React는 목록의 각 요소에 대해 key을 지정하도록 요구합니다.이 특성은 ComponentSibling과 구분하기 위해서 입니다. 이 경우, Alexa, Ben, Claudia는 합리적인 키가 될 수 있습니다. 항목이 데이터베이스의 개체에 해당하는 경우 데이터베이스 ID는 일반적으로 좋은 선택입니다.


1
<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>
cs


key React 의해 예약 특별한 속성입니다 (ref 함께, 고급 기능). 요소가 만들어지면 React 속성을 가져와 키를 반환 요소에 직접 저장합니다. 그것이 props 일부인 것처럼 보일지라도 this.props.key 참조 수는 없습니다. React key 자동으로 사용하여 업데이트 Child 결정합니다. Component 자체 key 대해 질의 있는 방법은 없습니다


목록이 다시 표시되면, React 버전의 요소를 가져 와서 이전 목록에서 일치하는 key 있는지 요소를 찾습니다. 세트에 key 추가되면 Component 작성됩니다. key 제거되면 구성 요소가 파괴됩니다. Keys React에게 컴포넌트의 신원을 알려서 rerenders 전체에서 상태를 유지할 있도록 합니다. Component key 변경하면 완전히 파손되어 새로운 상태로 다시 작성됩니다


동적 목록을 작성할 때마다 적절한 키를 할당하는 것이 강력하게 추천합니다. 적절한 키가 없으면 데이터 재구성을 고려해야 수도 있습니다


어떤 key 지정하지 않으면 React 경고를 표시하고 배열 인덱스를 key 사용합니다. 목록에서 요소의 순서를 바꾸거나 항목의 아래에 있는 항목을 추가 / 제거하는 경우 올바른 선택이 아닙니다. 명확하게 key = {i} 전달하면 경고가 사라지지만 동일한 문제가 발생하므로 권장하지 않습니다


Component key들은 전역적으로 고유 필요는 없으며, 직접적인 sibling과의 관계에서는 고유 해야합니다.

 

시간 여행 구현 


이동 목록의 경우 단계마다 고유 ID 이미 있습니다. 이동이 발생한 시기의 번호입니다. 키를 <li key = {move}> 추가하면 key 경고가 사라집니다.


jumpTo 정의되지 않았기 때문에 이동 링크 하나를 클릭하면 오류가 발생합니다. 게임의 상태에 새로운 키를 추가하여 현재 보고있는 단계를 알려줍니다. 먼저 stepNumber : 0 초기 state 추가 다음 jumpTo 사용하여 해당 상태를 업데이트하도록 합시다.


우리는 또한 xIsNext 업데이트하려고 합니다. 이동 번호의 인덱스가 짝수이면 xIsNext true 설정합니다.


1
2
3
4
5
6
jumpTo(step) {
  this.setState({
    stepNumber: step,
    xIsNext: (step % 2) ? false : true,
  });
}
cs

그런 다음 stepNumber : history.length handleClick의 상태 업데이트에 추가하여 새 이동이 수행되면 stepNumber를 업데이트 하겠습니다. 이제 render를 수정하여 히스토리에서 해당 단계를 읽을 수 있게 하겠습니다.


1
const current = history[this.state.stepNumber];
cs


이제 이동 링크를 클릭하면 Board가 즉시 업데이트 되어 그 시간에 게임이 어떻게 보였는지 보여줍니다. 현재의 board state을 읽을 때, stepNumber를 인식하도록 handleClick을 업데이트하여 보드에 시간을 두고 다시 돌아와 새로운 항목을 만들 수 있습니다. (힌트 : handleClick의 맨 위에서 역사의 추가 요소를 .slice ()하는 것이 가장 쉽습니다.)

 


마무리


이제 tic-tac-toc 게임을 만들었습니다.

    • tic-tac-toe 게임을 해보세요.

    • 한 플레이어가 게임에서 이기면 화면에 표시됩니다.

    • 게임 중 동작의 이력을 저장하고,

    • 플레이어는 이전 버전의 게임 보드를 보기 위해 시간을 거슬러 올라갈 수 있습니다.


잘 했어! 우리는 당신이 지금 React가 어떻게 작동하는지에 관해 알맞은 생각을 가지고 있다고 느끼기를 바랍니다.

추가 시간이 있거나 새로운 기술을 연습하고 싶다면 다음과 같이 개선 할 수 있는 아이디어가 있습니다. 어려움이 갈수록 커집니다.


1.         "6"대신 "(1, 3)"형식으로 이동 위치를 표시하십시오.

2.        이동 목록에서 현재 선택된 항목을 굵게 표시하십시오.

3.        보드를 다시 작성하여 두 개의 루프를 사용하여 Square을 하드 코딩하는 대신 사용하십시오.

4.        오름차순 또는 내림차순으로 동작을 정렬 할 수 있는 전환 버튼을 추가하십시오.

5.        누군가가 이기면 승리를 가져온 세 개의 Square을 강조 표시합니다.

 

VO vs DTO

 

VO(Value Object)

-       데이터 그 자체로 의미 있는 것을 담고 있는 객체이다.

-       DTO와 동일한 개념이나 차이점은 Read–Only 속성 객체이다.

-       간단한 독립체( Entity ) 의미하는 작은 객체를 의미한다

-       관계데이터베이스의 레코드에 대응되는 자바클래스이다.

n   형태는 Database레코드를 구성하는 필드들을 VO Attribute로 하고 해당 변수에 접근 할 수 있는 Getter Setter 메소드의 조합으로 클래스를 형성되어진 클래스이다.

n   거의 불변성을 가지고 equals()로 비교할 때 객체의 모든 값을 비교해야 한다.

 

색상으로 예를 들어보겠다. 색상 중에 빨강과 초록을 RGBA로 나타내면 RGBA(255,0,0,0) RGBA(0,255,0,0) 표현된다. 나아가서 코드명이 붙은 색상도 존재할 거이다. 코드로 보면 아래와 같다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

class Color {

 

private int R,G,B,A;

 

public Color(int r,int g, int b, int a){…}

 

//getters and setters

 

public final static Color RED = new Color(255,0,0);

 

public final static Color GREEN = new Color(0,255,0,0);

 

}

Colored by Color Scripter

cs

 

위와 같은 색상 객체가 있고 빨강과 초록이 있다. 코드에서 빨강 또는 초록을 사용할 때, Color.RED, Color.GREEN 를 사용하면 된다. , 값 객체 Color.RED, Color.GREEN 자체로서 의미가 있게 된다.

 

DTO(Data Transfer Object)

-       전송되는 데이터의 컨테이너이다.

-       VO와 동일하게 데이터를 저장하여 사용하도록 하는 부분에서 필요하다.

-       VO와 비교를 하여 보면 DTO는 같은 시스템에서 사용되는 것이 아닌 다른 시스템으로 전달하는 작업을 처리하는 객체이다.

-       Layer간의 통신 용도로 오가는 객체를 말하기도 한다.

-       현재의 개발 환경에서 보통 데이터는 다음과 같이 흐름으로 이동한다.

n   서버 측 : Database Column Data -> DTO -> API(JSON or XML) -> Client

n   클라이언트 측 : Server -> API(JSON or XML) -> DTO -> View or Local Database System

 

VO DTO 정리 :

-       VO : 사용 되는 값이 객체로 표현 되며, 값 변경이 없는 경우를 말한다.

-       DTO : 데이터의 전송을 위한 객체이며, 비지니스 로직까지 담아서 사용하기 한다.

 

그런데 VO/DTO에 대한 논쟁도 많고 반드시 사용 용도를 구분하는 건 무의미해 보인다. 개발팀 내에서 용도를 정해서 사용하면 좋을 것 같다.

예를 들어 외부 시스템과 데이터 통신을 할 경우에는 DTO, DB에서 가져오는 DataVO로 정의해서 사용한다고 약속을 하면, 향후 DTO/VO를 수정할 경우 좀 더 고민할 여지가 생길것 같다.

 

참고 사이트

http://mrgamza.tistory.com/49

https://martinfowler.com/bliki/ValueObject.html

http://parkgaram.com/blog/vo-%EC%99%80-dto-%EC%B0%A8%EC%9D%B4/

'IT > JAVA' 카테고리의 다른 글

GC 튜닝 절차  (0) 2018.11.09
Java Method Signature  (0) 2017.12.08
String VS SringBuffer VS StringBuilder  (0) 2016.10.14
Annotaion  (0) 2016.05.09

 

 Handlebars  코딩 해보기

 

1)    기본적인 구조를 통해 이해

 

Handlebar 템플릿은 아래처럼 script태그 안에 type “text/x-handlebars-template” 등으로 설정하고 위치시킨다. 그리고 템플릿 구조에 {{ }} 감싸진 부분이 데이터가 binding되는 부분이다.

{{#users}} {{/users}} 안에 들어있는 html 태그들은 items라는 배열의 길이만큼 반복하게 된다. 그리고 내부의 {{name}},{{id}},{{email}} 배열 요소 객체의 name,id,email 속성값이 바인딩 된다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<script id="entry-template" type="text/x-handlebars-template">

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th> 

    </thead> 

    <tbody> 

        {{#users}} 

        <tr> 

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            <td><a href="mailto:{{email}}">{{email}}</a></td> 

        </tr> 

        {{/users}} 

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

실제 템플릿과의 바인딩은 자바스크립트를 통해서 이루어진다. 아래는 자바스크립트를 통해 Binding하는 코드이다.

아래 코드를 보면 먼저 앞에서 작성한 Handlebar Template의 내용을 가져온다. Handlebars.compile 메소드를 통해 Template을 컴파일한 뒤, 리턴 된 함수의 Parameter에 바인딩할 데이터를 Parameter로 넣으면 데이터가 BindingHTML Template이 리턴된다. HTML TemplateDOM에 추가하면 끝난다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//핸들바 템플릿 가져오기

var source = $("#entry-template").html(); 

 

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동1", id: "aaa1", email: "aaa1@gmail.com" },

            { name"홍길동2", id: "aaa2", email: "aaa2@gmail.com" },

            { name"홍길동3", id: "aaa3", email: "aaa3@gmail.com" },

            { name"홍길동4", id: "aaa4", email: "aaa4@gmail.com" },

            { name"홍길동5", id: "aaa5", email: "aaa5@gmail.com" }

        ]

}; 

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

Colored by Color Scripter

cs

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//Handlebar 템플릿을 가져오기

var source = $("#entry-template").html();

//Handlebar 템플릿을 precompile

var template = Handlebars.compile(source);

//Handlebar 템플릿에 바인딩할 데이터

var data = {

items:[

{name"shoes", price:"5000", intro:"www.a.com"},

{name"T-shirt", price:"1000", intro:"www.b.com"},

{name"Jeans", price:"2000", intro:"www.c.com"}

]

};

//Handlebar 템플릿에 데이터를 바인딩해서 HTML 생성

var itemList = template(data);

//생성된 HTML DOM 주입

$('body').append(itemList);

cs

 

아래와 같은 결과가 출력된다.

 

1

2

3

4

5

6

이름    아이디    메일주소

홍길동1    aaa1    aaa1@gmail.com

홍길동2    aaa2    aaa2@gmail.com

홍길동3    aaa3    aaa3@gmail.com

홍길동4    aaa4    aaa4@gmail.com

홍길동5    aaa5    aaa5@gmail.com

cs

 

 

참고로 링크 부분의 malito는 저 링크를 클릭했을 때 바로 전자메일을 보낼 수 있게 해주는 태그이다.

 

코드를 통해 알 수 있듯이 실제 템플릿이 데이터를 컴파일하고, 바인딩해주는 부분이 모두 자바스크립트 코드에서 이루어진다. Handlebars.compile 메서드를 이용해 템플릿을 컴파일하고, 리턴 된 함수의 ParameterBinding된 데이터를 Parameter로 넣으면 Binding HTML템플릿이 리턴 된다. 그리고 이 HTML템플릿을 DOM에 추가함으로써 구현이 완료된다.

 

 

2)    사용자 정의 Helper 사용해보기

 

Handlebar Mustache를 기초로 개발되었지만, Mustache와의 차별 점은 바로 사용자 정의 Helper이다. Mustache는 단순하게 주입된 데이터를 바인딩해주는 템플릿 엔진이었다면 Handlebar if, unless 등의 기본 Helper뿐만 아니라 사용자 정의 Helper를 추가해서 좀 더 템플릿 엔진에서 간단한 로직을 포함시킬 수 있다는 것이다. 아래는 기본적인 사용자 정의 Helper예시이다.

 

1

2

{{!-- 사용자 정의 Helper email id 인자로 넘긴다 --}}

<td><a href="mailto:{{email id}}">{{email id}}</a></td>

cs

 

앞의 예시에서 HTML코드에서  email라고만 되었던 부분에 {{email id}} 형태로 바뀌고, idParameter를 전달해주고 있다. , email이라는 이름의 사용자 정의 Helper가 사용되고 있는걸 알 수 있다. 이를 해석하면 email이라는 이름의 사용자 정의 Helper를 호출하면서 Parameterid 값을 전달한다는 의미이다.

전체 코드를 보면 아래와 같다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<script id="entry-template" type="text/x-handlebars-template">

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th> 

    </thead> 

    <tbody> 

        {{#users}} 

        <tr> 

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            

            {{!-- 사용자 정의 헬퍼인 email id 인자로 넘긴다 --}}

            <td><a href="mailto:{{email id}}">{{email id}}</a></td> 

        </tr> 

        {{/users}} 

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

그리고 {{!--   --}}를 통행 주석을 표시해 주고도 있다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동1", id: "aaa1" },

            { name"홍길동2", id: "aaa2" },

            { name"홍길동3", id: "aaa3" },

            { name"홍길동4", id: "aaa4" },

            { name"홍길동5", id: "aaa5" }

        ]

}; 

 

//커스텀 헬퍼 등록 (id 인자로 받아서 전체 이메일 주소를 반환)

Handlebars.registerHelper('email'function (id) {

  return id + "@daum.net";

});

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

cs

 

스크립트 부분을 보면 Binding할 데이터에 email이 없는걸 확인할 수 있다. 그 대신 Custom Helper가 등록하는 코드가 구현되고 있는걸 확인할 수 있다. Handlebars.registerHelper라는 메소드를 사용하여 사용자 정의 Helper를 등록하고 있다. 첫번째 ParameterHelper의 이름을 넣고, 두 번째 Parameter에서 Helper의 동작을 정의한다. 위 코드에서는 id을 인자로 받아서 ‘@daum.net’이라는 문자열을 더해주는 로직이다 

결과는 아래와 같다.

 

1

2

3

4

5

6

7

 

이름    아이디    메일주소

홍길동1    aaa1    aaa1@daum.net

홍길동2    aaa2    aaa2@daum.net

홍길동3    aaa3    aaa3@daum.net

홍길동4    aaa4    aaa4@daum.net

홍길동5    aaa5    aaa5@daum.net

cs

 

 

3)    Handlebar에서 제공하는 기본 Helper

 

Handlebar if, unless 등의 기본 Helper와 사용자 정의 Helper를 이용해서 간단한 로직을 구현할 수 있다하지만 if의 경우 오직 true/false만 판별할 수 있기 때문에 복잡한 조건은 Handlebar를 통해서 처리할 수 없다. 사용자 정의 helper를 통해 가능하지만, Temple에 로직을 배제하는 사상과 맞지 않기 때문에 가능하면 사용하지 않는게 좋다.

그럼 Handlebars에서 제공하는 기본 Helper를 알아보자

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

<script id="entry-template" type="text/x-handlebars-template">

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th>

        <th>요소정보</th>

    </thead> 

    <tbody> 

        {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

        {{#each users}}

        <tr>

            {{!-- {{name}}  {{this.name}}  같고 {{.}}  현재 name id 포함하고 있는 오브젝트를 가리킨다 --}}

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            

            {{!-- 사용자 정의 헬퍼인 email id 인자로 넘긴다 --}}

            <td><a href="mailto:{{email id}}">{{email id}}</a></td> 

            

            {{!-- 요소의 순번은 @index 혹은 @key 잡을  있는데,

            array object 모두 잡을  있는 key  나아보인다 --}}

            

            {{#if @first}}

                <td> 아이템 ({{@key}} 번째 요소)</td>

            {{else if @last}}

                <td>마지막 아이템 ({{@key}} 번째 요소)</td>

            {{else}}

                <td>중간 아이템 ({{@key}} 번째 요소)</td>

            {{/if}}

        </tr> 

        {{/each}}

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

 

배열 등을 반복해주는 each Helper의 경우에는 #배열명으로 대체가 가능하다. @기호와 함께 몇 번째 인덱스인지도 구분이 가능하다. @first로 첫번째 인덱스를 알 수 있고, @last로 마지막 인덱스를 확인할 수 있다. 또한 해당 인덱스가 몇 번째 인덱스인지, 오브젝트라면 어떤 오브젝트인지 @key를 통해 알 수 있다. 앞에서 설명했듯이 if true/false 만 판별이 가능하며, elseelse if 를 지원하며, unless if의 반대되는 Helper이다.

 

 

4)    partial template 등록 및 사용

 

다른 템플릿엔진처럼 HandlebarHandlebar 템플릿을 다른 Handlebar 템플릿의 일부로 포함시킬 수 잇다. 이를 통해 템플릿의 재 사용성과 가독성을 높일 수 있고, 좀 더 객체지향적인 프로그래밍이 가능하다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

<!-- 핸들바 템플릿 -->

<script id="partial-template" type="text/x-handlebars-template">

    {{!-- #unless 헬퍼는 #if  정반대 기능을 한다--}}

    <h1>리스트 {{#unless users}}<small>사용자 리스트가 없습니다.</small>{{/unless}}</h1>

</script>

 

<!-- 핸들바 템플릿 -->

<script id="entry-template" type="text/x-handlebars-template">

 

{{!-- 조각 템플릿은 #>  사용해서 포함시킬  있다--}}

{{#> commonHeader}}

    partial template 로드 실패시 보여지는 내용

{{/commonHeader}}

 

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th> 

        <th>요소정보</th>

    </thead> 

    <tbody> 

        {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

        {{#each users}} 

        <tr> 

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            

            {{!-- 사용자 정의 헬퍼인 email id 인자로 넘긴다 --}}

            <td><a href="mailto:{{email id}}">{{email id}}</a></td> 

            

            {{!-- 요소의 순번은 @index 혹은 @key 잡을  있는데,

            array object 모두 잡을  있는 key  나아보인다 --}}

            

            {{#if @first}}

                <td> 아이템 ({{@key}} 번째 요소)</td>

            {{else if @last}}

                <td>마지막 아이템 ({{@key}} 번째 요소)</td>

            {{else}}

                <td>중간 아이템 ({{@key}} 번째 요소)</td>

            {{/if}}

        </tr> 

        {{/each}}

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

위의 예를 보면, 앞서 나온 예제와는 다른 부분이 있다. Template을 담고 있는 Script 태그가 한 개 더 있다.

 

1

2

3

4

<script id="partial-template" type="text/x-handlebars-template">

{{!-- #unless 헬퍼는 #if  정반대 기능을 한다--}}

<h1>리스트 {{#unless users}}<small>사용자 리스트가 없습니다.</small>{{/unless}}</h1>

</script>

Colored by Color Scripter

cs

 

Entry-template 안을 잘 보면, #> 기호와 함께 partial template을 삽입해 주는 부분을 발견 할 수 있다. 이는 #>와 함께 partial template의 이름을 넣어주면 해당 위치에 partial template이 삽입된다. partial template의 이름은 자바스크립트에서 등록한다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동1", id: "aaa1" },

            { name"홍길동2", id: "aaa2" },

            { name"홍길동3", id: "aaa3" },

            { name"홍길동4", id: "aaa4" },

            { name"홍길동5", id: "aaa5" }

        ]

}; 

 

//조각 템플릿을 'commonHeader' 라는 이름으로 등록

Handlebars.registerPartial('commonHeader', partial);

 

//커스텀 헬퍼 등록 (id 인자로 받아서 전체 이메일 주소를 반환)

Handlebars.registerHelper('email'function (id) {

  return id + "@daum.net";

});

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

cs

 

위 코드를 보면, entry-template 과 마찬가지로 partial-template을 가져온 뒤, Handlebars.registerPartial 메소드를 사용해서 가져온 TemplatecommonHeader라는 이름으로 등록해주고 있는 것을 확인할 수 있다. 이렇게 등록만 해주면 데이터를 Binding해주는 시점에서 partial template을 삽입해주게 되고, partial template에도 주어진 데이터와 Binding될 부분이 있다면 데이터 Binding역시 처리되게 된다.

 

 

5)    withas 그리고 상대 경로 참조


지금까지 살펴본 내용들은 기본적인 Handlebars의 문법이었다면, 이런 문법을 조합한 좀더 복잡한 구조를 살펴보자. 자주 있는 상황은 아니지만, 이중 혹은 삼중의 Loop을 돌리는 상황에서 상위 Loop의 값을 하위 Loop에서 참조해야 하는 상황에서 활용할 수 있다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

<!-- 핸들바 템플릿 -->

<script id="entry-template" type="text/x-handlebars-template">

 

    <table>

        <thead> 

            <th>이름</th> 

            <th>아이디</th> 

            <th>취미</th>

        </thead> 

        <tbody> 

            {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

            {{#each users as |user userId|}} 

                <tr> 

                    <td>{{name}}</td> 

                    <td>{{id}}</td> 

                    <td>

                      {{#each hobbys as |hobby hobbyId|}}

                        {{!-- 처음이 아닌 경우에는 쉼표(,) 넣기 --}}

                        {{#unless @first}}, {{/unless}}

 

                                                {{!-- 상위 이터레이션의 인덱스 넘버를 가져올  --}}

                                                {{@../index}} ==

                        {{!-- 상위 이터레이션의 인덱스 넘버는 아래와 같은 방식도 가능하다 --}}

                        {{userId}}

 

                        {{!-- 상대경로로 참조한 name  myName 이라는 변수로 할당 --}}

                        {{#with ../this.name as |myName|}}

                          {{!-- 상대경로를 참조해서 #hobby 현재값 출력 --}}

                          {{myName}}  {{hobbyId}} 취미 {{hobby}}

                        {{/with}}

                      {{/each}}

                    </td>

                </tr> 

            {{/each}}

        </tbody> 

    </table>

 

</script>

Colored by Color Scripter

cs

 

../ 를 사용해서 상위의 경로를 참조할 수 있고, with as 를 활용해서 이렇게 참조한 상위의 값을 다른 이름으로 할당해서 사용하고 있다.

상위 Loop에서 참조하고 있는 user item에 있는 name myName으로 할당하여 활용하고 있다. 그리고 상대 경로를 참조하여 hobby의 값도 출력해주고 있다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//핸들바 템플릿 가져오기

var source = $("#entry-template").html();

 

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동", id: "aaa", hobbys: ['축구''야구''농구'] },

            { name"이순신", id: "bbb", hobbys: ['족구''피구''탁구'] },

            { name"이성계", id: "ccc", hobbys: ['수구''배구''농구'] },

            { name"장영실", id: "ddd", hobbys: ['축구''피구''농구'] },

            { name"장보고", id: "eee", hobbys: ['배구''야구''족구'] }

        ]

}; 

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

Colored by Color Scripter

cs

 

결과는 아래와 같다.

1

2

3

4

5

6

이름    아이디    취미

홍길동    aaa    0 == 0 홍길동  0 취미 축구 , 0 == 0 홍길동  1 취미 야구 , 0 == 0 홍길동  2 취미 농구

이순신    bbb    1 == 1 이순신  0 취미 족구 , 1 == 1 이순신  1 취미 피구 , 1 == 1 이순신  2 취미 탁구

이성계    ccc    2 == 2 이성계  0 취미 수구 , 2 == 2 이성계  1 취미 배구 , 2 == 2 이성계  2 취미 농구

장영실    ddd    3 == 3 장영실  0 취미 축구 , 3 == 3 장영실  1 취미 피구 , 3 == 3 장영실  2 취미 농구

장보고    eee    4 == 4 장보고  0 취미 배구 , 4 == 4 장보고  1 취미 야구 , 4 == 4 장보고  2 취미 족구

cs

 

 

6)    lookup을 활용한 변수 참조


 lookup Helper를 활용하면 특정 상대 경로에 있는 변수 값을 참조할 수 있다. lookup Helper로 참조할 수 있는 변수 값은 with as 를 활용하는 방법으로 거의 대부분 참조 가능하다. 개인적으로는 with as 를 활용하는 것이 더 명시적이기 때문에 선호한다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

<!-- 핸들바 템플릿 -->

<script id="entry-template" type="text/x-handlebars-template">

 

    <table>

        <thead> 

            <th>이름</th> 

            <th>아이디</th> 

            <th>취미</th>

        </thead> 

        <tbody> 

            {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

            {{#each users as |user userId|}} 

                <tr> 

                    <td>{{name}}</td> 

                    <td>{{id}}</td> 

                    <td>

                      {{#each hobbys as |hobby hobbyId|}}

                          {{!-- 

                                                    상대경로를 참조해서 상위 이터레이션의 name 값을 가져오는 것은

                          with 헬퍼를 사용해서 {{#with ../this.name as |myName|}}

                           같이 처리해줄 수도 있고 아래와 같이

                                                    lookup 활용해서 처리해줄 수도 있다.

                                                --}}

                        {{lookup ../this "name"}}  {{hobbyId}} 취미 {{hobby}}

                      {{/each}}

                    </td>

                </tr> 

            {{/each}}

        </tbody> 

    </table>

 

</script>

Colored by Color Scripter

cs

 

Lookup을 통해 상대 경로에  name값을 찾아서 출력해주고 있다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//핸들바 템플릿 가져오기

var source = $("#entry-template").html();

 

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동", id: "aaa", hobbys: ['축구''야구''농구'] },

            { name"이순신", id: "bbb", hobbys: ['족구''피구''탁구'] },

            { name"이성계", id: "ccc", hobbys: ['수구''배구''농구'] },

            { name"장영실", id: "ddd", hobbys: ['축구''피구''농구'] },

            { name"장보고", id: "eee", hobbys: ['배구''야구''족구'] }

        ]

}; 

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

Colored by Color Scripter

cs

 

 

1

2

3

4

5

6

이름    아이디    취미

홍길동    aaa    홍길동  0 취미 축구 홍길동  1 취미 야구 홍길동  2 취미 농구

이순신    bbb    이순신  0 취미 족구 이순신  1 취미 피구 이순신  2 취미 탁구

이성계    ccc    이성계  0 취미 수구 이성계  1 취미 배구 이성계  2 취미 농구

장영실    ddd    장영실  0 취미 축구 장영실  1 취미 피구 장영실  2 취미 농구

장보고    eee    장보고  0 취미 배구 장보고  1 취미 야구 장보고  2 취미 족구

cs

 

 

2.    Handlebar의 장점

 

  • precompile할 수 있기 때문에 성능개선에 도움이 된다.

  • Helper를 자유롭게 추가할 수 있다.

  • JADE와 같은 문법에 비해 친숙하면서도 가독성이 높다.

  • Grunt 등으로 빌드 자동화에 precompile 과정을 포함시키기 쉽다.

  • 템플릿에는 로직 코드를 넣지 않는 것이 일반적이다.

    • 컴파일한 템플릿에서 오류가 발생했을 때 디버깅도 어렵다.

    • 실제 문법에서도 간단한 분기문, 배열, 반복문 정도를 지원한다.

 


 

참고사이트

 

페이지: http://handlebarsjs.com/

깃허브: https://github.com/wycats/handlebars.js/

http://ohgyun.com/427

http://sailboat-d.tistory.com/40

http://jojoldu.tistory.com/23

http://tmondev.blog.me/220398995882

http://programmingsummaries.tistory.com/381

https://blog.outsider.ne.kr/939

http://aroundck.tistory.com/957

http://uncle-bae.blogspot.kr/2016/06/handlebars-expression.html


'IT > Handlebar' 카테고리의 다른 글

Handlebar란  (0) 2017.01.12



1.    Handlebar.js


Handlebar.js(이하 Handlebar) 자바스크립트의 템플릿 엔진 하나이다. 먼저 템플릿 엔진이라 하면 프로그램 로직과 프레젠테이션 계층을 분리하기위한 수단이다.  Mustache 기반으로 구현한 템플릿 인자, 여기서 Mustache 콧수염모양의 {{ }} Bracket 이용하여 data 표현하는 것을 의미한다. 이를 이용하면 html페이지에서 HTML+Bracket 구성으로 디자이너와 개발자가 협업할 때도 디자이너에게도 이해하기 쉬운 구조로써 협업을 하는데도 도움이 된다.

 

 

2.    기본 사용방법


표현식


  • {{title}}  같은 형태로 표현되며, 현재 Context에서 title이라는 Property를 찾아서 대체하는 의미이다.

  • {{section.title}}  같은 형태로 dot(.)으로 분리된 경로 탐색도 가능하다. 현재 Context에서 section을 찾고, title 속성을 찾아서 대체하는 의미이다.

  • {{section/title}} 과 같이 / 문법을 사용할 수 있다. 식별자는 아래 열거된 UniCode를 제외하고 모두 사용 가능하다.

    • Whitespace ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~

  • 배열 접근 표현식을 통해 Property에 접근할 수 있다section 배열의 3번째 titles 의 모든 property 를 context 로 갖으면서 subject 와 body 에 접근한다.

1

2

3

4

5

6

{{#each section.[3].titles}}

  <h1>{{subject}}</h1>

  <div>

    {{body}}

  </div>

{{/each}}

cs


  • {{! }} 블록으로 주석을 추가할 수 있다.

  • {{log}} 블록으로 템플릿 출력 시 로깅할 수 있다.

  • {{{body}}} HTML요소는 자동으로 이스케이프 시켜준다.

 

Helper


  • Helper 기본적으로 다른 개발 언어의 함수라고 볼 수 있다.

  • 기본 문법은 아래와 같다.

 

1

{{{ Helper_Name param1 parm2 ... }}}

cs

  • HandlebarsHelper Call은 단순한 식별자이며, 0개 이상의 Parameter를 받을 수 있다. 그리고 구분은 공백으로 한다. 나누게 된다.  Handlesbars는 위에 설명한 표현식과 완전히 동일한 방법으로 사용된다.

  • 반복과 같은 다른 템플릿엔진에서 제공하는 Helper 존재한다.

 

1

2

3

{{#each userlist}}

{{user}}

{{/each}}

cs

 

아래 코드는 link라는 Helper story라는 Parameter를 넣어 실행시킨 결과를 출력하라는 의미이다.

 

1

{{{link story}}}

cs

 

link helper  Handlebars.registerHelper(Helper_Name, function ) 으로 등록한다.

 

1

2

3

4

5

Handlebars.registerHelper('link'function(object) {

  return new Handlebars.SafeString(

    "<a href='" + object.url + "'>" + object.text + "</a>"

  );

});

Colored by Color Scripter

cs

 

Helper 는 최종 Parameter들로 key-value pair hash 를 전달할 수도 있다.

 

1

{{{link "See more..." href=story.url class="story"}}}

cs

 

이럴 경우 Helper 함수에서는 hash 부분을 하나의 object 로 받아서, object.hash 로 접근하여 key-value pair 에 대한 context를 얻을 수 있다.

 

1

2

3

4

5

6

7

8

9

10

11

Handlebars.registerHelper('link'function(text, options) {

  var attrs = [];

 

  for(var prop in options.hash) {

    attrs.push(prop + '="' + options.hash[prop] + '"');

  }

 

  return new Handlebars.SafeString(

    "<a " + attrs.join(" "+ ">" + text + "</a>"

  );

});

Colored by Color Scripter

cs

 

Subexpressions


Handlebars subexpression을 제공한다. 이는 단일 mustache를 이용하여 복합 Helper를 호출할 수 있다. 그리고 내부 Helper의 결과를 외부 Helper Parameter로 전달 할 수 있다. Subexpression ( )로 구분한다.

1

{{outer-helper (inner-helper 'abc''def' }}

cs

 

위 코드에서는 inner-helper는 문자열 'abc'를 받는 Helper로 호출이 되며 이 결과는 outer-helper의 첫번째 Parameter로 전달된다. 그리고 'def'는 두번째 Parameter 로 전달된다.

 

Whitespace Control


템플릿의 공백은 특정 mustache스테이트먼트의 각 부분에서 ~ 문자를 브레이스{의 의해서 생략이 될 수 있다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

{

    nav: [

        {url: 'foo', test: true, title: 'bar'},

        {url: 'bar'}

    ]

}

 

 

{{#each nav ~}}

    <a href="{{url}}">

        {{~#if test}}

            {{~title}}

        {{~^~}}

            Empty

        {{~/if~}}

    </a>

{{~/each}}

Colored by Color Scripter

cs

 

출력 결과 뉴라인과 포매팅된 공백 처리 결과는 아래와 같다.

 

1

<a href="foo">bar</a><a href="bar">Empty</a>

cs

 

이 확장은 라인 처리의 기본적인 동작이며 "standalone" Helper이다.

 

1

2

3

4

5

6

7

8

9

{{#each nav}}

    <a href="{{url}}">

        {{#if test}}

            {{title}}

        {{^}}

            Empty

        {{/if}}

    </a>

{{~/each}}

cs

 

 

결과는 아래와 같다.

 

1

2

3

4

5

6

<a href="foo">

    bar

</a>

<a href="bar">

    Empty

</a>

cs

 

 

 

Escaping


Handlebars컨텐츠는 하나 혹은 두개의 방법으로 이스케이프 될 수 있다. 이는 인라인 이스케이프 혹은 raw block helpers이다. 인라인 escape prefixing mustache블록으로 \으로 시작하는 블록이다. Raw block {{{{ mustache 브레이스로 생성된다.

 

1

2

3

4

\{{escaped}}

{{{{raw}}}}

    {{escaped}}

{{{{/raw}}}}

cs



 

참고사이트

 

페이지: http://handlebarsjs.com/

깃허브: https://github.com/wycats/handlebars.js/

http://ohgyun.com/427

http://sailboat-d.tistory.com/40

http://jojoldu.tistory.com/23

http://tmondev.blog.me/220398995882

http://programmingsummaries.tistory.com/381

https://blog.outsider.ne.kr/939

http://aroundck.tistory.com/957

http://uncle-bae.blogspot.kr/2016/06/handlebars-expression.html

 

'IT > Handlebar' 카테고리의 다른 글

Handlebars 코딩 해보기  (0) 2017.01.12

+ Recent posts