正确的单元测试

时间:2017-09-11 21:42:01

标签: php symfony unit-testing ldap phpunit

我开始在这个项目中使用单元和功能测试,因此我有一些问题:

我正在使用symfony php框架。我有像LDAP ORM服务这样的学说。

此外,我有一个用户存储库(作为服务),它依赖于LDAP ORM服务,记录器和验证服务。

现在我想为UserRepo的addUser函数编写单元测试。 它将在内部调用:getNewUidNumber,userToEntities,doesUserExist和getUserByUid。

我的问题是: 我应该模拟所有这些内部函数来测试addUser函数吗?这是否违背了单元测试的想法(只是测试API)。

或者我应该只是模拟LDAP ORM服务,Logger和验证服务,以便该类调用所有内部函数?但是这会导致一个巨大的测试功能,因为我必须模拟所有内部存储库调用的存储库。

或者我应该启动symfony内核并使用ServiceContainer将ORM LDAP服务与真实的测试数据库一起使用。但这不是功能测试而不是单元测试吗? 我听说在测试中有这么多依赖项是不好的。所以我认为使用整个serviceContainer会很糟糕。

增加会员:

public function addUser(User $user)
{
    $pbnlAccount = $this->userToEntities($user);

    if(!$this->doesUserExist($user)) {
        $pbnlAccount->setUidNumber($this->getNewUidNumber());
        $this->ldapEntityManager->persist($pbnlAccount);
        $this->ldapEntityManager->flush();
    }
    else {
        throw new UserAlreadyExistException("The user ".$user->getUid()." already exists.");
    }

    return $this->getUserByUid($user->getUid());
}

更多代码,例如内部函数: https://gist.github.com/NKPmedia/4a6ee55b6bb96e8af409debd98950678

由于 保罗

3 个答案:

答案 0 :(得分:2)

首先,如果可以的话,我想稍微改写一下这个方法。

public function addUser(User $user)
{
    if ($this->doesUserExist($user)) {
        throw new UserAlreadyExistException("The user ".$user->getUid()." already exists.");
    }

    // ... shortened for brevity
    $pbnlAccount = $this->userToEntities($user);
    $this->ldapEntityManager->persist($pbnlAccount);
}

其他相关方法是:

private function doesUserExist(User $user)
{
    $users = $this->ldapRepository->findByUid($user->getUid());
    return count($users) === 1;
}

我们立即可以看到我们基本上有两个测试:

  • 我们测试该方法在用户存在时抛出
  • 如果用户不存在
  • ,我们测试该方法是否持续存在PbnlAccount。

如果你不明白为什么我们有这两个测试,请注意这个方法中有两个可能的“流”:一个执行if语句中的块,另一个不执行它。

让我们解决第一个问题:

public function testAddUserThrowsWhenUserExistsAlready()
{
    $user = new User();
    $user->setUid('123');

    $ldapRepositoryMock = $this->createMock(LdapRepository::class);
    $ldapRepositoryMock
        ->method('findByUid')
        ->expects($this->once())
        ->with('123')
        ->willReturn(new PbnlAccount());

    $userRepository = new UserRepository($ldapRepositoryMock);

    $this->expectException(UserAlreadyExistException::class);
    $userRepository->addUser($user);    
}

第二次测试留给读者练习:)

在你的情况下做一些嘲弄。在这种情况下,您将需要模拟LdapRepository和LdapEntityManager。

注1:这段代码可能不可运行,因为我不知道你的代码库的确切细节(我把它写在了我的脑海中),但这不是重点。关键是你要测试异常。

注2: 我会将你的函数重命名为createNewPbnlAccountForUser(User $user),这个函数更长,但更具描述性。它实际上做了什么。

注3: 我不确定你为什么要返回$this->getUserByUid(),因为那似乎是多余的(你已经拥有了用户),所以我省略了这种情况。

答案 1 :(得分:1)

你需要模拟ldapEntityManager和所有存储库服务而不是内部函数。如你所说,不要在单元测试中启动内核。因此,您应该成功测试所有案例并抛出异常(确保检查所有行为)

答案 2 :(得分:0)

如果要执行单元测试,则应模拟所有协作者。 现在,不应该模拟实体管理器,ldap服务等(read more here)。

此外,如果您恰好处于编配部分(设置模拟,存根等)很痛苦并且需要很多事情的情况下。测试,也许这是一种气味,你的班级有太多的责任(做太多事情)。

那就是说,当我进行单元测试时,我希望测试只能因为内部(对班级)的原因而失败,而不是因为我改变了一个混乱的合作者行我所有的测试。