- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
这是一个关于公认的编码实践的好奇心问题。我(主要)是一名 Java 开发人员,并且越来越努力地对我的代码进行单元测试。我花了一些时间研究如何编写最易测试的代码,特别关注 Google 的 How to write untestable code。指南(非常值得一看,如果您还没有看过的话)。
自然地,我最近和一个更偏向于 C++ 的 friend 争论每种语言的继承模型的优点,我想我会拿出一张王牌,说 C++ 程序员通过以下方式测试他们的代码有多难经常忘记 virtual
关键字(对于 C++ 用户 - 这是 Java 中的默认设置;您可以使用 final
摆脱它)。
我发布了一个代码示例,我认为它可以很好地展示 Java 模型的优势(full thing is over on GitHub)。简短版本:
class MyClassForTesting {
private final Database mDatabase;
private final Api mApi;
void myFunctionForTesting() {
for (User u : mDatabase.getUsers()) {
mRemoteApi.updateUserData(u);
}
}
MyClassForTesting ( Database usersDatabase, Api remoteApi) {
mDatabase = userDatabase;
mRemoteApi = remoteApi;
}
}
不管我在这里写的内容的质量如何,我的想法是该类需要对数据库和一些 API(可能在远程 Web 服务器上)进行一些(可能非常昂贵的)调用。 myFunctionForTesting()
没有返回类型,那么如何对其进行单元测试呢?在 Java 中,我认为答案并不太难——我们模拟:
/*** Tests ***/
/*
* This will record some stuff and we'll check it later to see that
* the things we expect really happened.
*/
ActionRecorder ar = new ActionRecorder();
/** Mock up some classes **/
Database mockedDatabase = new Database(ar) {
@Override
public Set<User> getUsers() {
ar.recordAction("got list of users");
/* Excuse my abuse of notation */
return new Set<User>( {new User("Jim"), new User("Kyle")} );
}
Database(ActionRecorder ar) {
this.ar = ar;
}
}
Api mockApi = new Api() {
@Override
public void updateUserData(User u) {
ar.recordAction("Updated user data for " + u.name());
}
Api(ActionRecorder ar) {
this.ar = ar;
}
}
/** Carry out the tests with the mocked up classes **/
MyClassForTesting testObj = new MyClassForTesting(mockDatabase, mockApi);
testObj.myFunctionForTesting();
// Check that it really fetches users from the database
assert ar.contains("got list of users");
// Check that it is checking the users we passed it
assert ar.contains("Updated user data for Jim");
assert ar.contains("Updated user data for Kyle");
通过模拟这些类,我们将依赖项注入(inject)我们自己的轻量级版本,我们可以对这些版本进行断言以进行单元测试,并避免对数据库/api 进行昂贵、耗时的调用。 Database
和Api
的设计者不用太在意,这就是我们要做的,MyClassForTesting
的设计者> 当然不必知道!这对我来说似乎是一种很好的做事方式。
然而,我的 C++ friend 反驳说,这是一个可怕的 hack,C++ 不允许您这样做是有充分理由的!然后他提出了一个基于 Generics 的解决方案,它做的事情大致相同。为了简洁起见,我将只列出他给出的解决方案的一部分,但您同样可以找到 the whole thing over on Github .
template<typename A, typename D>
class MyClassForTesting {
private:
A mApi;
D mDatabase;
public MyClassForTesting(D database, A api) {
mApi = api;
mDatabase = database;
}
...
};
然后将像以前一样进行测试,但重要的位被替换如下所示:
class MockDatabase : Database {
...
}
class MockApi : Api {
...
}
MyClassForTesting<MockApi, MockDatabase>
testingObj(MockApi(ar), MockDatabase(ar));
所以我的问题是:首选方法是什么?我一直认为基于多态性的方法更好——而且我认为没有理由它不会在 Java 中出现——但通常认为使用泛型比在 C++ 中虚拟化所有内容更好吗? 你在你的代码中做了什么(假设你做单元测试)?
最佳答案
我可能有偏见,但我会说 C++ 版本更好。除其他事项外,多态性会带来一些成本。在这种情况下,您是在让您的用户支付这笔费用,即使他们没有从中直接受益。
例如,如果您有一个多态对象列表,并且想通过基类操作所有对象,那么使用多态性是合理的。然而,在这种情况下,多态性被用于用户从未见过的东西。您已经内置了操纵多态对象的能力,但从未真正使用过它——为了测试,您将仅拥有模拟对象,而对于实际使用,您将只有真实的对象。永远不会有(例如)数据库对象数组,其中一些是模拟数据库,另一些是真实数据库。
这不仅仅是一个效率问题(或者至少是一个运行时效率问题)。代码中的关系应该是有意义的。当有人看到(公共(public))继承时,应该告诉他们一些关于设计的事情。然而,正如您在 Java 中概述的那样,所涉及的公共(public)继承关系基本上是一个谎言——即他应该从中知道的(您正在处理多态后代)是一个彻头彻尾的谎言.相比之下,C++ 代码正确地向读者传达了意图。
当然,在某种程度上,我夸大了那里的情况。通常阅读 Java 的人几乎肯定已经习惯了继承通常被滥用的方式,所以他们根本不认为这是一个谎言。不过,这有点像把婴儿连同洗澡水一起倒掉——他们没有看到“谎言”的真相,而是学会了完全忽视遗传的真正含义(或者根本不知道,尤其是如果他们上了大学其中 Java 是教授 OOP 的主要工具)。正如我所说,我可能有点偏见,但对我来说,这使(大多数)Java 代码更难理解。您基本上必须小心忽略 OOP 的基本原则,并习惯于不断滥用它。
关于java - 依赖注入(inject) : templates (/generics) or virtual functions?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4877405/
进程虚拟机和系统虚拟机有什么区别? 我的猜测是,进程 VM 没有为该操作系统的整个应用程序提供一种操作系统,而是为某些特定应用程序提供环境。 系统虚拟机为操作系统提供了一个安装环境,就像 Virtua
我在成员函数的上下文中理解 virtual,例如 virtual void frob()。但它在类声明的上下文中意味着什么,例如 class Foo : public virtual Bar? 对于给
根据 react-virtualized 文档,“AutoSizer 组件装饰 React 元素并自动管理宽度和高度属性,以便装饰元素填充可用空间”。 建议通常是加上height: 100%;或 fl
根据 this类似 StackOverflow 问题和其他文章,默认情况下 C# 方法是“非虚拟的”,我认为这意味着您不能在派生类中覆盖它们。 如果那是真的,能否请您向我解释一下,在下面的示例中,我如
我有一个基类Media和几个派生类,即DVD、Book等...基类写成: class Media{ private: int id; string title;
我搜索了一些关于虚函数声明的帖子,相信 =0 在 virtual void test()=0; 是固定句法所以 virtual void test()=NULL; virtual void test(
我正在使用 RV 列表加载具有自定义格式的大型文档。它非常有效,但我遇到了以下两个问题: 我目前在 cellmeasurer 中设置了一个列表 based on this计算行的动态高度(宽度是固定的
我一直在努力制作 this react virtualized table example工作 & 开始严重怀疑我的理智。我创建了一个 react 应用程序,我只是想在 App.js 中使用以下内容呈
我在Windows 7 Pro计算机上安装了Windows Virtual PC和Windows XP Mode。运行XP模式会在Virtual PC上自动安装XP。我想创建第二台与第一台相同的虚拟P
我使用 Virtual PC 来创建新的环境来测试我的安装程序。但我一定是做错了什么,因为内部装有 Vista 或 XP 的 VPC 镜像占用了大约 15GB 的磁盘空间(包括安装在其中的 VS200
是否可以为 Ubuntu 虚拟机动态分配处理器和内存?例如。进程在主机系统上运行,导致处理器的使用率从 30%-70% 上下波动,这些进程还占用 8GB 内存中 3GB-7GB 之间的波动量,即 1G
我正在使用“react-virtualized”来创建一个表。在该表中,一些数据可能显示为 'Brian Vaughn1'。 .此表格单元格应具有 font-weight: bold并且只应呈现文本,
我正在使用“react-virtualized”来创建一个表。在该表中,一些数据可能显示为 'Brian Vaughn1'。 .此表格单元格应具有 font-weight: bold并且只应呈现文本,
我一直在努力理解一段这样的代码: class A { // some class definition } class B { public: virtual A *s
基于 http://en.wikipedia.org/wiki/Virtual_inheritance class Animal { ... }; // Two classes virtually i
我看到 C++ 中的某些函数被声明为 virtual const int getNumber(); 但是如果函数声明如下有什么区别呢? const virtual int getNumber(); 这
问题来自C++ faq。 http://www.parashift.com/c++-faq-lite/protected-virtuals.html 使用公共(public)重载虚拟的代码: clas
这个问题在这里已经有了答案: How is "=default" different from "{}" for default constructor and destructor? (3 个答案
virtual final 函数(final 在基类)是否有任何 vtable/virtual 成本? class B{ public: virtual void fFinal() final
我有一个只包含 exe 文件(没有源代码)的 hello 工具。 你好工具结构: bin helloBin.exe helloRoot.exe conanfile.py conanfile.py
我是一名优秀的程序员,十分优秀!