我有以下类连接到ldap服务器以检查用户是否属于某个组。我想为这堂课写单元测试。我该如何实现这一目标。如何模拟ldap php本机函数。有人可以帮我一些示例代码。
<?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;
}
答案 0 :(得分:2)
看一下uopz - 这个包允许你覆盖PHP中的本机函数。
以下用法示例(请参阅Github上的文档,因为PHP 5和7之间存在更改)。
// backup the original function
uopz_backup('ldap_connect');
// override and perform your testing
uopz_function('ldap_connect', function() {
// override here
});
// testing...
// once finished
uopz_restore('ldap_connect');
// override and perform your testing
uopz_set_return('ldap_connect', function() {
// override here
});
// testing...
// reset return value
uopz_unset_return('ldap_connect');
注意:请注意您要安装的版本。 >=5.x
支持PHP 7.x,<=2.0.x
支持PHP 5.3.x
答案 1 :(得分:2)
您通常不需要模拟这些功能。嘲笑这些实际上意味着你会模仿服务器行为,这可能很麻烦。您的实现中可能存在一个缺陷,该缺陷不适用于特定的ldap服务器(设置) - 或者您在使用 LdapAuthentication 类时存在缺陷。
your app <---> LdapAuthentication <---> ldap server
这也是因为 LdapAuthentication 是PHP Ldap扩展的包装器 - 这是一个很好的做法,因为您可以保护应用程序的其余部分远离具体库,以便您可以随时更改它(例如,处理设置和随时间变化的一些差异。)
处理此问题的一个策略是界面测试。 LdapAuthentication 具有您在应用程序中使用的公共接口。要测试您的应用程序是否与该类型正确接口,请模拟该类本身。这将涵盖它的公共界面。
另一方面,您想测试身份验证器是否与ldap服务器实例一起使用。这需要一个测试套件,公共接口所代表的所有功能都可以在具体的服务器上使用。这有效地测试了服务器接口。在Phpunit中,这可能是两个测试用例。一个用于公共类接口,另一个用于测试(test-)ldap-server配置的集成。
要测试的两个接口:
application <---> <<ldap class interface>>
ldap class <---> <<ldap server interface>>
从您的应用程序的角度来看,测试应用程序不需要模拟PHP内部函数(通过PHP Ldap扩展)。
要测试Ldap服务器接口,你不也需要模拟这些函数,因为在这种情况下,你实际上想要测试事情是否真实,而不是模拟。因此,通过该界面测试,您甚至不想模拟这些内部函数,您实际上想要使用它们。
由于模拟表示服务器交互的类可能很复杂,并且为此类交互密集型类型动态设置模拟对象可能很麻烦,您应该考虑在纯PHP中编写 LdapAuthentication 的模拟对象编码您期望该类用示例数据覆盖的简要功能。
也就是说,您编写 LdapAuthentication 的第二个实现,让它命名为 LdapAuthenticationMock ,其行为与您期望的一样。通常将它放在单元测试旁边,与 LdapAuthenticationTest (单元测试用例)在同一目录中。
但是在你创建这样一个满足 LdapAuthentication 公共接口的模拟之前,首先需要进行一些重构工作。其中很重要的是,您的整体应用程序将从中受益,而不仅仅是测试。
额外的工作是从 LdapAuthentication 中提取界面。提取接口意味着您创建一个接口(请参阅PHP接口),其名称包含您在应用程序中使用的公共方法:
interface LdapAuthentication
{
public function validateUser($user, $password);
...
现在,您可以将接口和类共享相同的名称,然后将 LdapAuthentication 类重命名为实现接口的其他名称:
在:
class LdapAuthentication
{
...
后:
class FooLdapAuthentication implements LdapAuthentication
{
...
(请注意,名称只是示例,您应该能够找到更合适的名称)
这样做的主要好处是您现在能够针对界面而不是具体类型进行编程。如果您将接口命名为您在当前代码中命名的类(以便说明您之前的命名方式),那么现有代码会自动从针对具体类型的编程更改为针对接口编程。例如,类型提示现在接受任何实现。
这允许你交换实现(也就是说,你可以在子类中进行更改或者甚至在新类中重写一个实现,例如使用不同的ldap库或者用于不同类型的ldap服务器的实现没有破坏你的其余代码。)
例如,一个这样的新实现将成为 LdapAuthenticationMock :
class LdapAuthenticationMock implements LdapAuthentication
{
...
然后你可以在测试中传递它来测试你的应用程序代码,甚至不需要ldap服务器甚至那个 ldap PHP扩展。感谢界面,如果您没有完整实现,PHP将通知您。
除了mock的测试(更多的是你写下你希望该类如何工作,通常测试是以代码接近的方式写下规范),你还需要对你的具体实现进行集成测试反对Ldap(测试 - )服务器, FooLdapAuthenticationTest 。
编写这些测试将帮助您在运行整个应用程序的独立测试用例中编写Ldap身份验证。然后,应用程序可以编写针对接口的编程,无需再关注 FooLdapAuthentication 或任何其他 LdapAuthentication 的实现细节。
FooLdapAuthenticationTest.php - test of the server interface
LdapAuthenticationTest.php - test of the PHP interface
LdapAuthenticationMock.php - mock object
因此,通过接口测试,您可以测试您的应用程序类 - 或者 - 服务器,您可以在它们所属的位置进行更改(或处理服务器接口中的更改,而无需更改整个应用程序)
将模拟作为具体的PHP类也有一个好处,即您不需要一次又一次地为需要与 LdapAuthentication 协作的其他测试进行密集设置,您只需注入该模拟。
如果正确设置自动装带器,则可以直接使用。不需要繁琐的内部函数模拟,易于编写需要 LdapAuthentication 进行单元测试的单元测试以及服务器接口的广泛集成测试。
这是克服集成测试创建的问题的一种方法:
覆盖范围:集成测试经常缺乏(通过PHP界面覆盖,任何实现需要100%完成界面,否则它不会开始)
复杂性:集成测试没有显示错误的确切原因(您发现应用程序代码中某处存在服务器问题,而应用程序代码只是希望您的实现执行工作而不是错误)
在更高级的测试设置中,网络协议级别上的整个服务器接口也将被抽象化,以便您可以测试具体的服务器,它将以预先接入的方式匹配预期的接口。同样适用于客户端,再次进出。但是,这肯定会留下PHP的范围。根据我的建议,您至少通过在其间引入PHP接口将您的应用程序与服务器接口分开。您仍然可以对具体(Test-)服务器进行集成测试,以便减少整体集成测试问题并限制为(更具体的)集成测试用例。由于您经常依赖于或多或少的具体服务器,我认为在您发现进一步(回归)测试需求之前,此快捷方式是可以的。