Laravel模型工厂与测试

时间:2017-12-29 14:05:35

标签: laravel laravel-5 phpunit

我尝试创建一个团队,然后将该团队添加到游戏中,然后将该游戏添加到该事件中,但是,当创建游戏时,它会自动生成附加到该事件的事件。在正常情况下,这很好但是因为我测试了团队加入时与他们的第一个游戏所在的事件相比,那么我需要创建一个具有特定日期的事件。我无法先创建活动,因为必须首先创建游戏才能将其添加到游戏中。

有没有人建议纠正我的逻辑,以便我可以正确创建游戏和事件?

我不知道我应该为这个边缘案件做些什么。

/** @test */
public function a_team_with_a_game_after_they_started_the_season_cannot_have_their_joined_at_date_after_their_first_match()
{
    $team = factory(Team::class)->create(['joined_at' => '2017-10-08']);

    $game = GameFactory::create([], [$team]);
    $event = EventFactory::create(['date' => '2017-10-09'], null, $game);

    $validator = new BeforeFirstGameDate($team);

    $this->assertFalse($validator->passes('joined_at', '2017-10-10'));
    $this->assertEquals('The joined at date cannot be AFTER the team\'s first game.', $validator->message());
}

Factories

<?php

use App\Models\Game;
use App\Models\Team;

class GameFactory
{
    public static function create($overrides = [], $teams = [])
    {
        $match = factory(Game::class)->create($overrides);

        self::addTeamsForGame($teams, $game);

        return $game;
    }

     /**
     * @param $teams
     * @param $game
     */
    public static function addTeamsForGame($teams, $game)
    {
        $teamsForGame = [];

        $numberOfTeamsToAdd = $numberOfTeams - count($teams);

        if ($numberOfTeamsToAdd) {
            $teamsForMatch = factory(Team::class, $numberOfTeamsToAdd)->create();
            array_push($teams, $teamsForGame);
        } else {
            array_push($teams, $teamsForGame);
        }

        $match->addTeams($teamsForGame);
    }
}


<?php

use App\Models\Event;

class EventFactory
{
    public static function create($overrides = [], $totalNumberOfGames = 8, $games = [])
    {
        $event = factory(Event::class)->create($overrides);

        $numberOfGamesToAdd = $totalNumberOfGames - count($games);
        $gameToStartAt = count($games) + 1;

        foreach (array_wrap($games) as $game) {
            $game->addToEvent($event);
        }

        for ($gameNumber = $gameToStartAt; $gameNumber <= $numberOfGamesToAdd; $gameNumber++) {
            GameFactory::create(['event_id' => $event->id, 'game_number' => $gameNumber]);
        }

        return $event;
    }
}


$factory->define(App\Models\Game::class, function (Faker\Generator $faker) {
    static $order = 1;
    return [
        'event_id' => function () {
            return factory(App\Models\Event::class)->create()->id;
        },
        'game_number' => $order++,

    ];  
});

$factory->define(App\Models\Event::class, function (Faker\Generator $faker) {
    $name = $faker->sentence;
    return [
        'name' => $name,
        'slug' => str_slug($name),
        'date' => $faker->dateTimeBetween('-10 years'),
    ];
 });

2 个答案:

答案 0 :(得分:2)

您应该使用与在应用程序中执行此操作时相同的逻辑:

•创建活动

•为比赛添加游戏

•在游戏中添加团队

如果您需要测试日期,请在以下后编辑它们:)

答案 1 :(得分:2)

  

注意:下面显示的源代码基于一些假设,因为在原始问题中未给出例如EventGameTeam等模型的特定实现。考虑添加一个包含问题来源的Git存储库,以获得更具体的答案,更详细地反映您的实现。

首先,对测试的一些一般性评论:

  • 单元测试(如果上面的代码是一个,重点是单元)应该只测试您的域图层的一个特定方面 - 因此,要确保BeforeFirstGameDate是行为正确但没有测试可能涉及的服务(数据库)或工厂组合以进行对象重构 - 要使用的技术是“模拟”或“预言”
  • 另一方面,
  • 集成测试(这就是上面代码的样子)应该用真实的实现进行测试(如果可能的话,没有“模拟”或“预言”),但是在伪造/提供的情况下 - 意味着你想要要测试工厂,模型和验证器的整个应用程序,您应该在数据库中提供完整的测试场景,并根据这些测试场景进行测试

正如所说,并将重点放在“单元测试”中的单元部分,您的测试可能看起来像 - 专注于测试单个,分离的单元而不是它们的组合:

/** @test */
public function eventIsCreated()
{
  ...
  static::assertSame($expectedEvent, EventFactory::create(...));
}
/** @test */
public function teamIsCreated()
{
  ...
  static::assertSame($expectedTeam, TeamFactory::create(...));
}
/** @test */
public function gameIsCreated()
{
  ...
  static::assertSame($expectedGame, GameFactory::create(...));
}
/** @test */
public function beforeFirstGameDateValidatorRejectsLateApplications()
{
  ...
}

上面提到的BeforeFirstGameDate验证的测试用例可能看起来像下面那样,使用预言 - 要测试的实例被命名为$subject以使其清楚,要测试的主题是什么(作为编写测试的常见最佳实践):

/**
 * @test
 */
public function beforeFirstGameDateValidatorRejectsLateApplications()
{
    $event = $this->prophesize(Event::class);
    $game = $this->prophesize(Game::class);
    $team = $this->prophesize(Team::class);

    $event->getGames()->willReturn([$game->reveal()]);
    $game->getTeams()->willReturn([$team->reveal()]);
    $team->get('joined_at')->willReturn('2017-10-08');

    $subject = new BeforeFirstGameDate($team->reveal());

    static::assertFalse(
        $subject->passes('joined_at', '2017-10-10')
    );
}

这样,您的EventGameTeam模型不再依赖任何工厂实现,而是使用预言模拟属性和行为。因此,如果工厂被更改或重构,您只需要调整那些断言对象重构的测试 - 可以跳过提到的beforeFirstGameDateValidatorRejectsLateApplications,因为它没有对这些工厂的硬依赖性。

正如开头所提到的,EventGameTeam的方法和属性只是假设,因为在撰写此答案时真正的实现是未知的。

参考文献: