77
PHPUnit よりよくテスト書くために @yuya_takeyama

PHPUnit でよりよくテストを書くために

Embed Size (px)

DESCRIPTION

第56回PHP勉強会@関東で PHPUnit について話してきた http://blog.yuyat.jp/archives/1386

Citation preview

Page 1: PHPUnit でよりよくテストを書くために

PHPUnit でよりよくテストを

書くために@yuya_takeyama

Page 2: PHPUnit でよりよくテストを書くために

自己紹介•@yuya_takeyama

•LAMP でお仕事

•メタラー (Black Sabbath, 聖飢魔II, Eyehategod)•Openpear でライブラリ公開してます•\Text\Ngram•HTTP_Parallel•Cache_Casual

Page 3: PHPUnit でよりよくテストを書くために

•テンプレートエンジン•PHP array markup Language

•[] (array) で HTML が書ける

•PHP 5.4 でしか動かない•元ネタは Clojure の Hiccup

Pamlhttps://github.com/yuya-takeyama/php-HTML_Paml

Page 4: PHPUnit でよりよくテストを書くために

['begin', ['define', 'fib', ['lambda', ['x'], ['if', ['<', ':x', 2], ':x', ['+', ['fib', ['-', ':x', 2]], ['fib', ['-', ':x', 1]]]]]], ['print', 'fib(10) = '], ['println', ['fib', 10]]] => fib(10) = 55

LisPHPhttps://github.com/yuya-takeyama/LisPHP

Page 5: PHPUnit でよりよくテストを書くために

テストと私•2010/02東京 RubyKaigi 03 で RSpec について教わる

•2010/03 頃PHP の assert() でテストコードを書いてみる

•2010/04 頃PHPUnit を業務で使い始める

•そして今に至る

Page 6: PHPUnit でよりよくテストを書くために

アジェンダ•PHPUnit 概説•よりよくテストを書くとはどういうことか

•よりよくテストを書くために•書法編

•パターン編

Page 7: PHPUnit でよりよくテストを書くために

PHPUnit 概説

Page 8: PHPUnit でよりよくテストを書くために

PHPUnit とは•テスティングフレームワーク•ユニットテストを書く•比較的簡単に書ける•多機能

Page 9: PHPUnit でよりよくテストを書くために

何故 PHPUnit か

•豊富なドキュメント•日本語訳も充実•豊富な利用実績 (ZF, Symfony2, etc)

Page 10: PHPUnit でよりよくテストを書くために

class CalculatorTest extends PHPUnit_Framework_TestCase{    public function setUp()    {        $this->calc = new Calculator;    }

    public function test_add_引数の和を返す()    {        $result = $this->calc->add(1, 2);        $this->assertSame(3, $result);    }}

Page 11: PHPUnit でよりよくテストを書くために

class CalculatorTest extends PHPUnit_Framework_TestCase{    public function setUp()    {        $this->calc = new Calculator;    }

    public function test_add_引数の和を返す()    {        $result = $this->calc->add(1, 2);        $this->assertSame(3, $result);    }}

1

テストに必要な物の用意

Page 12: PHPUnit でよりよくテストを書くために

class CalculatorTest extends PHPUnit_Framework_TestCase{    public function setUp()    {        $this->calc = new Calculator;    }

    public function test_add_引数の和を返す()    {        $result = $this->calc->add(1, 2);        $this->assertSame(3, $result);    }}

2

テスト対象の実行

Page 13: PHPUnit でよりよくテストを書くために

class CalculatorTest extends PHPUnit_Framework_TestCase{    public function setUp()    {        $this->calc = new Calculator;    }

    public function test_add_引数の和を返す()    {        $result = $this->calc->add(1, 2);        $this->assertSame(3, $result);    }}

3

実行結果の検証 (アサーション)

Page 14: PHPUnit でよりよくテストを書くために
Page 15: PHPUnit でよりよくテストを書くために
Page 16: PHPUnit でよりよくテストを書くために

よりよくテストを書くとは

どういうことか

Page 17: PHPUnit でよりよくテストを書くために

何故テストを書くか•ドキュメント•回帰テスト•リファクタリング•設計

Page 18: PHPUnit でよりよくテストを書くために

ドキュメントとしてのテスト

•Tests as Documentation

•API の一覧•動作するサンプルコード

Page 19: PHPUnit でよりよくテストを書くために

回帰テストとは•ある修正が新たなバグを生んでいないか

