代码之家  ›  专栏  ›  技术社区  ›  user3732216

Laravel模型工厂和测试

  •  4
  • user3732216  · 技术社区  · 7 年前

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

    有没有人对纠正我的逻辑有什么建议,这样我就可以正确地创建游戏和事件?

    我不知道我该怎么处理这个边缘案件。

    /** @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 回复  |  直到 6 年前
        1
  •  2
  •   Oliver Hader    7 年前

    注意:下面显示的源代码基于一些假设,因为模型的具体实现,例如 Event , Game Team 原始问题中没有给出。考虑在问题中添加一个包含源代码的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()
    {
      ...
    }
    

    所提到的测试用例 FirstGameDate之前 验证可能如下所示,使用预言-要测试的实例命名为 $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')
        );
    }
    

    这边,你的 事件 , 游戏 团队 模型不再依赖于任何工厂实现,而是使用预言来模拟属性和行为。因此,如果工厂发生更改或重构,您只需调整那些断言对象重新定位的测试,即 beforeFirstGameDateValidatorRejectsLateApplications 可以跳过,因为它对这些工厂没有很强的依赖性。

    如开头所述 事件 , 游戏 团队 这些都只是假设,因为在撰写本回答时,实际实现还不清楚。

    参考文献:

        2
  •  2
  •   Mathieu Ferre    7 年前

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

    创建事件

    将游戏添加到事件

    将团队添加到游戏中

    如果需要测试日期,只需在以下日期后编辑:)