IT Share you

PHPUnit에서 시간을 "모의"할 수 있습니까?

shareyou 2020. 11. 8. 11:31
반응형

PHPUnit에서 시간을 "모의"할 수 있습니까?


... 'mock'이 올바른 단어인지 알지 못합니다.

어쨌든, 시간 기반 테스트를 작성하려는 상속 된 코드 기반이 있습니다. 너무 모호 하지 않기 위해 코드는 항목의 기록을보고 해당 항목이 현재 시간 임계 값을 기반으로하는지 확인하는 것과 관련이 있습니다.

어떤 시점에서 나는 또한 그 기록에 무언가를 추가하고 임계 값이 이제 변경되었는지 (그리고 분명히 올바른지) 테스트해야합니다.

내가 치는 문제는 내가 테스트하는 코드의 일부가 time () 호출을 사용한다는 것이므로 임계 시간이 정확히 무엇인지 알기가 정말 어렵다는 사실을 바탕으로 그 time () 함수가 언제 호출 될지 정확히 확실하지 않습니다.

그래서 내 질문은 기본적으로 이것입니다. time () 호출을 '재정의'하거나 어떻게 든 시간을 '조롱'하여 테스트가 '알려진 시간'에 작동하도록 할 수있는 방법이 있습니까?

아니면 테스트중인 코드에서 필요한 경우 특정 시간을 사용하도록 강제로 허용하기 위해 코드에서 무언가를해야한다는 사실을 받아 들여야합니까?

어느 쪽이든 테스트 친화적 인 시간에 민감한 기능을 개발하기위한 '일반적인 관행'이 있습니까?

편집 : 내 문제의 일부도 역사에서 일어난 시간이 임계 값에 영향을 미친다는 사실입니다. 여기 내 문제의 일부 예가 있습니다.

당신이 바나나를 가지고 있고 그것을 먹어야 할 때 운동하려고한다고 상상해보십시오. 그것은 우리가 만료 4 일 추가하는 경우 일부 화학 물질로 분무하지 않는 한, 3 일 이내에 만료됩니다하자 말 스프레이가 적용된 시간부터 . 그 다음에는 냉동하여 3 개월을 더 추가 할 수 있지만 냉동 상태라면 해동 후 1 일 밖에 사용할 수 없습니다.

이러한 모든 규칙은 역사적시기에 따라 결정됩니다. 몇 초 내에 Dominik의 테스트 제안을 사용할 수 있다는 데 동의하지만 내 과거 데이터는 어떻습니까? 즉석에서 '생성'해야합니까?

당신이 말할 수 있거나 말할 수 없을지도 모르지만, 나는 여전히이 모든 '테스트'개념을 이해하려고 노력하고 있습니다.)


최근에 PHP 5.3 네임 스페이스를 사용하는 경우 유용한 또 다른 솔루션을 찾았습니다. 현재 네임 스페이스 내에 새로운 time () 함수를 구현하고 테스트에서 반환 값을 설정하는 공유 리소스를 만들 수 있습니다. 그런 다음 time ()에 대한 정규화되지 않은 호출은 새 함수를 사용합니다.

자세한 내용은 블로그 에 자세히 설명했습니다.


면책 조항 :이 라이브러리를 작성했습니다.

ouzo-goodies의 Clock사용하여 테스트 시간을 모의 할 수 있습니다 .

코드에서 간단히 사용 :

$time = Clock::now();

그런 다음 테스트에서 :

Clock::freeze('2014-01-07 12:34');
$result = Class::getCurrDate();
$this->assertEquals('2014-01-07', $result);

심포니 (> = 2.8) 작업 당신의 사람들을 위해 : 심포니의 phpunit을 브릿지는 내장 된 방법을 재정의하는 ClockMock 기능을 포함 time, microtime, sleepusleep.

참조 : http://symfony.com/doc/2.8/components/phpunit_bridge.html#clock-mocking


개인적으로 테스트 된 함수 / 메서드에서 time ()을 계속 사용합니다. 테스트 코드에서 time ()으로 동등성을 테스트하지 말고 단순히 1 또는 2 미만의 시간 차이 (함수가 실행되는 데 걸리는 시간에 따라 다름)에 대해 테스트하십시오.


단위 테스트가 아닌 앱 자체에서 미래 및 과거 날짜의 특정 요청을 시뮬레이션해야했습니다. 따라서 \ DateTime :: now ()에 대한 모든 호출은 앱 전체에서 이전에 설정 한 날짜를 반환해야합니다.

