我正在寻找测试以下静态方法的最佳方法(特别是使用Doctrine模型):
class Model_User extends Doctrine_Record
{
public static function create($userData)
{
$newUser = new self();
$newUser->fromArray($userData);
$newUser->save();
}
}
理想情况下,我会使用模拟对象来确保调用“fromArray”(使用提供的用户数据)和“save”,但这是不可能的,因为该方法是静态的。
有什么建议吗?
答案 0 :(得分:44)
PHPUnit的作者Sebastian Bergmann最近发表了一篇关于Stubbing and Mocking Static Methods的博客文章。使用PHPUnit 3.5和PHP 5.3以及后期静态绑定的一致使用,你可以做到
$class::staticExpects($this->any())
->method('helper')
->will($this->returnValue('bar'));
更新: staticExpects
为deprecated as of PHPUnit 3.8,将在以后的版本中完全删除。
答案 1 :(得分:11)
现在有AspectMock库来帮助解决这个问题:
https://github.com/Codeception/AspectMock
$this->assertEquals('users', UserModel::tableName());
$userModel = test::double('UserModel', ['tableName' => 'my_users']);
$this->assertEquals('my_users', UserModel::tableName());
$userModel->verifyInvoked('tableName');
答案 2 :(得分:1)
我会在单元测试命名空间中创建一个新类,它扩展了Model_User并对其进行测试。这是一个例子:
原班级:
class Model_User extends Doctrine_Record
{
public static function create($userData)
{
$newUser = new self();
$newUser->fromArray($userData);
$newUser->save();
}
}
要在单元测试中调用的模拟类:
use \Model_User
class Mock_Model_User extends Model_User
{
/** \PHPUnit\Framework\TestCase */
public static $test;
// This class inherits all the original classes functions.
// However, you can override the methods and use the $test property
// to perform some assertions.
}
在你的单元测试中:
use Module_User;
use PHPUnit\Framework\TestCase;
class Model_UserTest extends TestCase
{
function testCanInitialize()
{
$userDataFixture = []; // Made an assumption user data would be an array.
$sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing.
$sut::test = $this; // This is just here to show possibilities.
$this->assertInstanceOf(Model_User::class, $sut);
}
}
答案 3 :(得分:0)
测试静态方法通常被认为是有点难的(正如您可能已经注意到的),尤其是在PHP 5.3之前。
您是否可以修改代码以不使用静态方法?事实上,我真的不明白为什么你在这里使用静态方法;这可能会被重写为一些非静态代码,不是吗?
例如,像这样的事情可能不起作用:
class Model_User extends Doctrine_Record
{
public function saveFromArray($userData)
{
$this->fromArray($userData);
$this->save();
}
}
不确定你要测试什么;但是,至少,没有静态方法......
答案 4 :(得分:0)
另一种可能的方法是使用Moka库:
$modelClass = Moka::mockClass('Model_User', [
'fromArray' => null,
'save' => null
]);
$modelClass::create('DATA');
$this->assertEquals(['DATA'], $modelClass::$moka->report('fromArray')[0]);
$this->assertEquals(1, sizeof($modelClass::$moka->report('save')));
答案 5 :(得分:0)
doublit库也可以帮助您测试静态方法:
/* Create a mock instance of your class */
$double = Doublit::mock_instance(Model_User::class);
/* Test the "create" method */
$double::_method('create')
->count(1) // test that the method is called once
->args([Constraints::isInstanceOf('array')]) // test that first argument is an array
->stub('my_value') // stub the method to return "myvalue"
答案 6 :(得分:0)
再approach:
class Experiment
{
public static function getVariant($userId, $experimentName)
{
$experiment = self::loadExperimentJson($experimentName):
return $userId % 10 > 5; // some sort of bucketing
}
protected static function loadExperimentJson($experimentName)
{
// ... do something
}
}
在我的ExperimentTest.php
class ExperimentTest extends \Experiment
{
public static function loadExperimentJson($experimentName)
{
return "{
"name": "TestExperiment",
"variants": ["a", "b"],
... etc
}"
}
}
然后我会像这样使用它:
public function test_Experiment_getVariantForExperiment()
{
$variant = ExperimentTest::getVariant(123, 'blah');
$this->assertEquals($variant, 'a');
$variant = ExperimentTest::getVariant(124, 'blah');
$this->assertEquals($variant, 'b');
}
答案 7 :(得分:0)
找到了有效的解决方案,尽管主题很旧,但还是愿意分享。
class_alias
可以替换尚未自动加载的类(仅当您使用自动加载时才有效,而不是直接包含/要求文件)。
例如,我们的代码:
class MyClass
{
public function someAction() {
StaticHelper::staticAction();
}
}
我们的测试:
class MyClassTest
{
public function __construct() {
// works only if StaticHelper is not autoloaded yet!
class_alias(StaticHelperMock::class, StaticHelper::class);
}
public function test_some_action() {
$sut = new MyClass();
$myClass->someAction();
}
}
我们的模拟:
class StaticHelperMock
{
public static function staticAction() {
// here implement the mock logic, e.g return some pre-defined value, etc
}
}
这个简单的解决方案不需要任何特殊的库或扩展。