상세 컨텐츠

본문 제목

[Cucumber] 예제를 이용한 Cucumber 학습

IT/프로그래밍

by James Lee. 2015. 12. 1. 08:38

본문

본 포스트는 아래 해외 블로그를 참고하고 스스로 번역한것입니다.

잘못된 내용이 있을 수도 있으니 양해 바랍니다.


비어있는 메이븐 프로젝트 생성 후, 아래 각각의 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로 테스트하면 되는 것.



관련글 더보기

댓글 영역