•リグレッションテストとも言う•同じ過ちを繰り返さないため

Page 20: PHPUnit でよりよくテストを書くために

リファクタリングとは•振る舞いを変えること無く•ソースコードの内部構造を•変更すること•ソースコードの体質改善

Page 21: PHPUnit でよりよくテストを書くために

テストと設計

•Test Driven Design?•ライブラリは API が 9 割•API のユーザビリティテスト•リファクタリングで継続的改善

Page 22: PHPUnit でよりよくテストを書くために

テストで設計を考える•テスタビリティ•オブジェクト指向•デザインパターン•リファクタリング

Page 23: PHPUnit でよりよくテストを書くために

よりよくテストを

書くために

Page 24: PHPUnit でよりよくテストを書くために

書法編

Page 25: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が正常であればtrue(){    $user = new User;    $user->setName('Yuya');    $user->setUrl('http://yuyat.jp/');    $user->setAge(24);    $this->assertTrue($user->isValid());}

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = new User;    $user->setName('Yuya');    $user->setUrl('http://yuyat.jp/');    $user->setAge(NULL);    $this->assertFalse($user->isValid());}

Page 26: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が正常であればtrue(){    $user = new User;    $user->setName('Yuya');    $user->setUrl('http://yuyat.jp/');    $user->setAge(24);    $this->assertTrue($user->isValid());}

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = new User;    $user->setName('Yuya');    $user->setUrl('http://yuyat.jp/');    $user->setAge(NULL);    $this->assertFalse($user->isValid());}

どこがどう違うのかわかりづらい

Page 27: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が正常であればtrue(){    $user = $this->createValidUser();    $this->assertTrue($user->isValid());}

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());}

Page 28: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が正常であればtrue(){    $user = $this->createValidUser();    $this->assertTrue($user->isValid());}

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());}

ここが違う

Page 29: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が正常であればtrue(){    $user = $this->createValidUser();    $this->assertTrue($user->isValid());}

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());}

ここが違う

何が影響して結果に差が生まれたのかが明確

Page 30: PHPUnit でよりよくテストを書くために

ヘルパーメソッドを使う•複雑なオブジェクトの生成•複雑な依存オブジェクトの生成•長くても説明的で明確な名前•エッジケースに注視しやすく

Page 31: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());

    $user = $this->createValidUser();    $user->setName(NULL);    $this->assertFalse($user->isValid());}

Page 32: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());

    $user = $this->createValidUser();    $user->setName(NULL);    $this->assertFalse($user->isValid());}

ひとつのテスト内で複数のアサーション

Page 33: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの状態が異常であればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());

    $user = $this->createValidUser();    $user->setName(NULL);    $this->assertFalse($user->isValid());}

ひとつのテスト内で複数のアサーション

何をテストしたいのかわかりづらい

Page 34: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの年齢がNullであればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());}

public function test_isValid_ユーザの名前がNullであればfalse(){    $user = $this->createValidUser();    $user->setName(NULL);    $this->assertFalse($user->isValid());}

Page 35: PHPUnit でよりよくテストを書くために

public function test_isValid_ユーザの年齢がNullであればfalse(){    $user = $this->createValidUser();    $user->setAge(NULL);    $this->assertFalse($user->isValid());}

public function test_isValid_ユーザの名前がNullであればfalse(){    $user = $this->createValidUser();    $user->setName(NULL);    $this->assertFalse($user->isValid());} テストメソッドの名前も

より具体的に

Page 36: PHPUnit でよりよくテストを書くために

テストメソッドを分割する

•何のテストなのかを明確に•エッジケースを意識する•1 テスト 1 アサーション

Page 37: PHPUnit でよりよくテストを書くために

書法編 まとめ•Tests as Documentation

•何をテストしているのか明確に•ヘルパーメソッドの利用•テストメソッドの命名

Page 38: PHPUnit でよりよくテストを書くために

パターン編

Page 39: PHPUnit でよりよくテストを書くために

class Request{    public function isSsl()    {        return $_SERVER['HTTPS'] === 'on';    }}

Page 40: PHPUnit でよりよくテストを書くために

class Request{    public function isSsl()    {        return $_SERVER['HTTPS'] === 'on';    }}

スーパーグローバル変数に依存している

Page 41: PHPUnit でよりよくテストを書くために

