php - 单元测试和对象继承

标签 php unit-testing phpunit

我有一个关于“单元测试”和对象继承的问题。例如:

我有一个扩展类 B 的类 A。让我们假设这两个类的唯一区别是添加方法。在 B 类中,此添加方法略有扩展。现在我想为 B 类的 add 函数编写一个单元测试,但是由于 parent::add 调用,我对父类 A 有依赖性。在这种情况下,我不能模拟父类的 add 方法,所以结果测试将是一个集成测试,但如果我希望它成为一个单元测试?我不希望因为类 A 中的父方法而导致类 B 中方法添加的测试失败。在这种情况下,只有父方法的单元测试应该失败。

class B exends A
{
    public function add($item)
    {
        parent::add($item);
        //do some additional stuff  
    }
    ....
}

class A
{
    protected $items = [];
    public function add($item)
    {
        $this->items[] = $item;
    }
    ....
}

我当然可以使用对象聚合并将我的父对象传递给子构造函数,因此我可以模拟父方法添加,但这是最好的方法吗?我很少再使用对象继承。

class B
{
    protected $a;

    public function __contruct(A $a)
    {
        $this->a = $a;
    }

    public function add($item)
    {
        $this->a->add($item);
        //do some additional stuff  
    }
    ....
}

class A
{
    protected $items = [];
    public function add($item)
    {
        $this->items[] = $item;
    }
    ....
}

非常感谢您的意见。谢谢!

最佳答案

问问自己,你想要实现什么样的继承?如果 B 是一种 A,那么您需要接口(interface)继承。如果 B 与 A 共享大量代码,那么您需要实现继承。有时您两者都想要。

接口(interface)继承将语义意义划分为严格的层次结构,该意义从广义到专门组织。想想taxonomy .接口(interface)(方法签名)表示行为:类响应的消息集,以及类发送的消息集。从类继承时,您隐含地接受父类(super class)代表您发送的所有消息的责任,而不仅仅是它可以接收的消息。出于这个原因,父类(super class)和子类之间的耦合是紧密的,每个都必须严格替代另一个(参见 Liskov Substitution Principle)。

实现继承将数据表示和行为(属性和方法)的机制封装到一个方便的包中,以供子类重用和增强。根据定义,子类继承父类的接口(interface),即使它只需要实现。

最后一部分很关键。再读一遍:子类继承接口(interface),即使它们只想要实现。

B是否严格要求A的接口(interface)?可乙substitute对于 A,在所有情况下都匹配协方差和逆方差?

  • 如果答案是,那么您就有了真正的子类型化。恭喜。现在您必须对相同的行为进行两次测试,因为在 B 中您负责维护 A 的行为:对于 A 可以做的每一件事,B 必须能够做。

  • 如果答案是,那么您只需要共享实现,测试实现是否有效,然后测试 B 和 A 是否分别分派(dispatch)到实现中。

实际上,我避免extends。当我想要实现继承时,我使用trait在一个地方定义static行为,然后使用合并它在需要的地方。当我想要接口(interface)继承时,我定义了许多狭窄的 interface 然后在所有具体类型中与 implements 结合,可能使用 trait 来利用行为。

对于你的例子,我会这样做:

trait Container {
    public function add($item) { $this->items[] = $item; }
    public function getItems() { return $this->items; }
    private $items = [];
}

interface Containable { public function add($item); }

class A implements Containable { use Container; }

class B implements Containable {
    use Container { Container::add as c_add; }
    public function add($item) {
        $this->c_add($item);
        $this->mutate($item);
    }
    public function mutate($item) { /* custom stuff */ }
}

Container::addB::mutate 将进行单元测试,而 B::add 将进行集成测试。

总而言之,更喜欢组合而不是继承因为extends is evil .阅读 ThoughtWorks 入门 Composition vs. Inheritance: How To Choose以更深入地了解设计权衡。


你问“静态行为”?是的。低耦合是一个目标,这适用于特征。一个特征应该尽可能只引用它定义的变量。 最安全 的实现方式是使用将所有输入作为正式参数的静态方法。 最简单 的方法是在特征中定义成员变量。 (但是,请避免让特征使用特征中未明确定义的成员变量——否则,那是盲目耦合!)我发现,特征成员变量的问题是,当混合多个特征时,你增加了碰撞。诚然,这很小,但对于图书馆作者来说,这是一个实际的考虑。

关于php - 单元测试和对象继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40471428/

相关文章:

php - 如何使用 PHP 或 Mysql 或 Laravel 优化将 12K 的 JSON 插入数据库

php - symfony: Autowiring 接口(interface)

c# - Visual Studio C# 单元测试 - 使用不同/多个测试初始化​​运行单元测试,多次运行相同的单元测试?

php - PHPUnit 中的单元测试 - 处理多个条件

php - 在 PHPUnit 测试中找不到 Slim/Http/Environment

php - 将一个表的一列连接到另一个表的多列

php - 学说迁移 : How to avoid SQL errors in the postUp step?

java - 在一个方法中多次模拟 Calendar.getInstance 静态方法

c - 模拟/伪造文件系统以测试 C 代码?

php - 如何在 selenium PHP 单元中切换到 iframe