PHPUnitManual:12.1

From 흡혈양파의 번역工房
Jump to: navigation, search
12.1 은행 구좌에 대한 예제

이 절에서는 은행 구좌를 표현하는 클래스를 예로 소개합니다. 예금 잔고의 조회, 설정, 입금과 출금 등의 메소드를 가지는 BankAccount 클래스는 다음 2가지 제약을 만족시켜야 합니다.

  • 예금 잔고의 초기값은 0 이어야만 한다.
  • 예금 잔고가 0 이하로 되서는 안 된다.


먼저 BankAccount 클래스의 테스트를 작성한 뒤에, 실제 코드를 작성하도록 합시다. 위의 두 제약을 테스트 작성의 기준으로 삼아, 이 기준에 따라 예12.1 "BankAccount 클래스의 테스트" 에 있는 것처럼 테스트 메소드의 이름을 붙입니다.


예12.1 BankAccount 클래스의 테스트

<?php
require_once 'BankAccount.php';
 
class BankAccountTest extends PHPUnit_Framework_TestCase
{
    protected $ba;
 
    protected function setUp()
    {
        $this->ba = new BankAccount;
    }
 
    public function testBalanceIsInitiallyZero()
    {
        $this->assertEquals(0, $this->ba->getBalance());
    }
 
    public function testBalanceCannotBecomeNegative()
    {
        try {
            $this->ba->withdrawMoney(1);
        }
 
        catch (BankAccountException $e) {
            $this->assertEquals(0, $this->ba->getBalance());
 
            return;
        }
 
        $this->fail();
    }
 
    public function testBalanceCannotBecomeNegative2()
    {
        try {
            $this->ba->depositMoney(-1);
        }
 
        catch (BankAccountException $e) {
            $this->assertEquals(0, $this->ba->getBalance());
 
            return;
        }
 
        $this->fail();
    }
}
?>


그리고, 첫번째 테스트인 testBalanceIsInitiallyZero() 를 통과할 수 있도록 필요 최소한의 코드를 작성해 봅시다. 필요한 것은 BankAccount 클래스의 getBalance() 메소드를 예12.2 "테스트 testBalanceIsInitiallyZero() 를 통과하기 위해 필요한 코드" 에 나온 것처럼 구현하는 것입니다.


예12.2 테스트 testBalanceIsInitiallyZero() 를 통과하기 위해 필요한 코드

<?php
class BankAccount
{
    protected $balance = 0;
 
    public function getBalance()
    {
        return $this->balance;
    }
}
?>


이걸로 첫번째 테스트는 통과하지만, 두번째 테스트에서 실패할 것입니다. 그 이유는, 테스트 메소드 안에서 호출하는 메소드가 아직 구현되지 않았기 때문입니다.

phpunit BankAccountTest
PHPUnit 3.7.0 by Sebastian Bergmann.

.
Fatal error: Call to undefined method BankAccount::withdrawMoney()


두번째 규약의 테스트를 통과하기 위해서는, withdrawMoney(), depositMoney(), 그리고 setBalance() 메소드들을 예12.3 "완전한 BankAccount 클래스" 에 나온 것처럼 구현해야만 합니다. 이 메소드들은, 규약에 어긋나는 인수를 넘겨 받은 경우, BankAccountException 를 발생시키도록 구현되어 있습니다.


예12.3 완전한 BankAccount 클래스

<?php
class BankAccount
{
    protected $balance = 0;
 
    public function getBalance()
    {
        return $this->balance;
    }
 
    protected function setBalance($balance)
    {
        if ($balance >= 0) {
            $this->balance = $balance;
        } else {
            throw new BankAccountException;
        }
    }
 
    public function depositMoney($balance)
    {
        $this->setBalance($this->getBalance() + $balance);
 
        return $this->getBalance();
    }
 
    public function withdrawMoney($balance)
    {
        $this->setBalance($this->getBalance() - $balance);
 
        return $this->getBalance();
    }
}
?>


이제 두번째 규약에 관한 테스트도 통과하게 되었습니다.

phpunit BankAccountTest
PHPUnit 3.7.0 by Sebastian Bergmann.

...

Time: 0 seconds


OK (3 tests, 3 assertions)


다른 방법으로는, PHPUnit_Framework_Assert 클래스가 제공하는 정적 검증 메소드를 사용하여, 코드 안에서 "규약에 의한 설계" 방식의 검증을 작성하는 것입니다. 예12.4 ""규약에 의한 설계" 검증을 사용한 BankAccount 클래스"가 그 예입니다. 예에 나온 검증 중 하나라도 실패할 경우, 예외 PHPUnit_Framework_AssertionFailedError 가 발생합니다. 이 방식을 사용하여 조건 체크 코드를 줄일 수 있기 때문에 코드의 가독성이 좋아집니다. 단, 프로그램을 실행할 때에도 PHPUnit 이 필요합니다.


예12.4 "규약에 의한 설계" 검증을 사용한 BankAccount 클래스

<?php
class BankAccount
{
    private $balance = 0;
 
    public function getBalance()
    {
        return $this->balance;
    }
 
    protected function setBalance($balance)
    {
        PHPUnit_Framework_Assert::assertTrue($balance >= 0);
 
        $this->balance = $balance;
    }
 
    public function depositMoney($amount)
    {
        PHPUnit_Framework_Assert::assertTrue($amount >= 0);
 
        $this->setBalance($this->getBalance() + $amount);
 
        return $this->getBalance();
    }
 
    public function withdrawMoney($amount)
    {
        PHPUnit_Framework_Assert::assertTrue($amount >= 0);
        PHPUnit_Framework_Assert::assertTrue($this->balance >= $amount);
 
        $this->setBalance($this->getBalance() - $amount);
 
        return $this->getBalance();
    }
}
?>


규약을 만족시키기 위한 조건을 테스트 안에 포함시켜서 "규약에 의한 설계" 방식으로 BankAccount 클래스를 프로그래밍했습니다. 다음으로, Test-First Programming 의 개념에 따라, 테스트를 통과하기 위해 필요한 코드를 작성했습니다. 여기에는 한가지 빠진 사항이 있는데, setBalance(), depositMoney(), 그리고 withdrawMoney() 에 올바른 값을 지정한 경우, 정상 작동하는지는 확인하는 테스트를 작성하는 것입니다. 테스트를 빠짐없이 작성하기 위해서는, 작성한 테스트가 적절한 것인지, 충분한지를 조사하기 위한 테스트가 필요합니다. 다음 장에서는 이를 위한 "code-coverage 해석" 을 설명하겠습니다.


Notes