PHPpunit 测试随机失败

时间:2021-03-05 09:17:48

标签: php symfony phpunit tdd

我有一个测验课程。 此类根据测验对象的级别和类型从数据库中加载 10 个问题: 级别 0 先加载 10 个,级别 1 加载接下来的 10 个,依此类推。

所以在我的测试中,我在测试数据库中创建了 30 个问题。 然后我创建了不同级别的测验对象,并检查测验步骤数组中的第一个问题是否符合我的预期。

此测试“quiz_contain_steps_depending_on_type_and_level()”至少每 5 次发布随机失败一次。

这是 QuizTest 类

<?php


namespace App\Tests\Quiz;

use App\Quiz\Question;
use App\Quiz\Quiz;
use App\Quiz\QuizQuestionRepositoryManager;
use App\Quiz\QuizStep;
use App\Quiz\QuizType;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectRepository;
use Faker\Factory;
use Faker\Generator;
use ReflectionException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Config\Definition\Exception\Exception;


class QuizTest extends KernelTestCase
{
    use QuestionLoremTrait;
    use PrivatePropertyValueTestTrait;

    private Generator $faker;
    private ?EntityManagerInterface $em;
    private ObjectRepository $questionRepo;
    private QuizQuestionRepositoryManager $quizQuestionManager;

    protected function setUp(): void
    {
        $kernel = self::bootKernel();
        $this->faker = Factory::create();
        $this->em = $kernel->getContainer()->get('doctrine')->getManager();
        $this->em->getConnection()->beginTransaction();

        $this->questionRepo = $kernel->getContainer()->get('doctrine')->getRepository(Question::class);
        $this->quizQuestionManager = new QuizQuestionRepositoryManager($this->questionRepo);
    }

    protected function tearDown(): void
    {
        parent::tearDown();
        $this->em->getConnection()->rollBack();
        $this->em->close();
        $this->em = null;
    }

    /**
     * @test
     * @dataProvider provideQuizDataAndFirstQuestionExpectedIndex
     * @param array $quizData
     * @param int $firstQuestionExpectedIndex
     * @throws ReflectionException
     * @throws \Exception
     */
    public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
    {
        //We have questions in db
        $questions = [];

        for ($q = 1; $q <= 30; $q++) {
            $question = $this->persistLoremQuestion($this->faker, $this->em);
            $questions[] = $question;
        }
        $this->em->flush();


        //When we create Quiz instance $quiz
        $quiz = new Quiz($this->quizQuestionManager,quizData:  $quizData);

        //When we look at this $quiz steps property
        $quizSteps = $quiz->getSteps();
        /** @var QuizStep $firstStep */
        $firstStep = $quizSteps[0];

        //We expect
        $this->assertNotEmpty($quizSteps);
        $this->assertCount(10, $quizSteps);

        //We expect if quiz is type normal and level variable questions depends of level:
        $this->assertEquals($firstStep->getQuestion(), $questions[$firstQuestionExpectedIndex]);

    }

    public function provideQuizDataAndFirstQuestionExpectedIndex(): array
    {
        return [
            [[], 0],
            [['type' => QuizType::NORMAL, 'level' => '1'], 10],
            [['type' => QuizType::NORMAL, 'level' => '2'], 20]
        ];
    }
}

这是生成假问题的特质

<?php

namespace App\Tests\Quiz;

use App\Quiz\Question;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Faker\Generator;

Trait QuestionLoremTrait{

    /**
     * This function persist a aleatory generated question, you must flush after
     * @param Generator $faker
     * @param EntityManagerInterface $em
     * @return Question
     * @throws Exception
     */
    public function persistLoremQuestion(Generator $faker, EntityManagerInterface $em): Question
    {
        $nbrOfProps = random_int(2,4);
        $answerPosition = random_int(0, $nbrOfProps - 1);
        $props = [];

        for ($i = 0; $i < $nbrOfProps; $i++){
            $props[$i] = $faker->sentence ;
        }

        $question = new Question();

        $question
            ->setSharedId(random_int(1, 2147483647))
            ->setInfo($faker->paragraph(3))
            ->setStatement($faker->sentence ."?")
            ->setProps($props)
            ->setAnswerPosition($answerPosition)
        ;

        $em->persist($question);

        return $question;
    }
}

这是我的测验课:

<?php


namespace App\Quiz;


use Symfony\Component\Config\Definition\Exception\Exception;

