Есть две вещи, которые вы, возможно, пытаетесь проверить.
- Когда есть событие, представляющее интерес для оператора моей программы, выполняет ли моя программа соответствующую операцию регистрации, которая может информировать оператора об этом событии.
- Когда моя программа выполняет операцию регистрации, имеет ли сообщение, которое она выдает, правильный текст.
Эти две вещи на самом деле разные вещи, и поэтому могут быть проверены отдельно. Тем не менее, тестирование второго (текст сообщения) настолько проблематично, что я рекомендую вообще не делать этого. В конечном итоге проверка текста сообщения будет состоять из проверки того, что одна текстовая строка (ожидаемый текст сообщения) совпадает или может быть легко получена из текстовой строки, используемой в коде регистрации.
- Эти тесты вообще не проверяют логику программы, они только проверяют, что один ресурс (строка) эквивалентен другому ресурсу.
- Испытания хрупкие; даже незначительные изменения в форматировании сообщения журнала нарушают ваши тесты.
- Тесты несовместимы с интернационализацией (переводом) вашего интерфейса регистрации. Тесты предполагают, что существует только один возможный текст сообщения и, следовательно, только один возможный человеческий язык.
Обратите внимание, что наличие в программном коде (возможно, реализующего некоторую бизнес-логику) прямого вызова интерфейса ведения текстового журнала - плохой дизайн (но, к сожалению, очень обычный). Код, отвечающий за бизнес-логику, также определяет политику ведения журнала и текст сообщений журнала. Он смешивает бизнес-логику с кодом пользовательского интерфейса (да, сообщения журнала являются частью пользовательского интерфейса вашей программы). Эти вещи должны быть отдельными.
Поэтому я рекомендую, чтобы бизнес-логика напрямую не генерировала текст сообщений журнала. Вместо этого пусть он делегируется объекту регистрации.
- Класс объекта ведения журнала должен предоставлять подходящий внутренний API, который ваш бизнес-объект может использовать для выражения события, которое произошло с использованием объектов вашей доменной модели, а не текстовых строк.
- Реализация вашего класса ведения журналов отвечает за создание текстовых представлений этих объектов домена и отрисовку подходящего текстового описания события, а затем пересылку этого текстового сообщения в низкоуровневую структуру ведения журналов (например, JUL, log4j или slf4j) .
- Ваша бизнес-логика отвечает только за вызов правильных методов внутреннего API вашего класса регистратора, передачу правильных доменных объектов для описания реальных событий, которые произошли.
- Ваш конкретный класс ведения журнала
implements
и interface
, который описывает внутренний API, который может использовать ваша бизнес-логика.
- Ваш класс (ы), который реализует бизнес-логику и должен выполнять ведение журнала, имеет ссылку на объект ведения журнала для делегирования. Классом ссылки является реферат
interface
.
- Используйте внедрение зависимостей для установки ссылки на регистратор.
Затем вы можете проверить, правильно ли ваши классы бизнес-логики сообщают интерфейсу ведения журнала о событиях, создав поддельный регистратор, который реализует внутренний API ведения журнала, и используя внедрение зависимостей на этапе настройки вашего теста.
Как это:
public class MyService {// The class we want to test
private final MyLogger logger;
public MyService(MyLogger logger) {
this.logger = Objects.requireNonNull(logger);
}
public void performTwiddleOperation(Foo foo) {// The method we want to test
...// The business logic
logger.performedTwiddleOperation(foo);
}
};
public interface MyLogger {
public void performedTwiddleOperation(Foo foo);
...
};
public final class MySl4jLogger: implements MyLogger {
...
@Override
public void performedTwiddleOperation(Foo foo) {
logger.info("twiddled foo " + foo.getId());
}
}
public final void MyProgram {
public static void main(String[] argv) {
...
MyLogger logger = new MySl4jLogger(...);
MyService service = new MyService(logger);
startService(service);// or whatever you must do
...
}
}
public class MyServiceTest {
...
static final class MyMockLogger: implements MyLogger {
private Food.id id;
private int nCallsPerformedTwiddleOperation;
...
@Override
public void performedTwiddleOperation(Foo foo) {
id = foo.id;
++nCallsPerformedTwiddleOperation;
}
void assertCalledPerformedTwiddleOperation(Foo.id id) {
assertEquals("Called performedTwiddleOperation", 1, nCallsPerformedTwiddleOperation);
assertEquals("Called performedTwiddleOperation with correct ID", id, this.id);
}
};
@Test
public void testPerformTwiddleOperation_1() {
// Setup
MyMockLogger logger = new MyMockLogger();
MyService service = new MyService(logger);
Foo.Id id = new Foo.Id(...);
Foo foo = new Foo(id, 1);
// Execute
service.performedTwiddleOperation(foo);
// Verify
...
logger.assertCalledPerformedTwiddleOperation(id);
}
}