如何模拟php本机ldap功能,如lda_connect、ldap_get_条目、ldap_搜索和ldap_读取
我使用下面的类连接到ldap服务器,以检查用户是否属于某个组。我想为这个类编写单元测试。我如何做到这一点。如何模拟ldap php本机函数。谁能帮我写一点示例代码吗如何模拟php本机ldap功能,如lda_connect、ldap_get_条目、ldap_搜索和ldap_读取,php,unit-testing,mocking,ldap,phpunit,Php,Unit Testing,Mocking,Ldap,Phpunit,我使用下面的类连接到ldap服务器,以检查用户是否属于某个组。我想为这个类编写单元测试。我如何做到这一点。如何模拟ldap php本机函数。谁能帮我写一点示例代码吗 <?php namespace ABC\Admin\Login; use Doctrine\ORM\EntityManagerInterface; /** * Class Authenticate AD Login * @package Adp\Admin\Login */ class LdapAuthentica
<?php
namespace ABC\Admin\Login;
use Doctrine\ORM\EntityManagerInterface;
/**
* Class Authenticate AD Login
* @package Adp\Admin\Login
*/
class LdapAuthentication
{
/**
* @var string host
*/
private $ldapHost;
/**
* @var string Admin
*/
private $ldapDomain;
/**
* @var string DN
*/
private $baseDn;
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function validateUser($user, $password)
{
$ad = ldap_connect("$this->ldapHost") or die('Could not connect to LDAP server.');
ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ad, LDAP_OPT_REFERRALS, 0);
if (!ldap_bind($ad, "{$user}@{$this->ldapDomain}", $password)) {
return false;
}
$userDn = $this->getDN($ad, $user, $this->baseDn);
return $this->checkGroupEx($ad, $userDn, $groups);
ldap_unbind($ad);
}
/**
* @param $ad
* @param $samAccountName
* @param $baseDn
* @return string
*/
private function getDN($ad, $samAccountName, $baseDn)
{
$attributes = array('dn');
$result = ldap_search(
$ad,
$baseDn,
"(samaccountname={$samAccountName})",
$attributes
);
if ($result === false) {
return '';
}
$entries = ldap_get_entries($ad, $result);
if ($entries['count'] > 0) {
return $entries[0]['dn'];
}
return '';
}
/**
* @param $ad
* @param $userDn
* @param $groups
* @param $roles
* @return bool
*/
private function checkGroupEx($ad, $userDn, $groups)
{
$attributes = array('cn','memberof', 'mail');
$result = ldap_read($ad, $userDn, '(objectclass=*)', $attributes);
if ($result === false) {
return false;
};
$entries = ldap_get_entries($ad, $result);
$response = array();
$name = explode(',', $entries[0]['cn'][0]);
$response['firstName'] = $name[0];
$response['lastName'] = $name[1];
$response['email'] = $entries[0]['mail'][0];
if ($entries['count'] <= 0) {
return false;
};
if (empty($entries[0]['memberof'])) {
return false;
}
for ($i = 0; $i < $entries[0]['memberof']['count']; $i++) {
$groupName = explode(',', $entries[0]['memberof'][$i]);
$pos = array_search(strtolower(substr($groupName[0], 3)), $groups);
if (!empty($pos)) {
return $response;
}
}
return false;
}
/**
* @return string
*/
public function getBaseDn()
{
return $this->baseDn;
}
/**
* @param string $baseDn
*/
public function setBaseDn($baseDn)
{
$this->baseDn = $baseDn;
}
/**
* @return string
*/
public function getLdapDomain()
{
return $this->ldapDomain;
}
/**
* @param string $ldapDomain
*/
public function setLdapDomain($ldapDomain)
{
$this->ldapDomain = $ldapDomain;
}
/**
* @return string
*/
public function getLdapHost()
{
return $this->ldapHost;
}
/**
* @param string $ldapHost
*/
public function setLdapHost($ldapHost)
{
$this->ldapHost = $ldapHost;
}
看一看-这个包允许您重写PHP中的本机函数
下面的使用示例(请参阅Github上的文档,因为PHP5和PHP7之间有变化)
uopz-2.0.x
uopz-5.0.x
注意:请注意要安装的版本=5.x
支持PHP7.x和,通常不需要模拟这些函数。模拟这些实际上意味着您将模拟可能很麻烦的服务器行为。您的实现中可能存在与特定ldap服务器(设置)不兼容的缺陷-或者-您在使用LdapAuthentication类时存在缺陷
your app <---> LdapAuthentication <---> ldap server
现在,由于不能让接口和类共享相同的名称,因此可以将ldapaauthentication类重命名为实现接口的不同名称:
之前:
class LdapAuthentication
{
...
之后:
class FooLdapAuthentication implements LdapAuthentication
{
...
(请注意,该名称只是示例性的,您应该能够找到更合适的名称)
这样做的主要好处是,您现在可以针对接口而不是具体类型进行编程。如果您像在当前代码中命名类那样命名接口(也就是说您之前如何命名它),那么您现有的代码将自动从针对具体类型的编程更改为针对接口的编程。例如,类型提示现在接受任何实现
这允许您交换实现(也就是说,您可以在子类中进行更改,或者甚至在新类中重新编写实现,例如使用不同ldap库的实现,或者用于不同类型的ldap服务器的实现,而不必中断其余代码)
例如,一个这样的新实现将成为ldapaauthenticationmock:
class LdapAuthenticationMock implements LdapAuthentication
{
...
然后,您可以在测试中传递它来测试您的应用程序代码,甚至不需要ldap服务器或ldap PHP扩展。感谢这个接口,如果您还没有完全实现它,PHP会通知您
在模拟测试(它比您写下您期望该类如何工作更有效,测试通常是以接近代码的方式写下规范)的旁边,您还需要对Ldap(测试)服务器的具体实现进行集成测试,doubdapAuthenticationTest
编写这些测试将帮助您在运行整个应用程序的独立测试用例中编写Ldap身份验证。然后,由于针对接口编程,可以编写应用程序,而不再关心身份验证或任何其他LDA身份验证的实现细节
因此,通过接口测试,您可以测试应用程序类或服务器,并且可以在它们所属的位置进行更改(或者在不需要更改整个应用程序的情况下处理服务器接口的更改)
使用mock-as-concrete-PHP类还有一个好处,即您不需要一次又一次地密集地设置它,以便进行其他需要与ldapaauthentication协作的测试,您只需注入该mock即可
如果您正确设置自动加载器,这是相当直接的使用。无需对内部函数进行繁琐的模拟,易于编写需要ldapaauthentication才能使用的单元测试,通过这种方式可以对服务器接口进行广泛的集成测试
这是克服集成测试产生的问题的一种方法:
- 覆盖率:集成测试通常缺乏滞后性(通过PHP接口覆盖,任何实现都需要100%实现接口,否则无法启动)
- 复杂性:集成测试没有显示错误的确切原因(您注意到应用程序代码中的某个服务器问题,而应用程序代码只是希望您的实现完成工作,而不是出错)
在更高级的测试设置中,网络协议级别上的整个服务器接口也将被抽象,以便您可以测试一个具体的服务器,它将在输入和输出两个方面匹配预期的接口。客户端也一样,同样是输入和输出。然而,这肯定会留下PHP的作用域。根据我的建议,您至少可以通过在应用程序与服务器接口之间引入一个PHP接口来将应用程序与服务器接口分开。您仍然需要对一个具体的(测试)服务器进行集成测试,这样就可以减少总体集成测试问题,并将其限制在一个(更具体的)集成测试用例中。由于您经常依赖于或多或少的具体服务器,我认为在您发现进一步(回归)测试需求之前,这个快捷方式是可以的
interface LdapAuthentication
{
public function validateUser($user, $password);
...
class LdapAuthentication
{
...
class FooLdapAuthentication implements LdapAuthentication
{
...
class LdapAuthenticationMock implements LdapAuthentication
{
...
FooLdapAuthenticationTest.php - test of the server interface
LdapAuthenticationTest.php - test of the PHP interface
LdapAuthenticationMock.php - mock object