Javascript 混合对象构造和应用逻辑

Javascript 混合对象构造和应用逻辑,javascript,unit-testing,testing,Javascript,Unit Testing,Testing,我已经开始重构我的代码,使其可测试。我发现的一个问题是,我将对象构造与应用程序逻辑混合在一起。如果我有一个名为SomeClass的构造函数,它执行应用程序逻辑,但也实例化了另一个类,那么我在尝试测试时遇到了问题: function SomeClass() { //does lots of application type work //but then also instantiates a different object new AnotherClass(); }

我已经开始重构我的代码,使其可测试。我发现的一个问题是,我将对象构造与应用程序逻辑混合在一起。如果我有一个名为
SomeClass
的构造函数,它执行应用程序逻辑,但也实例化了另一个类,那么我在尝试测试时遇到了问题:

function SomeClass() {

    //does lots of application type work
    //but then also instantiates a different object
    new AnotherClass();
}
测试变得很困难,因为现在我需要找到一种方法在测试环境中创建另一个类

我使用依赖注入处理了这个问题。因此,
SomeClass
将另一个类的实例作为参数:

function SomeClass(anotherObj) {

}
就我所能看到的而言,这个问题只是将问题推迟到我的应用程序中的其他地方。我仍然需要从代码中的其他地方创建另一个类

这篇谷歌测试文章建议:

为了有一个可测试的代码库,您的应用程序应该有两个 各种各样的课程。工厂里,到处都是“新”的经营者 并负责构建应用程序的对象图, 但是什么都不要做。应用程序逻辑类没有“new”操作符,负责执行工作

这听起来很像我的问题,工厂类型模式正是我所需要的。所以我尝试了以下方法:

function anotherClassFactory() {
    return new AnotherClass();
}

function SomeClass() {
    anotherClassFactory();
}
但是
SomeClass
仍然依赖于工厂。我该如何正确地回避这个问题呢?

(我之所以将此作为一个社区维基答案,是因为我坦率地认为它只回答了问题的一部分,而没有说太多。希望有更多知识的其他人能够改进它。)

但是
SomeClass
仍然依赖于工厂。我怎样才能正确地处理这个问题

根据您链接的链接,您可以这样做:

anotherclass.js

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}
function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}
function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}
var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
someclass.js

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}
function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}
function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}
var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
someclass test.js

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}
function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}
function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}
var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
app.js

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}
function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}
function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}
var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
因此,真正的应用程序是使用
buildApp
构建的,它为
SomeClass
提供了它的
另一个class
实例。您对
SomeClass
的测试将使用
SomeClass\u testSomething
等等,它使用的是真实的
SomeClass
,但是是一个模拟实例,而不是一个真实的
另一个类
,其中包含的内容对于测试来说刚好足够

不过,我的依赖注入功能很弱,坦率地说,我不知道
buildApp
如何扩展到现实世界,也不知道如果一个方法必须创建一个对象来完成它的工作,你应该做什么,例如:

SomeClass.prototype.doSomething = function() {
    // Here, I need an instance of AnotherClass; maybe I even need to
    // create a variable number of them, depending on logic internal
    // to the method.
};
你不会把方法需要的所有东西都作为参数传递,那将是一场意大利面噩梦。这可能就是为什么对于更静态的语言,通常会涉及工具,而不仅仅是编码模式


当然,在JavaScript中,我们还有另一个选择:只需在代码中使用
new-AnotherClass

anotherclass.js

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}
function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}
function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}
var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
someclass.js

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}
function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}
function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}
var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
someclass test.js

function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass(a) {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    a.foo();

    // ...app logic...
}
function someClass_testSomething() {
    var sc = new SomeClass({
        foo: function() { /* ...appropriate `foo` code for this test... */}
    });
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
function buildApp() {
    return {
        // ...lots of various things, including:
        sc: new SomeClass(new AnotherClass())
    };
}
function AnotherClass() {
}
AnotherClass.prototype.foo = function() { /* ... */ };
AnotherClass.prototype.bar = function() { /* ... */ };
AnotherClass.prototype.baz = function() { /* ... */ };
function SomeClass() {
    // ...app logic...

    // Use AnotherClass instance `a`; let's say you're going to call `foo`,
    // but have no use for `bar` or `baz`
    (new AnotherClass()).foo();

    // ...app logic...
}
var AnotherClass;
function someClass_testSomething() {
    // Just enough AnotherClass for this specific test; there might be others
    // for other tests
    AnotherClass = function() {
    };
    AnotherClass.prototype.foo = function() { /* ...appropriate `foo` code for this test... */};

    var sc = new SomeClass();
    // ...test `sc`...
}
function someClass_testSomethingElse() {
    // ...
}
在实际应用程序中使用
anotherclass.js
someclass.js
,在测试
someclass
时使用
someclass.js
someclass test.js


这当然是一个粗略的草图;我假设你的真实世界的应用程序可能没有全局变量(
SomeClass
AnotherClass
),但是你包含的是
SomeClass
,而
AnotherClass
可能也可以用来包含
SomeClass
,并包含对它的测试及其各种伪
其他类的测试。
s.

感谢您的详细回答。我读过这篇链接文章,我想你答案的第一部分解释了为什么我没有采用这种方法……在现实世界中,或者至少在现实世界中,当涉及到js时,我不会这么做。目前,我已经得到了你在回答的第二部分中建议的场景…那就是模仿另一个假班级。正如你所说的,希望有人能对此提出改进建议。