Phpunit,如何测试方法是否“什么都不做”


Phpunit, how to test if method does "nothing"?

class Testme()
{
    public function testMe ($a)
    {
        if ($a == 1)
        {
            throw new Exception ('YAY');
        }
    }
}

所以如果它抛出异常很容易测试

/**
 * @expectedException Exception
 */
public function test()
{
    new Testme(1);
}

但是,如果它什么也没做呢

public function test()
{
    new Testme(2);
 ?? ? ? ? ?
}

场景

函数不执行任何操作有两种可能的情况:

场景 1:无返回语句

您的函数不执行任何操作,因为您不在其中执行操作,并且您不包含 return 关键字:

public function doNothing()
{
    // Do nothing.
}

场景 2:使用 return 语句

您的函数不执行任何操作,因为您不在其中执行操作,并且在其中包含 return 关键字而不表示任何返回值:

public function doNothing()
{
    // Do nothing.
    return;
}

其他方案

我将省略这些情况来处理以下情况:

  1. 您不返回任何内容,但执行可在其他对象上测试的重要操作的情况。在这种情况下,必须对已修改对象的结果状态进行单元测试。

  2. 如果您只返回某些内容,则应该对返回值进行单元测试。

浏览 PHP 手册中的文档

对于第一种情况,PHP 手册记录了函数的计算表达式将被null。它在这里说: http://php.net/manual/en/functions.returning-values.php 在注释中

如果省略返回值,则将返回值 NULL。

对于第二种情况,PHP 手册记录了函数的计算表达式也将null 。它在这里说: http://php.net/manual/en/function.return.php 在注释中

如果未提供参数,则必须省略括号,并返回 NULL。[...]

结论

因此,清楚地记录了"什么都不做"的函数必然计算为null

如何测试不执行任何操作的函数

只要坚持你的期望:

$this->assertNull( $sut->doNothing() );
通过这种方式,你">

执行"你的函数,你运行它,使代码覆盖率完成所有行,你通过测试其计算值作为表达式的null值来"期望"什么也没发生",如文档所示。

如何测试不执行任何操作的构造函数