public function test_isSsl_HTTPSであればtrue(){    $_SERVER['HTTPS'] = 'on';    $this->assertTrue($this->request->isSsl());}

public function test_isSsl_HTTPSでなければfalse(){    unset($_SERVER['HTTPS']);    $this->assertFalse($this->request->isSsl());}

Page 42: PHPUnit でよりよくテストを書くために

public function test_isSsl_HTTPSであればtrue(){    $_SERVER['HTTPS'] = 'on';    $this->assertTrue($this->request->isSsl());}

public function test_isSsl_HTTPSでなければfalse(){    unset($_SERVER['HTTPS']);    $this->assertFalse($this->request->isSsl());}

グローバル変数を無理矢理書き換え

Page 43: PHPUnit でよりよくテストを書くために

public function test_isSsl_HTTPSであればtrue(){    $_SERVER['HTTPS'] = 'on';    $this->assertTrue($this->request->isSsl());}

public function test_isSsl_HTTPSでなければfalse(){    unset($_SERVER['HTTPS']);    $this->assertFalse($this->request->isSsl());}

グローバル変数を無理矢理書き換え

他のテストの影響を考慮する必要性

Page 44: PHPUnit でよりよくテストを書くために

public function test_isSsl_HTTPSであればtrue(){    $_SERVER['HTTPS'] = 'on';    $this->assertTrue($this->request->isSsl());}

public function test_isSsl_HTTPSでなければfalse(){    unset($_SERVER['HTTPS']);    $this->assertFalse($this->request->isSsl());}

グローバル変数を無理矢理書き換え

他のテストの影響を考慮する必要性

複数のテストが影響し得る≒ テストが壊れやすい

Page 45: PHPUnit でよりよくテストを書くために

class Request{ public function __construct($server) {     $this->_server = $server; }

public function isSsl() {     return $this->_server === 'on'; }}

Page 46: PHPUnit でよりよくテストを書くために

class Request{ public function __construct($server) {     $this->_server = $server; }

public function isSsl() {     return $this->_server === 'on'; }}

サーバ変数を外から差し込んでいる

Page 47: PHPUnit でよりよくテストを書くために

public function test_isSsl_HTTPSであればtrue(){    $request = new Request(array('HTTPS' => 'on'));    $this->assertTrue($request->isSsl());}

public function test_isSsl_HTTPSでなければfalse(){    $request = new Request(array());    $this->assertFalse($request->isSsl());}

Page 48: PHPUnit でよりよくテストを書くために

public function test_isSsl_HTTPSであればtrue(){    $request = new Request(array('HTTPS' => 'on'));    $this->assertTrue($request->isSsl());}

public function test_isSsl_HTTPSでなければfalse(){    $request = new Request(array());    $this->assertFalse($request->isSsl());}

複数のテスト間の影響が排除された!

Page 49: PHPUnit でよりよくテストを書くために

依存性は外から差し込む•Dependency Injection•差し込める ≒ 差し替えられる•再利用性の高い設計•テスタビリティを意識すると自然とそうなる

Page 50: PHPUnit でよりよくテストを書くために

外部への依存を避ける•コンテキストを明確に•グローバル変数•ファイルシステム•ネットワーク/データベース

Page 51: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }

    public static function findByName($name) { /*~*/ }    public function save() { /*~*/ }}

ActiveRecord

Page 52: PHPUnit でよりよくテストを書くために

$user = User::findByName('Bob');

$user->setName('Alice');

$user->save();

ActiveRecord

Page 53: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }}

class UserMapper{    public function findByName($name) { /*~*/ }    public function save(User $user) { /*~*/ }}

DataMapper

Page 54: PHPUnit でよりよくテストを書くために

$mapper = new UserMapper;

$user = $mapper->findByName('Bob');

$user->setName('Alice');

$mapper->save($user);

DataMapper

Page 55: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }

    public static function findByName($name) { /*~*/ }    public function save() { /*~*/ }}

ActiveRecord

Page 56: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }

    public static function findByName($name) { /*~*/ }    public function save() { /*~*/ }}

ActiveRecord

ビジネスロジック

Page 57: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }

    public static function findByName($name) { /*~*/ }    public function save() { /*~*/ }}

ActiveRecord

ビジネスロジック

データアクセス

Page 58: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }

    public static function findByName($name) { /*~*/ }    public function save() { /*~*/ }}

ActiveRecord

ビジネスロジック

データアクセス

異なる関心事が混交している

