質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.04%

デフォルトで使える -v のテスト方法

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 1,568

hotta

score 1541

Laravel 5.2 で以下のようなコンソールアプリを作りました。動作としては問題ないように見えます。

$ ./artisan goodexample:start

  [RuntimeException]
  Either id or tag needed.

$ ./artisan goodexample:start --id=1
GoodExampleCommand called with id=1

$ ./artisan goodexample:start --id=1 -v
GoodExampleCommand called with id=1(verbose)

$ ./artisan goodexample:start --undefined=1

  [Symfony\Component\Console\Exception\RuntimeException]
  The "--undefined" option does not exist.

ソースは以下の通りです。当初はコマンド定義として $signature を使っていましたが、今は $name と getOptions() に変えています。

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use RuntimeException;

class GoodExampleCommand extends Command
{
//protected $signature = 'example:start {--id=} {--tag=}';
  protected $name = 'goodexample:start';
  protected $description = 'Run GoodExample';
  public function __construct()
  {
      parent::__construct();
  }
  public function handle()
  {
    $id = $this->option('id');
    $tag = $this->option('tag');
    if (!$id && !$tag) {
      throw new RuntimeException('Either id or tag needed.');
    }
    $this->info(sprintf("GoodExampleCommand called with %s=%s%s",
      $id ? 'id':'tag', $id ? $id : $tag,
      $this->getOutput()->isVerbose() ? '(verbose)' : ''));
  }
  protected function getOptions()
  {
    return  [
      [ 'id', 'i',  InputOption::VALUE_REQUIRED, 'desc of id', null ],
      [ 'tag', 't', InputOption::VALUE_REQUIRED, 'desc of tag', null ],
    ];
  }
}


ところがテストでは、デフォルトで使える -v が未定義オプション扱いになってしまいます。

$ phpunit tests/GoodExampleCommandTest.php
PHPUnit 5.6.0 by Sebastian Bergmann and contributors.

...E                                                                4 / 4 (100%)

Time: 130 ms, Memory: 10.00MB

There was 1 error:

1) GoodExampleCommandTest::testGoodExampleCommandWithIdVerboseWillSuccess
Symfony\Component\Console\Exception\InvalidOptionException: The "-v" option does not exist.

/var/www/laravel/vendor/symfony/console/Input/ArrayInput.php:154
/var/www/laravel/vendor/symfony/console/Input/ArrayInput.php:136
/var/www/laravel/vendor/symfony/console/Input/Input.php:62
/var/www/laravel/vendor/symfony/console/Command/Command.php:221
/var/www/laravel/vendor/laravel/framework/src/Illuminate/Console/Command.php:155
/var/www/laravel/tests/GoodExampleCommandTest.php:53
/var/www/laravel/tests/GoodExampleCommandTest.php:45

ERRORS!
Tests: 4, Assertions: 4, Errors: 1.

テスト側のソースは以下の通りです:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use App\Console\Commands\GoodExampleCommand;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Input\ArrayInput;

class GoodExampleCommandTest extends TestCase
{
  public function setUp()
  {
    parent::setUp();
    $this->command = new \App\Console\Commands\GoodExampleCommand;
    $this->command->setLaravel($this->app);
  }

  /** example:start - no options
   * @expectedException RuntimeException
   * @expectedExceptionMessage Either id or tag needed.
   */
  public function testGoodExampleCommandWithoutMandatoryOptionsWillFail()
  {
    $output = $this->execute();
  }

  /** example:start - id specified */
  public function testGoodExampleCommandWithIdWillSuccess()
  {
    $output = $this->execute(['--id' => 'id1']);
    $this->assertEquals('GoodExampleCommand called with id=id1',
      trim($output->fetch()));
  }

  /** example:star - tag specified */
  public function testGoodExampleCommandWithTagWillSuccess()
  {
    $output = $this->execute(['--tag' => 'tag1']);
    $this->assertEquals('GoodExampleCommand called with tag=tag1',
      trim($output->fetch()));
  }

  /** example:start - id specified (verbose) */
  public function testGoodExampleCommandWithIdVerboseWillSuccess()
  {
    $output = $this->execute(['--id' => 'id1', '-v' => null ]);  // ここが通らない
    $this->assertEquals('GoodExampleCommand called with id=id1(verbose)',
      trim($output->fetch()));
  }

  protected function execute(array $params = [])
  {
    $output = new BufferedOutput();
    $this->command->run( new ArrayInput($params), $output);
    return $output;
  }

}

CLI に関するテストの書き方は書籍(LaravelリファレンスVer.5.1 LTS対応)を参考にしていますが、Symfony のコンポーネントを使って実行する部分については、正直あまり理解できておりません。何かアドバイスがありましたらお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

Laravelのことはあまり分かりませんが、Symfony\Consoleサイドから回答させていただきます。原因は、-vや-hなどの共通オプションを管理しているのがSymfony\Component\Console\Command\Command(Illuminate\Console\Commandの親クラス)ではなく、Symfony\Component\Console\Applicationなことだと思います。

Symfony\Consoleで作成したコンソールアプリケーションでは、新しいCommandApplicationに追加してから呼び出す形になっていて、この場合に-vオプションなどが有効になるようです。多分、Laravelでも内部で利用しているのではないでしょうか。