class Quiz
{
    /**
     * Quiz constructor.
     * @param QuizQuestionManagerInterface $quizQuestionManager
     * @param array $quizData
     * This array of key->value represent quiz properties.
     * Valid keys are 'step','level','type'.
     * You must use QuizType constant as type value
     * @param string $type
     * @param int $level
     * @param int $currentStep
     * @param array $steps
     */
    public function __construct(
        private QuizQuestionManagerInterface $quizQuestionManager,
        private string $type = QuizType::FAST,
        private int $level = 0,
        private array $quizData = [],
        private int $currentStep = 0,
        private array $steps = [])
    {

        if ($quizData != []) {
            $this->hydrate($quizData);
        }
        $this->setSteps();
    }


    private function hydrate(array $quizData)
    {
        foreach ($quizData as $key => $value) {
            $method = 'set' . ucfirst($key);

            // If the matching setter exists
            if (method_exists($this, $method) && $method != 'setQuestions') {
                // One calls the setter.
                $this->$method($value);
            }
        }
    }

    public function getCurrentStep(): int
    {
        return $this->currentStep;
    }

    public function getLevel(): int
    {
        return $this->level;
    }

    public function getType(): string
    {
        return $this->type;
    }

    public function getSteps(): array
    {
        return $this->steps;
    }

    private function setCurrentStep($value): void
    {
        $this->currentStep = $value;
    }

    private function setLevel(int $level): void
    {
        $this->level = $level;
    }

    private function setType($type): void
    {
        if (!QuizType::exist($type)) {
            throw new Exception("This quiz type didn't exist, you must use QuizType constante to define type", 400);
        }
        $this->type = $type;
    }

    private function setSteps()
    {
        $this->steps = [];
        $questions = $this->quizQuestionManager->getQuestions($this->type, $this->level);
        foreach ($questions as $question) {
            $this->steps[] = new QuizStep(question: $question);
        }
    }
}

这是问题类:

<?php


namespace App\Quiz;

use App\Repository\QuestionRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=QuestionRepository::class)
 */
class Question
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id;

    /**
     * @ORM\Column(type="integer")
     */
    private ?int $sharedId;

    /**
     * @ORM\Column(type="string", length=1000, nullable=true)
     * @Assert\Length(max=1000)
     */
    private ?string $info;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private ?string $statement;

    /**
     * @ORM\Column(type="array")
     */
    private array $props = [];

    /**
     * @ORM\Column(type="integer")
     */
    private ?int $answerPosition;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getSharedId(): ?int
    {
        return $this->sharedId;
    }

    public function setSharedId(int $sharedId): self
    {
        $this->sharedId = $sharedId;

        return $this;
    }

    public function getInfo(): ?string
    {
        return $this->info;
    }

    public function setInfo(?string $info): self
    {
        $this->info = $info;

        return $this;
    }

    public function getStatement(): ?string
    {
        return $this->statement;
    }

    public function setStatement(?string $statement): self
    {
        $this->statement = $statement;

        return $this;
    }

    public function getProps(): ?array
    {
        return $this->props;
    }

    public function setProps(array $props): self
    {
        $this->props = $props;

        return $this;
    }

    public function getAnswerPosition(): ?int
    {
        return $this->answerPosition;
    }

    public function setAnswerPosition(int $answerPosition): self
    {
        $this->answerPosition = $answerPosition;

        return $this;
    }
}

如果有人理解这种行为。我提前感谢他帮助我睡得更好:-)

1 个答案:

答案 0 :(得分:-1)

感谢@AlessandroChitolina 的评论。

在我的测试中创建的问题集在我的数据库中并不总是以相同的顺序记录。

因此,我没有从我的起始 $questions 数组中测试预期的问题,而是在新的 $dbQuestions 数组中从数据库中检索问题。这解决了我的问题。

这是新的测试:

/**
     * @test
     * @dataProvider provideQuizDataAndFirstQuestionExpectedIndex
     * @param array $quizData
     * @param int $firstQuestionExpectedIndex
     * @throws \Exception
     */
    public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
    {
        //We have questions in db
        $questions = [];

        for ($q = 1; $q <= 30; $q++) {
            $question = $this->persistLoremQuestion($this->faker, $this->em);
            $questions[] = $question;
        }
        $this->em->flush();

        $dbQuestions = $this->questionRepo->findAll();

        //When we create Quiz instance $quiz
        $quiz = new Quiz($this->quizQuestionManager,quizData:  $quizData);

        //When we look at this $quiz steps property
        $quizSteps = $quiz->getSteps();
        /** @var QuizStep $firstStep */
        $firstStep = $quizSteps[0];

        //We expect
        $this->assertNotEmpty($quizSteps);
        $this->assertCount(10, $quizSteps);

        //We expect if quiz is type normal and level variable questions depends of level:
        $this->assertEquals($firstStep->getQuestion(), $dbQuestions[$firstQuestionExpectedIndex]);
    }