모든 코드를 변경하지 않고 날짜를 조롱 할 수 있기 때문에 https://github.com/rezzza/TimeTraveler 라이브러리를 사용하기로 결정했습니다 .

\Rezzza\TimeTraveler::enable();
\Rezzza\TimeTraveler::moveTo('2011-06-10 11:00:00');

var_dump(new \DateTime());           // 2011-06-10 11:00:00
var_dump(new \DateTime('+2 hours')); // 2011-06-10 13:00:00

런킷 확장을 사용하여 PHP의 time () 함수를 덮어 쓸 수 있습니다. runkit.internal_overide를 On으로 설정했는지 확인하십시오.


[runkit] [1] 확장 사용 :

define('MOCK_DATE', '2014-01-08');
define('MOCK_TIME', '17:30:00');
define('MOCK_DATETIME', MOCK_DATE.' '.MOCK_TIME);

private function mockDate()
{
    runkit_function_rename('date', 'date_real');
    runkit_function_add('date','$format="Y-m-d H:i:s", $timestamp=NULL', '$ts = $timestamp ? $timestamp : strtotime(MOCK_DATETIME); return date_real($format, $ts);');
}


private function unmockDate()
{
    runkit_function_remove('date');
    runkit_function_rename('date_real', 'date');
}

다음과 같이 mock을 테스트 할 수도 있습니다.

public function testMockDate()
{
    $this->mockDate();
    $this->assertEquals(MOCK_DATE, date('Y-m-d'));
    $this->assertEquals(MOCK_TIME, date('H:i:s'));
    $this->assertEquals(MOCK_DATETIME, date());
    $this->unmockDate();
}

대부분의 경우 이렇게됩니다. 몇 가지 장점이 있습니다.

  • 당신은 아무것도 조롱 할 필요가 없습니다
  • 외부 플러그인이 필요하지 않습니다
  • time ()뿐만 아니라 DateTime 객체도 모든 시간 함수를 사용할 수 있습니다.
  • 네임 스페이스를 사용할 필요가 없습니다.

phpunit을 사용하고 있지만 다른 테스트 프레임 워크에 추가 할 수 있습니다. phpunit의 assertContains ()처럼 작동하는 함수 만 있으면됩니다.

1) 테스트 클래스 또는 부트 스트랩에 아래 기능을 추가하십시오. 시간에 대한 기본 허용 오차는 2 초입니다. 세 번째 인수를 assertTimeEquals에 전달하거나 함수 인수를 수정하여 변경할 수 있습니다.

private function assertTimeEquals($testedTime, $shouldBeTime, $timeTolerance = 2)
{
    $toleranceRange = range($shouldBeTime, $shouldBeTime+$timeTolerance);
    return $this->assertContains($testedTime, $toleranceRange);
}

2) 테스트 예 :

public function testGetLastLogDateInSecondsAgo()
{
    // given
    $date = new DateTime();
    $date->modify('-189 seconds');

    // when
    $this->setLastLogDate($date);

    // then
    $this->assertTimeEquals(189, $this->userData->getLastLogDateInSecondsAgo());
}

assertTimeEquals ()는 (189, 190, 191) 배열에 189가 포함되어 있는지 확인합니다.

이 테스트는 테스트 기능을 실행하는 데 2 ​​초 미만이 소요되는 경우 올바른 작동 기능을 위해 통과해야합니다.

It's not perfect and super-accurate, but it's very simple and in many cases it's enough to test what you want to test.


Simplest solution would be to override PHP time() function and replace it with your own version. However, you cannot replace built-in PHP functions easily (see here).

Short of that, the only way is to abstract time() call to some class/function of your own that would return the time you need for testing.

Alternatively, you could run the test system (operating system) in a virtual machine and change the time of the entire virtual computer.


Here's an addition to fab's post. I did the namespace based override using an eval. This way, I can just run it for tests and not the rest of my code. I run a function similar to:

function timeOverrides($namespaces = array()) {
  $returnTime = time();
  foreach ($namespaces as $namespace) {
    eval("namespace $namespace; function time() { return $returnTime; }");
  }
}

then pass in timeOverrides(array(...)) in the test setup so that my tests only have to keep track of what namespaces time() is called in.

참고URL : https://stackoverflow.com/questions/2371854/can-i-mock-time-in-phpunit

반응형