Page 59: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }}

class UserMapper{    public function findByName($name) { /*~*/ }    public function save(User $user) { /*~*/ }}

DataMapper

Page 60: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }}

class UserMapper{    public function findByName($name) { /*~*/ }    public function save(User $user) { /*~*/ }}

DataMapperビジネスロジック

Page 61: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }}

class UserMapper{    public function findByName($name) { /*~*/ }    public function save(User $user) { /*~*/ }}

DataMapperビジネスロジック

データアクセス

Page 62: PHPUnit でよりよくテストを書くために

class User{    protected $_name;    protected $_birthday;

    public function setName($name) { /*~*/ }    public function getName() { /*~*/ }    public function getAge() { /*~*/ }}

class UserMapper{    public function findByName($name) { /*~*/ }    public function save(User $user) { /*~*/ }}

DataMapperビジネスロジック

データアクセス

異なる関心事が分離されている

Page 63: PHPUnit でよりよくテストを書くために

class Config{    protected static $_instance;

    private function __construct() {}

    public static function getInstance()    {        if (empty(self::$_instance)) {            self::$_instance = new self;        }        return self::$_instance;    }}

Page 64: PHPUnit でよりよくテストを書くために

public function __clone(){    throw new BadMethodCallException( 'Clone is not allowed.' );}

Page 65: PHPUnit でよりよくテストを書くために

public function test_get_セットされた値を取得する(){    $config = Config::getInstance();    $config->set('message', 'Hello');    $this->assertSame( 'Hello', $config->get('message') );}

Page 66: PHPUnit でよりよくテストを書くために

public function test_get_値がセットされていなければNull(){    $config = Config::getInstance();    $this->assertNull( $config->get('message') );}

Page 67: PHPUnit でよりよくテストを書くために

public function test_get_値がセットされていなければNull(){    $config = Config::getInstance();    $this->assertNull( $config->get('message') );}

他のテストで値がセットされている可能性

Page 68: PHPUnit でよりよくテストを書くために

public function test_get_値がセットされていなければNull(){    $config = Config::getInstance();    $config->init();    $this->assertNull( $config->get('message') );}

Page 69: PHPUnit でよりよくテストを書くために

public function test_get_値がセットされていなければNull(){    $config = Config::getInstance();    $config->init();    $this->assertNull( $config->get('message') );}

状態の初期化

Page 70: PHPUnit でよりよくテストを書くために

public function test_get_値がセットされていなければNull(){    $config = Config::getInstance();    $config->init();    $this->assertNull( $config->get('message') );}

状態の初期化

一応の解決にはなるが...

Page 71: PHPUnit でよりよくテストを書くために

Singleton を避ける

•Singleton ≒ グローバル変数•状態を元に戻しにくい•テストが書きにくい

Page 72: PHPUnit でよりよくテストを書くために

パターン編 まとめ•依存性は外から差し込む•外部への依存を避ける•テスタブルな単位にクラス分割•Singleton は避ける

Page 73: PHPUnit でよりよくテストを書くために

まとめ

•可読性に留意してテストを書く•まずはパターンを身につける•テストで設計を考える

Page 74: PHPUnit でよりよくテストを書くために

参考資料 (書籍)• xUnit Test Patterns: Refactoring Test Code

http://www.amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054

• Real-World Solutions for Developing High-Quality PHP Frameworks and Applicationshttp://www.amazon.com/Real-World-Developing-High-Quality-Frameworks-Applications/dp/0470872497

• Patterns of Enterprise Application Architecturehttp://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420

• PHPによるデザインパターン入門http://d.hatena.ne.jp/shimooka/20100301/1267436385

Page 75: PHPUnit でよりよくテストを書くために

参考資料 (スライド)• Dependency Injection with PHP 5. 3

http://www.slideshare.net/fabpot/dependency-injection-with-php-53

• BEAR DIhttp://www.slideshare.net/akihito.koriyama/bear-di

• Clean PHPhttp://www.slideshare.net/sebastian_bergmann/clean-php-dpc11

• Singletons in PHP - Why they are bad and how you can eliminate them from your applicationshttp://www.slideshare.net/go_oh/singletons-in-php-why-they-are-bad-and-how-you-can-eliminate-them-from-your-applications

Page 76: PHPUnit でよりよくテストを書くために

Questions?

Page 77: PHPUnit でよりよくテストを書くために

ご清聴ありがとうございました