测试和继承问题

发布于 2024-10-06 16:24:15 字数 1031 浏览 4 评论 0原文

想象一下您有一个应用程序,并且您想要对其进行单元测试和功能测试(不难想象)。您可能有一个抽象类,我们将其称为 AbstractTestClass,所有单元测试都从该类扩展。

AbstractTestClass 看起来像这样(使用 JUnit 4):

class AbstractTestClass {
    boolean setupDone = false;

    @Before
    public void before() {
        if(!setupDone) {
            // insert data in db
            setupDone = true;
        }
    }
}

这就是我正在努力解决的问题。我有另一个测试 Web 界面的抽象类:

class AbstractWebTestClass extends WebTestCase {
    boolean setupDone = false;

    @Before
    public void before() {
        if(!setupDone) {
            // here, make a call to AbstractTestClass.before()
            // init the interfaces
            setupDone = true;
        }
        // do some more thing
    }
}

它几乎是同一个类,只是它扩展了 WebTestCase。这种设计可以让我在单元测试时获得与测试接口时相同的数据。

通常,在处理此类问题时,您应该倾向于组合而不是继承或使用策略模式。

不幸的是,我不太喜欢在这种特定情况下倾向于组合而不是继承的想法,并且我不知道如何使用策略模式,可能存在设计缺陷,并且我不太明白解决方案。

我如何设计这个架构才能实现我的目标。

Imagine you have an application and you want to make unit tests and functionnal tests over it (not quite hard to imagine). You might have an abstract class, let's call it AbstractTestClass, from which all your unit tests extends.

AbstractTestClass would look something like this (using JUnit 4) :

class AbstractTestClass {
    boolean setupDone = false;

    @Before
    public void before() {
        if(!setupDone) {
            // insert data in db
            setupDone = true;
        }
    }
}

Here is what I'm struggling with. I'm having another abstract class which test the web interfaces :

class AbstractWebTestClass extends WebTestCase {
    boolean setupDone = false;

    @Before
    public void before() {
        if(!setupDone) {
            // here, make a call to AbstractTestClass.before()
            // init the interfaces
            setupDone = true;
        }
        // do some more thing
    }
}

It's pretty much the same class, except that it extends WebTestCase. This design could give me the possibility to have the same data while unit testing than when testing the interface.

Usually, when dealing with such issue, you should favor composition over inheritance or use a strategy pattern.

Unfortunately, I don't quite like the idea to favor composition over inheritance in this particular scenario and I don't see how I could use a strategy pattern, there is probably a design flaw and I can't quite see the solution.

How could I design this architecture in order to achieve my goal.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

旧伤慢歌 2024-10-13 16:24:15

我将通过以下方式实现这一点:

class Example {

class LazyInitStrategy implements Runnable {
    private final Runnable operation;
    private boolean done = false;

    LazyInitStrategy(Runnable operation) {
        this.operation = operation;
    }

    @Override
    public void run() {
        if (!done) {
            operation.run();
            done = true;
        }
    }
}

private final class AbstractInit implements Runnable {
    public void run() {
        // insert data in db
    }
}

private final class AbstractWebInit implements Runnable {
    public void run() {
        // here, make a call to AbstractTestClass.before() init the interfaces
    }
}

class AbstractTestClass {

    final LazyInitStrategy setup = new LazyInitStrategy(new AbstractInit());

    @Before
    public void before() {
        setup.run();
        // do some more thing
    }
}

class AbstractWebTestClass extends WebTestCase {

    final LazyInitStrategy setupInfo = new LazyInitStrategy(new AbstractWebInit());

    @Before
    public void before() {
        setupInfo.run();
        // do some more thing
    }
}

}

当然,这是非常简单的解决方案,但它应该消除用于检查设置是否完成的 if/else 逻辑重复。使用 Runnable 是可选的,我这样做只是为了演示目的,在阅读世界中您可能会使用另一个接口。

I would implement this in the following way:

class Example {

class LazyInitStrategy implements Runnable {
    private final Runnable operation;
    private boolean done = false;

    LazyInitStrategy(Runnable operation) {
        this.operation = operation;
    }

    @Override
    public void run() {
        if (!done) {
            operation.run();
            done = true;
        }
    }
}

private final class AbstractInit implements Runnable {
    public void run() {
        // insert data in db
    }
}

private final class AbstractWebInit implements Runnable {
    public void run() {
        // here, make a call to AbstractTestClass.before() init the interfaces
    }
}

class AbstractTestClass {

    final LazyInitStrategy setup = new LazyInitStrategy(new AbstractInit());

    @Before
    public void before() {
        setup.run();
        // do some more thing
    }
}

class AbstractWebTestClass extends WebTestCase {

    final LazyInitStrategy setupInfo = new LazyInitStrategy(new AbstractWebInit());

    @Before
    public void before() {
        setupInfo.run();
        // do some more thing
    }
}

}

Sure this is very simplistic solution but it should eliminate if/else logic duplication for checking if setup was done. Using Runnable is optional, I did this just for demo purposes, in read world you probably will use another interface.

绝不服输 2024-10-13 16:24:15

要完成的重要事情是不要重复代码。在这种情况下,我将创建一个

MyScenarioTestUtil

类,其中包含一堆静态方法,可根据需要设置数据。您可以从设置中调用实用程序方法。这样您就可以将所有代码保存在一个地方。

它实际上只是与使用组合的语义差异......

The important thing to accomplish is not to duplicate code. In this situation I would create a

MyScenarioTestUtil

class that has a bunch of static methods on it that sets up the data as you need to. You would invoke the utility methods from the setup. That way you keep all the code in one place.

Its really just a semantics difference from using composition...

剑心龙吟 2024-10-13 16:24:15

我认为总体设计是错误的。您根本不应该在单元测试中使用继承。测试应该是孤立的并且非常简单。很多时候,就像您的情况一样,有必要准备一些补充对象,这将有助于测试方法完成其工作。在这种情况下,您应该定义此类对象的构建器,并将它们放置在测试用例之外的某个位置。

例如:

public void testMethodThatNeedsSomePreparedObjects() {
  Foo foo = new FooBuilder()
    .withFile("some-text.txt")
    .withNumber(123)
    .build();
  // now we are testing class Bar, using object of class Foo
  Bar bar = new Bar(foo);
}

因此,您需要在其他地方定义 FooBuilder,并且此类将完成您现在尝试使用策略模式或继承执行的所有工作。在处理单元测试时,这两种方法都是错误的。

I think that the design is wrong in general. You shouldn't use inheritance in unit tests at all. Tests should be isolated and really plain. Very often, like in your case, it's necessary to prepare some supplementary objects, that will help test methods to do their job. In such a case you should define builders of such objects, and place them somewhere outside of test cases.

For example:

public void testMethodThatNeedsSomePreparedObjects() {
  Foo foo = new FooBuilder()
    .withFile("some-text.txt")
    .withNumber(123)
    .build();
  // now we are testing class Bar, using object of class Foo
  Bar bar = new Bar(foo);
}

Thus, you need FooBuilder to be defined somewhere else, and this class will do all the work you're now trying to do using stategy pattern or inheritance. Both approached are wrong, when dealing with unit tests.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文