본 포스트는 아래 해외 블로그를 참고하고 스스로 번역한것입니다.
잘못된 내용이 있을 수도 있으니 양해 바랍니다.
비어있는 메이븐 프로젝트 생성 후, 아래 각각의 defendency들을 들을 너의 프로젝트의 pom.xml에 더해라.
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
표준 메이븐 프로젝트 디렉토리 구조는 src/test/resource라는 소스 폴더를 가질 것이다.
이것은 너의 feature files에 만들어야 하는 디렉토리다.
그러니까 사용자 은행 계좌에 예금하기 위해 feature을 만들자
deposit.feature이라는 파일을 src/test/resources에 만들어라.
물론 패키지를 만들어도 된다(이 소스폴더에 feature 파일들을 조직화하기 위해 만들 수 있다.
자, 이제 우리의 첫번째 시나리오를 써보자.
우리는 총액이 예금되고 있을때 사용자의 총계에 의해 현재 업데이트 되고 있는 잔금을 가진 계좌를 테스트하기를 원한다.
src/test/resources/com/c0deattack/cucumberjvmtutorial/deposit.feature
1 2 3 4 5 6 | Feature: Depositing money in to a User account Scenario: Depositing money in to User's account should add money to the User's current balance Given a User has no money in their account When £100 is deposited in to the account Then the 잔액 should be £100 |
기능 : 사용자 계좌에 돈 예금
시나리오 : 사용자 계좌에 돈 예금은 돈을 사용자의 현재 금액만큼 더해야 한다.
주어진 것(Given) : 사용자들은 그들의 현재 계좌에 돈이 없다.
조건(When) : 100불은 계좌에 예금된다.
결과(Then) : 잔금은 100불이 되어야 한다.
첫번째 시나리오는 성공적으로 통과되기 위해 실행되어야 하는 단계들을 묘사한다
현재 시점에서 너는 아마 어떻게 이 시나리오를 실행시킬지 궁금해하고 있을 것이다
너는 우리가 벌써 가지고 있는 폼 Cucumber-Junit Library에 .feature를 JUnit Runner를 이용하여 실행시킨다.
그래서 모든 우리는 비어있는 테스트 클래스를 만들어야 하고 이것에 주석을 달 필요가 있다.
JUnit's @RunWith annotation as follows (뭐 @Override처럼 시스템에 알려줄 주석을 달아야 할 필요가 있다고 하는 듯)
src/test/java/com/c0deattack/cucumberjvmtutorial/RunTests.java
1 2 3 4 5 6 7 8 9 | package com.c0deattack.cucumberjvmtutorial; import org.junit.runner.RunWith; import cucumber.junit.Cucumber; @RunWith (Cucumber. class ) @Cucumber .Options(format={ "pretty" , "html:target/cucumber" }) public class RunTests { } |
넌 지금 니가 다른 JUnit 테스트를 실행시키는 것처럼 RunTest.java를 실행시킬수 있다!
너의 이클립스 IDE에 있는 JUnit Tab을 한번 봐라.
너는 통과된 테스트를 볼 것이다.
어떻게 이것이 우리가 사용자, 계좌, 돈을 예금하는 계좌 메소드를 만들지 않았는데도 통과될까?
더 자세히 봐라.
시나리오의 단계들은 사실 생략된다.
그것들은 실행되지 않는다.
왜냐하면 아무 정의된 단계도 찾을 수 없기 때문이다.
이클립스 콘솔 탭에 있는 출력물을 봐라.
너는 실행된 시나리오에 대한 정보와 생략된 단계들에 대한 매우 유용한 정보를 볼 것이다.
출력물은 또한 너가 디폴트로 이 단계 정의들을 실행하는 것을 보여주고 있다.
얼마나 놀라운가!
단계 정의를 위해 클래스를 만들자
src/test/java/com/c0deattack/cucumberjvmtutorial/DepositStepDefinitions.java
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 | package com.c0deattack.cucumberjvmtutorial; import cucumber.annotation.en.Given; import cucumber.annotation.en.Then; import cucumber.annotation.en.When; import cucumber.runtime.PendingException; public class DepositStepDefinitions { @Given ( "^a User has no money in their account$" ) public void a_User_has_no_money_in_their_current_account() { // Express the Regexp above with the code you wish you had throw new PendingException(); } @When ( "^£(\\d+) is deposited in to the account$" ) public void £_is_deposited_in_to_the_account( int arg1) { // Express the Regexp above with the code you wish you had throw new PendingException(); } @Then ( "^the balance should be £(\\d+)$" ) public void the_balance_should_be_£( int arg1) { // Express the Regexp above with the code you wish you had throw new PendingException(); } } |
주어진 단계를 시작해보자.
1. 사용자는 그들의 계좌에 현재 돈이 없다.
이 단계는 우리에게 유저 엔티티와 계좌 엔티티를 만들도록 힌트를 준다.
그것들을 DepositStepDefinitions.java에 지역적으로 만들어 보자.
우리는 나중에 우리의 코드를 refactor할 것이다(코드를 깔끔하게 다듬는다는 말, 왜냐하면 지역적으로 만들었기 때문에 ㅋㅋ)
src/test/java/com/c0deattack/cucumberjvmtutorial/DepositStepDefinitions.java
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 | package com.c0deattack.cucumberjvmtutorial; import cucumber.annotation.en.Given; import cucumber.annotation.en.Then; import cucumber.annotation.en.When; import cucumber.runtime.PendingException; public class DepositStepDefinitions { @Given ( "^a User has no money in their account$" ) public void a_User_has_no_money_in_their_current_account() { User user = new User(); Account account = new Account(); user.setAccount(account); } @When ( "^£(\\d+) is deposited in to the account$" ) public void £_is_deposited_in_to_the_account( int arg1) { // Express the Regexp above with the code you wish you had throw new PendingException(); } @Then ( "^the balance should be £(\\d+)$" ) public void the_balance_should_be_£( int arg1) { // Express the Regexp above with the code you wish you had throw new PendingException(); } private class User { private Account account; public void setAccount(Account account) { this .account = account; } } private class Account { } } |
(오, 시나리오에서 변환된 Given When Then이 각 메소드에 annotation (@)으로 정의되어있다.
그런데 이게 아직까지 어떤 역할인지는 잘 모르겠다. 정규표현식은 왜 들어가는 걸까?)
이것은 꽤 괜찮다, 주어진 단계들은 balance이 제로가 되어야 한다고 말하고 있다.
그러나 모든 우리는 이 사용자와 계좌를 만들고 있는 단계 정의 안에서 하고 있다.
그래서 또한 문장이 balance을 0으로 만드는 것을 보장하기 위해 assert문을 더하자.
src/test/java/com/c0deattack/cucumberjvmtutorial/DepositStepDefinitions.java
1 2 3 4 5 6 7 | @Given ( "^a User has no money in their account$" ) public void a_User_has_no_money_in_their_current_account() { User user = new User(); Account account = new Account(); user.setAccount(account); assertTrue( "The balance is not zero." , account.getbalance() == 0 ); } |
assert문을 6번째 줄에 더하는 것은 우리에게 계좌 안의 잔금을 가르쳐준다.
지금 RunTest.java를 실행해봐라.
그러면 너는 100달러?가 계좌에 예금되었을때 우리가 지금 실행해야 하는 것을 볼 것이다.
그것을 지금 해보자.
우리는 우리의 단계 정의를 메소드 인자로써 통과한 총계를 가질 필요가 있다.
그리고 그것을 이전에 만들어진 계좌 안에 예금해라.
그래서 우리는 우리가 만든 계좌 안에 접근할 필요가 있다.
우리가 아래처럼 계좌에 접근할 수 있도록. 이 코드를 refactor하자.
src/test/java/com/c0deattack/cucumberjvmtutorial/DepositStepDefinitions.java
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 47 48 49 50 51 52 53 54 55 56 57 | package com.c0deattack.cucumberjvmtutorial; import static org.junit.Assert.assertTrue; import cucumber.annotation.en.Given; import cucumber.annotation.en.Then; import cucumber.annotation.en.When; import cucumber.runtime.PendingException; public class DepositStepDefinitions { private Account account; @Given ( "^a User has no money in their account$" ) public void a_User_has_no_money_in_their_current_account() { User user = new User(); account = new Account(); user.setAccount(account);
assertTrue( "The balance is not zero." , account.getbalance() == 0L); } @When ( "^£(\\d+) is deposited in to the account$" ) public void £_is_deposited_in_to_the_account( int amount) { account.deposit(amount); } @Then ( "^the balance should be £(\\d+)$" ) public void the_balance_should_be_£( int arg1) { // Express the Regexp above with the code you wish you had throw new PendingException(); } private class User { private Account account; public void setAccount(Account account) { this .account = account; } } private class Account { private int balance; public Account() { this .balance = 0 ; } public int getbalance() { return balance; } public void deposit( int amount) { this .balance += amount; } } } |
여기 우리는 새로운 계좌 클래스 멤버를 만들었다.
이것은 주어진 우리가 총액을 예금한 단계에 따라 초기화되었다.
이 단계들은 account.deposit(amount) method를 만들도록 우리를 가이드해왔다.
단계 정의 실행을 더한 후,
우리는 Feature을 다시 실행한다.
우리는 여전히 마지막 balance이 100달러가 되는 마지막 단계를 적절하게 실행하는 것이 필요하다고 들었다.
1 2 3 4 5 | @Then ( "^the balance should be £(\\d+)$" ) public void the_balance_should_be_£( int expectedbalance) { int currentbalance = account.getbalance(); assertTrue( "The expected balance was £100, but actually was: " + currentbalance, currentbalance == expectedbalance); } |
이게 전부다!
너는 너의 첫번째 기능을 첫번째 시나리오와 함께 썼다.
그리고 너의 첫번째 단계 정의를 시나리오를 실행했다.
다음은 무엇일까?
리팩토링!!
분명 우리는 우리의 DepositStepDefinitions.java 클래스를 리팩터링 할 기회가 많다.
사례에서는 우리는 이러한 private class를 떠나지 않는다
그리고 우리는 이것들과 같은 Account class member을 정말로 원하지 않는다.
우린 우리의 단계들이 많은 시나리오 사이에서 재사용될수 있는것을 원한다.
그러나 단계들은 첫번째 계좌가 존재한다는 보장이 없으면 사용 될 수 없다.
나의 다음 포스트에서는 우리는 리팩토링할것이다. 이 클래스를 저러한 문제들에 대해 연설하기 위해
하지만, 나의 리팩토링 포스팅을 왜 기다리는가?
스스로 해보시길!
추가 실험 사항
deposit.feature에 있는 시나리오를 한글로 바꾼다.
Feature: 사용자 계좌에 돈 예금
Scenario: 사용자 계좌에 돈 예금은 돈을 사용자의 현재 금액만큼 더해야 한다.
Given : 사용자들은 그들의 현재 계좌에 돈이 없다.
When : 100불은 계좌에 예금된다.
Then : 잔금은 100불이 되어야 한다.
콘솔의 결과
Feature: 사용자 계좌에 돈 예금
Scenario: 사용자 계좌에 돈 예금은 돈을 사용자의 현재 금액만큼 더해야 한다. [90m# resources/deposit.feature:3 [0m
[33mGiven [0m [33m: 사용자들은 그들의 현재 계좌에 돈이 없다. [0m
[33mWhen [0m [33m: 100불은 계좌에 예금된다. [0m
[33mThen [0m [33m: 잔금은 100불이 되어야 한다. [0m
1 Scenarios ( [33m1 undefined [0m)
3 Steps ( [33m3 undefined [0m)
0m0.000s
You can implement missing steps with the snippets below:
@Given("^: 사용자들은 그들의 현재 계좌에 돈이 없다\\.$")
public void 사용자들은_그들의_현재_계좌에_돈이_없다() throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@When("^: (\\d+)불은 계좌에 예금된다\\.$")
public void 불은_계좌에_예금된다(int arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@Then("^: 잔금은 (\\d+)불이 되어야 한다\\.$")
public void 잔금은_불이_되어야_한다(int arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
배운 것 : 자연어를 컴퓨터 언어(메소드)로 변환해주는 듯
이렇게 변환되어 나온 콘솔창이 테스트의 뼈대가 된다.
여기에 값만 넣어서 리팩토링 해주면 되는 듯..
※ Feature, Scenario, Given, When, Then : 은 영어로 써줘야 된다. 이것을 인식해서 메소드로 변환해주는 것 같다.
번역을 마치며 |
영문자료를 한글로 옮겨서 정리한거는 이번이 처음인데 영어와 한글은 정확한 1:1매칭이 안된다는것을 체감했다. (옮겨도, 어색하다. 의역해야된다) 그리고 우리가 말하거나 쓸때, 문법을 정확하게 지키지는 않는 것처럼 해외에서도 모든 영어의 문법을 맞춰서 말하지는 않는다. (그래서 가끔 번역이 꼬일때가 있는 것 같다) |
그래서..연습하고 나니까 Cucumber는 무엇인것 같은가?
Cucumber는 이클립스에 내장되어 Maven과 같이 쓰이는 플러그인이다.
이 플러그인은 자연어를 메소드로 바꿔주는데 이때 약간의 형식이 필요하다 (기능, 시나리오, Given, When, Then)
그러면 Cucumber는 이 자연어를 해석해서 메소드 형태로 바꿔준다.
Given, When Then에 각각의 자연어가 메소드 이름으로 붙는다.
그러면 각 메소드에 자기가 원하고 싶었던 처리 방법을 쓰면 된다.
이것을 TestCase로 테스트하면 되는 것.
[Cucumber] Cucumber 단계 정의 (0) | 2015.12.01 |
---|---|
[머신러닝] 머신러닝이란? (0) | 2015.12.01 |
[개발 프로세스] 익스트림 프로그래밍 : 운전하는 법 배우기 (0) | 2015.12.01 |
[개발 프로세스] 익스트림 프로그래밍 : XP란 무엇인가 (0) | 2015.12.01 |
[개발 프로세스] 익스트림 개발 방법론, eXtream Programming, XP (0) | 2015.12.01 |
댓글 영역