Статьи

Правила 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 .
Созданное мной правило не создает соединение с БД, но получает его в конструкторе.

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

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)

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 должен быть открытым статическим полем.

И вот оно:

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() {
}
}

Это все.

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