至少在具体值上模拟一次,其余的都不重要 [英] mock atLeastOnce with concrete value, the rest not important

查看:86
本文介绍了至少在具体值上模拟一次,其余的都不重要的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题在PHP中,但适用于使用xUnit框架的任何语言.

The question is in PHP, but applies to any language using the xUnit framework.

我想要一个模拟,它希望对方法jump进行140次调用.
我需要验证,至少一次有一个以500为参数的通话.
我不在乎是否所有的电话都是500,但是我至少需要一个以500打电话的电话.

I want a mock, that expects 140 calls to method jump.
I need to verify, that at least once there is a call with 500 as parameter.
I don't care if all the calls are 500, but I need at least one that is called with 500.

$mock = $this->getMock('Trampoline', ['jump']);

$mock->expects($this->atLeastOnce())
     ->method('jump')
     ->with($this->equalTo(500))
     ->will($this->returnValue(true));

$sportsman->setTramploine($mock);
$sportsman->jumpToRandomHeights($times = 140); // this calls Trampoline->jump
// I need to verify the sportsman had jumped 
// to the height of 500 at least once out of the 140 jumps he is performing

在当前代码中,第一次调用jump后测试失败,因为第一次调用的值与500不同,这意味着atLestOnce在这里仅指示应调用该方法,但不能除其他调用外,应使用特定值来调用它.

In the current code, the test fails after the first invocation of jump because the first invocation had a value different of 500, meaning the atLestOnce here only indicates that the method should be called, but not that it should be called with specific value among other calls.

缺少的信息是使用with内部的回调.多亏了edorian在下面的回答,这才解决了问题:

The missing piece of information was using callbacks inside the with. Thanks to edorian's answer below this is what worked out:

$testPassed = false;

$checkMinHeight = function ($arg) use(&$testPassed)
{
    if($arg === 500)
        $testPassed = true;

    // return true for the mock object to consider the input valid
    return true;
}


$mock = $this->getMock('Trampoline', ['jump'])
    ->expects($this->atLeastOnce())
    ->method('jump')
    ->with($checkMinHeight)
    ->will($this->returnValue(true));

$sportsman->setTramploine($mock);
$sportsman->jumpToRandomHeights($times = 1000); // this calls Trampoline->jump
// I need to verify the sportsman had jumped 
// to the height of 500 at least once out of the 1000 jumps he is performing


$this->assertTrue($testPassed, "Sportsman was expected to 
    jump 500m at least once");

推荐答案

您可以但我可以想到的使用PHPUnits模拟API的最佳实现仍然令人毛骨悚然.

You can but the best implementation withing PHPUnits mocking API, that I could come up with, still looks quite creepy.

解决此问题的另一种方法是更具可读性的方法是创建自己的Trampoline子类并在其中实现.

Another way to solve this is a little more readable way would be to create your own subclass of Trampoline and implement it there.

假设此类:

<?php
class FancyMocking {
    function doThing($value) { }
}

,并且我们有$x个调用,其中之一必须具有$value > 200:

and that we have $x calls and one of those has to have a $value > 200:

<?php

class FancyMockingTest extends PHPUnit_Framework_TestCase {

    public function testAtLeastOfMy200CallsShouldHaveAValueGreaterThan500() {
      $maxInvocations = 200;

      $mock = $this->getMock('FancyMocking');
      $mock->expects($this->exactly($maxInvocations))
        ->method('doThing')
        ->with($this->callback(function ($value) use ($maxInvocations) { 
            static $invocationCount = 0;
            static $maxValue = 0;

            $maxValue = max($value, $maxValue);
            /* The assertion function will be called twice by PHPUnit due to implementation details, so the *2 is a hack for now */
            if (++$invocationCount == $maxInvocations * 2) { 
                $this->assertGreaterThan(200, $maxValue, 'in 500 tries the max value didn\'t to over 200');
            } 
            return true;
        }))
        ->will($this->returnCallback(function ($value) { 
            return $value >= 200;
        }));
     for($i = $maxInvocations - 2; $i; --$i) { 
          $mock->doThing(50);
     } 
     var_dump($mock->doThing(250));
     var_dump($mock->doThing(50));
    }


}

这将产生:

PHPUnit 3.7.9 by Sebastian Bergmann.

.bool(true)
bool(false)


Time: 0 seconds, Memory: 2.75Mb

OK (1 test, 2 assertions)

将调用表示为250会返回true,表示整个测试用例都有效.

Meaning the call with 250 returns true an the whole test case works.

要使其失败,我们将var_dump($mock->doThing(250));更改为var_dump($mock->doThing(70));并再次运行:

To make it fail we change var_dump($mock->doThing(250)); to var_dump($mock->doThing(70)); and run it again:

PHPUnit 3.7.9 by Sebastian Bergmann.

Fbool(false)


Time: 0 seconds, Memory: 2.75Mb

There was 1 failure:

1) FancyMockingTest::testAtLeastOfMy200CallsShouldHaveAValueGreaterThan500
Expectation failed for method name is equal to <string:doThing> when invoked 200 time(s)
in 500 tries the max value didn't to over 200
Failed asserting that 70 is greater than 200.

.../FancyMockingTest.php:29

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.


不使用PHPUnits模拟API的解决方案将是


The solution that doesn't use PHPUnits mocking API would be something like

class FancyMockingFakeImplementation extends FancyMocking,使用它代替模拟并在其中编写自定义逻辑.

class FancyMockingFakeImplementation extends FancyMocking, using that instead of the mock and writing the custom logic there.

这篇关于至少在具体值上模拟一次,其余的都不重要的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