Тесты и проблема наследования - PullRequest
4 голосов
/ 08 декабря 2010

Представьте, что у вас есть приложение, и вы хотите выполнить над ним модульные и функциональные тесты (не сложно представить). У вас может быть абстрактный класс, назовем его AbstractTestClass, из которого распространяются все ваши модульные тесты.

AbstractTestClass будет выглядеть примерно так (с использованием JUnit 4):

class AbstractTestClass {
    boolean setupDone = false;

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

Вот с чем я борюсь. У меня есть другой абстрактный класс, который тестирует веб-интерфейсы:

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 . Этот дизайн может дать мне возможность иметь те же данные при модульном тестировании, что и при тестировании интерфейса.

Обычно, имея дело с такой проблемой, вы должны отдавать предпочтение композиции перед наследованием или использовать шаблон стратегии.

К сожалению, мне не совсем нравится идея отдавать предпочтение композиции перед наследованием в этом конкретном сценарии, и я не понимаю, как я мог бы использовать шаблон стратегии, возможно, есть недостаток дизайна, и я не совсем вижу решение.

Как я мог спроектировать эту архитектуру для достижения своей цели.

Ответы [ 3 ]

2 голосов
/ 09 декабря 2010

Я бы реализовал это следующим образом:

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 не является обязательным, я сделал это только для демонстрационных целей, в мире чтения вы, вероятно, будете использовать другой интерфейс.

0 голосов
/ 14 мая 2012

Я думаю, что дизайн не так в целом.Вы не должны использовать наследование в модульных тестах вообще.Тесты должны быть изолированными и действительно простыми.Очень часто, как и в вашем случае, необходимо подготовить несколько дополнительных объектов, которые помогут тестовым методам выполнять свою работу.В таком случае вы должны определить создателей таких объектов и разместить их где-нибудь за пределами тестовых случаев.

Например:

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, чтобы где-то определитьсяиначе, и этот класс выполнит всю работу, которую вы сейчас пытаетесь сделать, используя шаблон состояния или наследование.Оба подхода неверны при работе с юнит-тестами.

0 голосов
/ 08 декабря 2010

Важно не дублировать код. В этой ситуации я бы создал

MyScenarioTestUtil

класс, в котором есть набор статических методов, который устанавливает данные так, как вам нужно Вы бы вызвали служебные методы из установки. Таким образом, вы храните весь код в одном месте.

Это на самом деле просто семантическое отличие от использования композиции ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...