Php 在单元测试中何时使用存根/模拟,何时使用真实对象?

Php 在单元测试中何时使用存根/模拟,何时使用真实对象?,php,unit-testing,mocking,phpunit,Php,Unit Testing,Mocking,Phpunit,我最近试图提高我的单元测试技能,并阅读了大量有关单元测试的文献,我还试图了解我在目前使用phpunit开发的一个php项目中学到的东西。但在我看来,我仍然有一个非常基本的问题,那就是如何对与其他类的对象甚至同一类的其他方法交互的方法进行单元测试 是否有一些经验法则或帮助我如何决定我应该存根/模拟哪些依赖项,对于哪些依赖项我应该简单地使用普通对象?为了澄清我的问题,下面是一个示例代码,包含不同的场景: interface DependencyInterface { public metho

我最近试图提高我的单元测试技能,并阅读了大量有关单元测试的文献,我还试图了解我在目前使用phpunit开发的一个php项目中学到的东西。但在我看来,我仍然有一个非常基本的问题,那就是如何对与其他类的对象甚至同一类的其他方法交互的方法进行单元测试

是否有一些经验法则或帮助我如何决定我应该存根/模拟哪些依赖项,对于哪些依赖项我应该简单地使用普通对象?为了澄清我的问题,下面是一个示例代码,包含不同的场景:

interface DependencyInterface {
    public method dependentMethod() { ... }
}
class Dependency implements DependencyInterface {...}

class ClassUnderTest {
    private $dependency
    public __construct(DependencyInterface $dependency) {
        $this->dependency = dependency;
    }
    public function methodUnderTest() {
        ...
        $result1 = $this->dependency->dependentMethod();
        ...
        $result2 = $this->otherMethod();
        ...
        $result3 = $this->usedInMultiplePublicMethods();
    }

    public function otherMethod() {...}
    private function usedInMultiplePublicMethods() {...}
}
因此,我现在的问题是一个单元测试,测试methodUnderTest方法的功能,我是否应该:

  • 存根接口DependencyInterface并将其注入构造函数中,还是应该简单地使用实现依赖项的实例
  • 对类本身进行部分存根测试,以便为otherMethod提供一个固定的结果,因为这个可能非常复杂的方法已经有了自己的完整单元测试
  • 我决定不对私有方法进行单元测试,因为它们不是类接口的一部分(我知道这是一个有争议的话题,不属于我的问题范围)。现在,我是否必须涵盖使用MultiplePublicMethods中的私有方法的每个公共方法,以及私有方法中可能出现的所有影响?或者我应该只在使用它的一个公共方法中测试所有可能的效果,并在所有其他公共方法的测试中存根私有方法吗

  • 我不确定什么时候使用存根/模拟,什么时候不使用。

    模拟的原因是能够编写单元测试,这意味着一种测试:快速、隔离、可重复、自我验证、彻底和及时()


    为了能够独立地测试单元/模块,您可能需要模拟/存根任何外部模块(数据库访问、api调用、日志系统…。

    模拟的原因是能够编写单元测试,这意味着一种测试:快速、隔离、可重复、自验证、彻底和及时()


    为了能够单独测试单元/模块,您可能需要模拟/存根任何外部模块(数据库访问、api调用、日志系统…)。

    关于第1点和第2点,rad的回答指出了需要牢记的主要基本原则,例如,如果您要测试一个逻辑,该逻辑使用数据库服务获取数据,然后对获取的数据进行计算,您会模拟该数据库服务还是使用真实的数据库服务

    从目标中可以清楚地看到,您是单元测试逻辑本身,而不是数据库服务数据获取,所以您将模拟数据库服务,并假设数据库服务提供正确的数据&您只需将重点放在对计算数据的测试逻辑上。您将对数据库抓取服务进行单独的测试,隔离属性就是其中的一部分

    从这个意义上讲,unit这个词很重要,你的这些测试应该非常关注当前的逻辑,只通过限制你的范围,而不是把其他的东西都塞进其中

    这个答案主要是针对你的观点#3。不显式地测试私有方法是可以的,但如果您遵循单元测试的基本目的,您就不会太担心私有或公共的东西了。在某种程度上,单元测试也是为了开发人员的自我满足&如果单元测试也是为私有方法编写的,那么它只会使代码更加健壮

    仅仅因为访问级别是私有的,并不会改变需要测试的逻辑的基本概念。就代码覆盖率而言,您可以使用一个公共方法,但我认为您应该将来自不同公共方法的调用视为不同的调用


    永远不要忘记单元测试的基本目的——您正在尝试查找逻辑中的错误,尝试覆盖所有边界情况&尝试使代码更加健壮

    对于您的第1点和第2点,rad的回答指出了需要牢记的主要基本原则,例如,如果您要测试一个逻辑,该逻辑使用数据库服务获取数据,然后对获取的数据进行计算,您会模拟该数据库服务还是使用真实的数据库服务

    从目标中可以清楚地看到,您是单元测试逻辑本身,而不是数据库服务数据获取,所以您将模拟数据库服务,并假设数据库服务提供正确的数据&您只需将重点放在对计算数据的测试逻辑上。您将对数据库抓取服务进行单独的测试,隔离属性就是其中的一部分

    从这个意义上讲,unit这个词很重要,你的这些测试应该非常关注当前的逻辑,只通过限制你的范围,而不是把其他的东西都塞进其中

    这个答案主要是针对你的观点#3。不显式地测试私有方法是可以的,但如果您遵循单元测试的基本目的,您就不会太担心私有或公共的东西了。在某种程度上,单元测试也是为了开发人员的自我满足&如果单元测试也是为私有方法编写的,那么它只会使代码更加健壮

    仅仅因为访问级别是私有的,并不会改变需要测试的逻辑的基本概念。就代码覆盖率而言,您可以使用一个公共方法,但我认为您应该将来自不同公共方法的调用视为不同的调用


    永远不要忘记单元测试的基本目的——您正在尝试查找逻辑中的错误,尝试覆盖所有边界情况&尝试使代码更加健壮

    好的,我明白了,这意味着如果我在我的其他类中没有任何外部影响的“正常”逻辑,我不应该存根它们来测试我的方法,而只是使用正常的类?但例如,如果我有一个接口(如我的示例Depende)