Статьи

Правила JUnit

Вступление

В этом посте я хотел бы показать пример использования правила JUnit для упрощения тестирования.

Недавно я унаследовал довольно сложную систему, которая не все проверена. И даже проверенный код сложен. В основном я вижу отсутствие тестовой изоляции. (Я напишу другой блог о работе с Legacy Code).

Один из тестов (и кода), который я исправляю, фактически тестирует несколько компонентов вместе. Он также подключен к БД. Он проверяет некоторую логику и пересечение между компонентами. Когда код не компилировался в совершенно другом месте, тест не мог быть запущен, поскольку он загружал весь контекст Spring. Структура была такова, что перед тестированием (любого класса) был инициирован весь Spring-контекст. Тесты расширяют BaseTest, который загружает весь контекст Spring.

BaseTest также очищает БД в методе @After.

Важное примечание: эта статья посвящена изменению тестов, которые не являются полностью корректными. При создании нового кода и тестов они должны быть изолированы, тестировать одну вещь и т. Д. Лучшие тесты должны использовать фиктивные БД / зависимости и т. Д. После того, как я исправлю тест и рефакторинг, я буду уверен, что внесу больше изменений.

Вернуться к нашей теме …

Итак, что я получил, так это медленный запуск тестового набора, отсутствие изоляции и даже проблемы с запуском тестов из-за не связанных проблем. Поэтому я решил отделить загрузку контекста от соединения с БД и от того и другого до очистки базы данных.

Подход

Чтобы добиться этого, я сделал три вещи: во-первых, изменил наследование тестового класса. Он перестал наследовать BaseTest. Вместо этого он наследует AbstractJUnit4SpringContextTests. Теперь я могу создать свой собственный контекст для каждого теста и не загружать все.

Теперь мне нужно было два правила: @ClassRule и @Rule @ClassRule будут отвечать за соединение с БД @Rule будет очищать БД после / перед каждым тестом.

Но сначала, что такое JUnit Rules?
Краткое объяснение состоит в том, что они дают возможность перехватить метод испытания, аналогичный концепции АОП. @Rule позволяет нам перехватывать метод до и после фактического запуска метода. @ClassRule перехватывает тестовый прогон класса. Очень известная @Rule — это TemporaryFolder от JUnit.

(Аналогично @Before, @After и @BeforeClass).

Создание @Rule

Самым простым было создать правило, которое очищает БД до и после тестового метода. Вам необходимо реализовать TestRule , у которого есть один метод: Statement apply (База операторов , Описание описания); Вы можете многое сделать с этим. Я узнал, что обычно у меня будет внутренний класс, который расширяет Statement . Созданное мной правило не создает соединение с БД, но получает его в конструкторе.

Вот полный код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class DbCleanupRule implements TestRule {
    private final DbConnectionManager connection;
 
    public MongoCleanupRule(DbConnectionManager connection) {
        this.connection = connection;
    }
 
    @Override
    public Statement apply(Statement base, Description description) {
        return new MongoCleanupStatement(base, connection);
    }
 
    private static final class DbCleanupStatement extends Statement {
        private final Statement base;
        private final DbConnectionManager connection;
         
        private MongoCleanupStatement(Statement base, DbConnectionManager connection) {
            this.base = base;
            this.connection = connection;
        }
 
        @Override
        public void evaluate() throws Throwable {
            try {
                cleanDb();
                base.evaluate();
            } finally {
                cleanDb();
            }
        }
         
        private void cleanDb() {
            connection.doTheCleanup();
        }
    }
}

Создание @ClassRule

ClassRule на самом деле также TestRule. Единственное отличие от правила состоит в том, как мы используем его в нашем тестовом коде. Я покажу это ниже.

Сложность в создании этого правила заключалась в том, что я хотел использовать контекст Spring, чтобы получить правильное соединение.
Вот код:
(ExternalResource — TestRule)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class DbConnectionRule extends ExternalResource {
    private DbConnectionManager connection;
 
    public DbConnectionRule() {
    }
     
    @Override
    protected void before() throws Throwable {
        ClassPathXmlApplicationContext ctx = null;
        try {
            ctx = new ClassPathXmlApplicationContext("/META-INF/my-db-connection-TEST-ctx.xml");
            mongoDb = (DbConnectionManager) ctx.getBean("myDbConnection");
        } finally {
            if (ctx != null) {
                ctx.close();
            }
        }
    }
     
    @Override
    protected void after() {
    }
     
    public DbConnectionManager getDbConnecttion() {
        return connection;
    }
}

(Вы видели, что я могу заставить DbCleanupRule наследовать ExternalResource?)

Используй это

Последняя часть о том, как мы используем правила. @Rule должно быть открытым полем. @ClassRule должен быть открытым статическим полем.

И вот оно:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@ContextConfiguration(locations = { "/META-INF/one-dao-TEST-ctx.xml", "/META-INF/two-TEST-ctx.xml" })
public class ExampleDaoTest extends AbstractJUnit4SpringContextTests {
 
    @ClassRule
    public static DbCleanupRule  connectionRule = new DbCleanupRule ();
     
    @Rule
    public DbCleanupRule dbCleanupRule = new DbCleanupRule(connectionRule.getDbConnecttion());
 
    @Autowired
    private ExampleDao classToTest;
 
    @Test
    public void foo() {
    }
}

Это все.
Надеюсь, поможет.