强制PHPUnit测试抛出异常

时间:2013-04-30 17:08:28

标签: unit-testing phpunit

我对一般的单元测试和特别是PHPUnit都很陌生,所以如果这是一个简单的问题,请原谅我。我做了一些谷歌搜索,但我不知道要知道要搜索什么。

所以我有一个功能:

function convert_timezone($dt, $tzFrom, $tzTo, $format) {
    $newDate = '';

    $tzFromFull = timezone_name_from_abbr($tzFrom);
    $tzToFull = timezone_name_from_abbr($tzTo);

    if( $tzFromFull != $tzToFull ) {
        $dtFrom = new DateTimeZone($tzFromFull);
        $dtTo = new DateTimeZone($tzToFull);

        try {
            // find the offsets from GMT for the 2 timezones
            $current = new DateTime(date('c',$dt));
            $offset1 = $dtFrom->getOffset($current);
            $offset2 = $dtTo->getOffset($current);
            $offset = $offset2 - $offset1;

            // apply the offset difference to the current time
            $newDate = date($format, $current->format('U') + $offset);
        } catch (Exception $e) {
            $newDate = date($format.' (T)', $dt);
        }
    } else {
        $newDate = date($format, $dt);
    }

    return $newDate;
}

这是测试功能:

function test_convert_timezone() {
   $dt = mktime(0,0,0,1,1,2000);
   $tzFrom = 'CDT';
   $tzTo = 'EDT';
   $format = 'd/m/Y g:i a';
   $result = convert_timezone($dt, $tzFrom, $tzTo, $format);
   $this->assertEquals($result, '01/01/2000 1:00 am');
}

当我直接在Netbeans中运行测试时,测试通过。但是当我使用命令行选项生成代码覆盖率报告时,它告诉我这个功能没有被完全覆盖,因为测试没有达到异常。

我知道如果我可以改变原来的功能,我可以强迫它抛出一个错误。但我无法做到这一点。我需要一种方法,在测试函数中传递一些东西,这将使被测试的函数达到其异常。我从哪里开始?

1 个答案:

答案 0 :(得分:1)

你几乎无法像代码那样测试那个分支。

你的try-catch块实际上并没有捕获任何异常。 (date会抛出一个未捕获的错误。getOffset也没有。如果传入的时区常量不合适DateTimeZone::__construct(),可能会讽刺new DateTimeZone。因此,如果您可以重构它并传入未列为有效的时区,您可以获得异常(您应该看到这是因为您希望在此处捕获异常)。

function convert_timezone($dt, $tzFrom, $tzTo, $format) {
    $newDate = '';

    $tzFromFull = timezone_name_from_abbr($tzFrom);
    $tzToFull = timezone_name_from_abbr($tzTo);

    if( $tzFromFull != $tzToFull ) {
        try {
            $dtFrom = new DateTimeZone($tzFromFull);
            $dtTo = new DateTimeZone($tzToFull);


            // find the offsets from GMT for the 2 timezones
            $current = new DateTime(date('c',$dt));
            $offset1 = $dtFrom->getOffset($current);
            $offset2 = $dtTo->getOffset($current);
            $offset = $offset2 - $offset1;

            // apply the offset difference to the current time
            $newDate = date($format, $current->format('U') + $offset);
        } catch (Exception $e) {
            $newDate = date($format.' (T)', $dt);
        }
    } else {
        $newDate = date($format, $dt);
    }

    return $newDate;
}

然后测试

function test_convert_timezone() {
   $dt = mktime(0,0,0,1,1,2000);
   $tzFrom = 'foo';
   $tzTo = 'EDT';
   $format = 'd/m/Y g:i a';
   $result = convert_timezone($dt, $tzFrom, $tzTo, $format);
   $this->assertEquals($result, '01/01/2000 1:00 am');
}

在此测试中,timezone_name_from_abbr将返回false,这将导致DateTimeZone构造函数抛出异常到达您的分支。

IMO,您希望避免构建要在函数中使用的对象。对于此代码,我将传递两个DateTimeZone对象以及DateTime对象并对它们执行操作。将这些其他对象的创建移动到不同的函数/类中。这样,您可以模拟DateTimeZone和DateTime对象本身,并可以更好地控制发生的事情。这段代码不是太糟糕,但是在尝试测试其他东西时可能会造成麻烦。

另外,请注意在代码中捕获泛型异常。如果在try-catch块中使用模拟对象,则代码可以捕获FailedTestException(在未使用正确的参数或类似函数调用模拟时抛出),这将由您的代码捕获。让它看起来像你的测试实际上没有通过。由于代码和缺少模拟,这里不再是问题,但我想让你意识到它。