草庐IT

php - 单元测试和对象继承

coder 2024-04-18 原文

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

我有一个扩展类 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 - 单元测试和对象继承的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  3. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  4. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  5. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  6. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  7. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  8. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  10. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

随机推荐