尽管如此,要测试构造函数...井。。。常识:构造函数的目的是什么?创建某种类型(类(的对象(实例(,对吗?

所以。。。我更喜欢通过检查是否已创建$sut来启动 100% 的单元测试。这是我在编写新类的代码时编写的第一个测试。这是我甚至在类存在之前编写的测试。最后,这就是构造函数的用途。红色条。然后我创建类。绿色条。

假设我有一个接受字符串的Email类,并且只有在传递有效电子邮件时才会创建,否则会引发异常。 这与您的问题非常相似。一个构造函数,它只是"允许创建"或"通过爆炸系统来拒绝它"。

我通常会做这样的事情:

//-------------------------------------------------//
// Tests                                           //
//-------------------------------------------------//
/** @dataProvider validEmailProvider **/
public function testCreationIsOfProperClass( string $email )
{
    $sut = $this->getSut( $validEmail );
    $this->assertInstanceOf( Email::class, $sut );
}
/** @dataProvider invalidEmailProvider **/
public function testCreationThrowsExceptionIfEmailIsInvalid( string $invalidEmail )
{
    $this->expectException( EmailException::class );
    $this->getSut( $invalidEmail );
}
//-------------------------------------------------//
// Data providers                                  //
//-------------------------------------------------//
public function validEmailProvider() : array
{
    return
    [
        [ 'alice@example.com' ],
        [ 'bob.with-several+symbols@subdomain.another.subdomain.example.verylongTLD' ],
    ]
}
public function invalidEmailProvider() : array
{
    return
    [
        [ 'missing_at_symbol' ],
        [ 'charlie@cannotBeOnlyTld' ],
    ]
}
//-------------------------------------------------//
// Sut creators                                    //
//-------------------------------------------------//
private function getSut( string $email ) : Email
{
    return new Email( $email );
}

由于我使用 PHP 7.0 并且我在任何地方都放置了类型,无论是输入参数还是返回类型,如果创建的对象不是电子邮件,getSut(( 函数将首先失败。

但即使我写它省略了返回类型,测试也会测试它应该发生的情况:new Email( 'valid@example.com' );本身就是一个表达式,它应该计算为类 Email::class 的"某物"。

如何测试执行某些操作的构造函数

代码异味。构造函数可能不应该工作。如果有,只需存储参数。如果构造函数"确实工作"而不是存储参数,请考虑在 getter 上进行延迟处理,或者在工厂左右委派该工作。

如何测试"只存储参数"的构造函数

就像之前+然后获取数据一样。

  1. 在第一次测试中测试创建是否是某物的实例。
  2. 然后在另一个不同的测试中,练习类似 getter 的东西,即使构造函数没有做任何事情(除了存储它(,它也会让你在构造函数中输入什么。

希望这有帮助。

在 PHPUnit 7.2+ 中,你也可以使用 TestCase::expectNotToPerformAssertions()

public function test()
{
    // ...
    $this->expectNotToPerformAssertions();
}

这与@doesNotPerformAssertions批注具有相同的行为。

> 2018+

如今,最佳实践是完全针对这些情况进行注释:

/**
 * @doesNotPerformAssertions
 */
public function testSomething()
{
    $someService = new SomeObject();
    $someService->shallNotFail();
}
  • 拉取请求示例
  • PHPUnit 文档(差(

是不可能的。添加return语句并断言结果。

class Testme()
{
    public function testMe ($a)
    {
        if ($a == 1)
        {
            throw new Exception ('YAY');
        }
        return true;
    }
}

然后

$object = new Testme();
$this->assertTrue($object->testMe(2));

注意:此解决方案的学分将转到此相关答案。上下文可能看起来有点不同,但解决方案/解决方法的工作方式相同。测试是否引发异常与测试没有返回值的方法相同。

根据此问题线程,还没有内置的解决方案来测试 PHPUnit 中的 DoesNotThrowException 之类的东西(目前(。

所以是的,一种解决方案是从您的方法中返回一些虚拟值,例如

public function testMe ($a)
{
    if ($a == 1) { throw new Exception ('YAY'); }
    return true;
}

然后在测试中断言它。但是,如果您不想仅更改测试的代码,则可以解决此问题:

public function testExceptionIsNotThrown()
{
    try {
        new Testme(2);
    }
    catch(Exception $e) {
        /* An exception was thrown unexpectedly, so fail the test */
        $this->fail();
    }
    /* No exception was thrown, so just make a dummy assertion to pass the test */
    $this->assertTrue(true);
}

它可能看起来很笨拙,不是很直观,但如果它很愚蠢,但它有效,它并不愚蠢。

public function testThrowingException()
{
    $this->expectException(Exception::class);
    $this->expectExceptionMessage('YAY');
    (new Testme())->testMe(1);
}
public function testNotThrowingException()
{
    $this->expectNotToPerformAssertions();
    (new Testme())->testMe(2);
}

这是一个非常有趣的问题,虽然写了很多答案,但似乎没有一个正确回答了这个问题,因为你已经用类问了,让我解释一下。

请记住,您在类中创建的实例方法应该只有 2 个意图。

  1. 它可以改变类的状态(像私有变量一样改变类的属性(
  2. 它返回类的状态(getters(

除非它是静态方法,否则任何其他事物都是没有意义的。 例如如果你有这样的课程

class Foo {
   private $prop = null;
   public function fooMethod() {
      $this->prop = "string";
   }
   public function getProp() {
     return $this->prop;
   }
}

方法 fooMethod(( 不返回任何内容,但它会影响类中$prop属性的状态,您可以通过以下方式测试该方法

$this->assertNotNull( $instance->getProp() );

因为您知道如果运行此方法,那么 prop $prop 应该会受到影响,并且该变量的状态会更改。

杂项方案:我的方法不会更改状态,也不会返回任何状态变量。

然后该方法static.它不应该是一个实例方法,静态方法通常具有返回类型,因为它们不能影响类的状态,也不能返回状态变量。这限制了静态方法将结果存储在某处(除非您将它们存储为全局变量,否则不要这样做(,因此它肯定会返回一些输出。如果您不想返回输出,则可以考虑从静态方法返回布尔值。

我偶然发现了同样的问题。为了确保"什么都没有"发生,只需在单元测试中调用该方法就足够了。如果失败,测试无论如何都会失败。

如果你只是调用你的方法而不使用这样的@expectedException注释

public function test()
{
    new Testme(1);
}

您会收到错误

There was 1 error:
1) Testme::testMe
Exception: YAY