Подтвердить что ты не робот

Как я могу создать настраиваемый набор тестов JUnit4?

В Guava имеется обширный набор тестов для реализаций коллекции, написанных в JUnit3, которые выглядят так:

/*
 * Copyright (C) 2008 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class CollectionRemoveTester<E> extends AbstractTester<E> {

  @CollectionFeature.Require(SUPPORTS_REMOVE)
  @CollectionSize.Require(absent = ZERO)
  public void testRemove_present() {
     ...
  }
}

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

Я хотел бы построить что-то подобное в JUnit4, но мне не ясно, как это сделать: построив собственный Runner? Теории? До сих пор я догадываюсь написать что-то вроде

abstract class AbstractCollectionTest<E> {
   abstract Collection<E> create(E... elements);
   abstract Set<Feature> features();

   @Test
   public void removePresentValue() {
      Assume.assumeTrue(features().contains(SUPPORTS_REMOVE));
      ...
   }
}

@RunWith(JUnit4.class)
class MyListImplTest<E> extends AbstractCollectionTest<E> {
  // fill in abstract methods
}

Общий вопрос: что, в JUnit4, я могу построить набор тестов для типа интерфейса, а затем применить эти тесты к отдельным реализациям?

4b9b3361

Ответ 1

В Junit вы можете использовать categories. Например, этот набор будет выполнять al test из AllTestSuite, аннотированный как интеграция:

import org.junit.experimental.categories.Categories;
import org.junit.experimental.categories.Categories.IncludeCategory;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Categories.class)
@IncludeCategory(Integration.class)
@Suite.SuiteClasses ({AllTestsSuite.class} )
public class IntegrationTestSuite {}

Вы также можете использовать @ExcludeCategory. Это полезно для удаления медленных тестов. Классы классов - это просто старые Java-классы или интерфейсы. Например:

public interface Integration{}
public interface Performance{}
public interface Slow{}
public interface Database{}

Вам нужно только выполнить необходимые тесты:

@Category(Integration.class)
public class MyTest{

   @Test
   public void myTest__expectedResults(){
   [...]

В одном тесте может быть более одной категории:

   @Category({Integration.class,Database.class})  
   public class MyDAOTest{

Для простоты я обычно создаю Suite со всеми классами в тестовой папке с помощью инструментария google:

import org.junit.runner.RunWith;

import com.googlecode.junittoolbox.ParallelSuite;
import com.googlecode.junittoolbox.SuiteClasses;

@RunWith(ParallelSuite.class)
@SuiteClasses({"**/**.class",           //All classes
             enter code here  "!**/**Suite.class" })    //Excepts suites
public class AllTestsSuite {}

Это работает в AllTestSuite для всех классов в одной и той же папке и вложенных папках, даже если у них нет _Test sufix. Но не сможет увидеть тест, который не находится в одной и той же папке или вложенных папках. junit-toolbox доступен в Maven с помощью:

<dependency>
    <groupId>com.googlecode.junit-toolbox</groupId>
    <artifactId>junit-toolbox</artifactId>
    <version>2.2</version>
</dependency>

Теперь вам нужно только выполнить пакет, который соответствует вашим потребностям:)

ОБНОВЛЕНИЕ. В Spring есть аннотация @IfProfileValue, которая позволяет выполнить тест условно следующим образом:

@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {

Для получения дополнительной информации см. Spring JUnit Testing Annotations

Ответ 2

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

Один из вариантов - аннотировать ваш класс с помощью @RunWith(Parameterized.class) и вставить статический метод, аннотированный с помощью @Parameters, который будет использоваться для фактической параметризации с использованием конструктора теста JUnit. Ниже примера я бесстыдно взял из https://github.com/junit-team/junit/wiki/Parameterized-tests:

@RunWith(Parameterized.class)
public class FibonacciTest {
    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {     
                 { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }  
           });
    }

    private int fInput;

    private int fExpected;

    public FibonacciTest(int input, int expected) {
        fInput= input;
        fExpected= expected;
    }

    @Test
    public void test() {
        assertEquals(fExpected, Fibonacci.compute(fInput));
    }
}

Это приведет к тому, что все ваши тестовые методы будут использовать те же параметры, что и для соответствующих полей класса JUnit. Ключ будет воплощением различных реализаций в этом статическом методе (Dagger, Guice, фабрики, что угодно). Затем они будут автоматически переданы конструктору, и вы будете нести ответственность за присвоение им полей, которые вы собираетесь использовать в методах тестирования. Как вы видите, вместо использования массива примеров из целых чисел просто разместите экземпляры своей реализации внутри. Для получения дополнительной информации см. Ссылку выше.

Второй вариант - использовать Zohhak с аннотацией @RunWith(ZohhakRunner.class) от https://github.com/piotrturski/zohhak. Это позволит вам параметризовать ваши модульные тесты для каждого метода вместо каждого класса. Это может быть сложнее с созданием factory, но его можно сделать довольно элегантно с небольшой работой. Пример, взятый с сайта Зохака:

@TestWith({
    "clerk,      45'000 USD, GOLD",
    "supervisor, 60'000 GBP, PLATINUM"
})
public void canAcceptDebit(Employee employee, Money money, ClientType clientType) {
    assertTrue(   employee.canAcceptDebit(money, clientType)   );
}

Я бы начал с первого подхода, и если вы нажмете alimit, перейдите ко второму. Приветствия и удачи.

Ответ 3

Вы можете использовать правила JUnit для условного игнорирования тестов (они будут заканчиваться как пропущенные в отчете maven, но может быть способ обходить вокруг этого, о котором я не знаю).

Это основано на правиле в этой статье. Я немного изменил правило, см. Здесь.

public abstract class AbstractCollectionTest {

@Rule
public ConditionalSupportRule rule = new ConditionalSupportRule();

private Collection<String> collection;
private Set<Class<? extends Feature>> features;

public AbstractCollectionTest(Collection<String> collection,
                              Class<? extends Feature> ... features) {
    this.collection = collection;

    this.features = new HashSet<>();
    for (Class<? extends Feature> feature : features) {
        this.features.add(feature);
    }
}

@Test
@ConditionalSupport(condition = SupportsRemove.class)
public void remove() throws Exception {

    // ...
    System.out.println("test run");
}

private interface Feature {}

public class SupportsRemove implements RunCondition, Feature {

    public SupportsRemove() {
    }

    @Override
    public boolean isSatisfied() {
        return features.contains(SupportsRemove.class);
    }
}

Пример теста для списка массивов:

public class ArrayListTest extends AbstractCollectionTest {

    public ArrayListTest() {
        super(new ArrayList<>(), SupportsRemove.class);
    }

}

Список, который не поддерживает удаление:

public class UnmodifiableListTest extends AbstractCollectionTest {

    public UnmodifiableListTest() {
        super(Collections.unmodifiableList(new ArrayList<>()));
    }
}