この仕組みをテストで簡単に扱えるよう用意されているのがCommandTesterというクラスになります。一度以下の内容で試してみてください。

// 追加
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
// 変更
public function setUp()
{
    parent::setUp();
    $goodExampleCommand = new \App\Console\Commands\GoodExampleCommand;
    $goodExampleCommand->setLaravel($this->app);

    // ApplicationにCommandを登録
    $app = new Application();
    $app->add($goodExampleCommand);

    // CommandTesterを被せる
    $command = $app->find('goodexample:start');
    $this->command = new CommandTester($command);
}

// 変更
protected function execute(array $params = [])
{
    $this->command->execute($params);
    return $this->command->getDisplay();
}

 参考

Console コンポーネント | Symfony2日本語ドキュメント
http://docs.symfony.gr.jp/symfony2/components/console/introduction.html

console/Application.php at master · symfony/console
https://github.com/symfony/console/blob/master/Application.php#L892

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/10/14 10:30

    変更後ソース(テスト側):

    ```GoodExampleCommand.php
    <?php

    use Illuminate\Foundation\Testing\WithoutMiddleware;
    use App\Console\Commands\GoodExampleCommand;
    use Symfony\Component\Console\Output\BufferedOutput;
    use Symfony\Component\Console\Input\ArrayInput;

    use Symfony\Component\Console\Application;
    use Symfony\Component\Console\Tester\CommandTester;

    class GoodExampleCommandTest extends TestCase
    {
    public function setUp()
    {
    parent::setUp();
    $goodExampleCommand = new \App\Console\Commands\GoodExampleCommand;
    $goodExampleCommand->setLaravel($this->app);

    // ApplicationにCommandを登録
    $app = new Application();
    $app->add($goodExampleCommand);

    // CommandTesterを被せる
    $command = $app->find('goodexample:start');
    $this->command = new CommandTester($command);
    }

    /** example:start - no options
    * @expectedException RuntimeException
    * @expectedExceptionMessage Either id or tag needed.
    */
    public function testGoodExampleCommandWithoutMandatoryOptionsWillFail()
    {
    $output = $this->execute();
    }

    /** example:start - id specified */
    public function testGoodExampleCommandWithIdWillSuccess()
    {
    $output = $this->execute(['--id' => 'id1']);
    $this->assertEquals('GoodExampleCommand called with id=id1',
    trim($output));
    }

    /** example:star - tag specified */
    public function testGoodExampleCommandWithTagWillSuccess()
    {
    $output = $this->execute(['--tag' => 'tag1']);
    $this->assertEquals('GoodExampleCommand called with tag=tag1',
    trim($output));
    }

    /** example:start - id specified (verbose) */
    public function testGoodExampleCommandWithIdVerboseWillSuccess()
    {
    $output = $this->execute(['--id' => 'id1', '-v' => null ]);
    $this->assertEquals('GoodExampleCommand called with id=id1(verbose)',
    trim($output));
    }

    protected function execute(array $params = [])
    {
    $this->command->execute($params);
    return $this->command->getDisplay();
    }

    }
    ```

    これで再度テストを行ってみましたが、

    ```bash
    $ phpunit tests/GoodExampleCommandTest.php
    PHPUnit 5.6.0 by Sebastian Bergmann and contributors.

    ...F 4 / 4 (100%)

    Time: 139 ms, Memory: 10.00MB

    There was 1 failure:

    1) GoodExampleCommandTest::testGoodExampleCommandWithIdVerboseWillSuccess
    Failed asserting that two strings are equal.
    --- Expected
    +++ Actual
    @@ @@
    -'GoodExampleCommand called with id=id1(verbose)'
    +'GoodExampleCommand called with id=id1'

    /var/www/laravel/tests/GoodExampleCommandTest.php:57

    FAILURES!
    Tests: 4, Assertions: 5, Failures: 1.
    ```

    今度はアプリケーションが -v を認識してくれなくなりました。
    その後、以下のように変更することで、テストが通るようになりました!

    ```bash
    s$ diff -Nur GoodExampleCommandTest.php.orig GoodExampleCommandTest.php
    --- GoodExampleCommandTest.php.orig 2016-10-14 10:25:00.037816409 +0900
    +++ GoodExampleCommandTest.php 2016-10-14 10:23:48.688917317 +0900
    @@ -6,6 +6,7 @@
    use Symfony\Component\Console\Input\ArrayInput;

    use Symfony\Component\Console\Application;
    +use Symfony\Component\Console\Output\OutputInterface;
    use Symfony\Component\Console\Tester\CommandTester;

    class GoodExampleCommandTest extends TestCase
    @@ -53,14 +54,15 @@
    /** example:start - id specified (verbose) */
    public function testGoodExampleCommandWithIdVerboseWillSuccess()
    {
    - $output = $this->execute(['--id' => 'id1', '-v' => null ]);
    + $output = $this->execute(['--id' => 'id1', '-v' => null ],
    + [ 'verbosity' => OutputInterface::VERBOSITY_VERBOSE ]);
    $this->assertEquals('GoodExampleCommand called with id=id1(verbose)',
    trim($output));
    }

    - protected function execute(array $params = [])
    + protected function execute(array $input = [], $options = [])
    {
    - $this->command->execute($params);
    + $this->command->execute($input, $options);
    return $this->command->getDisplay();
    }

    ```

    以上、ご指導いただきまして、ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.04%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる