Ръководство за библиотеката на системните правила

1. Общ преглед

Понякога, когато пишем модулни тестове, може да се наложи да тестваме код, който взаимодейства директно с класа System . Обикновено в приложения като инструменти за команден ред, които извикват System.exit директно или четат аргументи, използвайки System.in .

В този урок ще разгледаме най-често срещаните характеристики на изчистена външна библиотека, наречена Системни правила, която предоставя набор от правила JUnit за тестване на код, който използва системния клас .

2. Зависимости на Maven

Първо, нека добавим зависимостта на системните правила към нашия pom.xml :

 com.github.stefanbirkner system-rules 1.19.0 

Ще добавим и системна ламбда зависимост, която се предлага и от Maven Central:

 com.github.stefanbirkner system-lambda 1.1.0 

Тъй като системните правила не поддържат директно JUnit5 , добавихме последната зависимост. Това осигурява методите на System Lambda обвивка, които да се използват при тестове. Има алтернатива, базирана на разширения, наречена System Stubs.

3. Работа със системни свойства

За бързо обобщение платформата Java използва обект Properties, за да предостави информация за локалната система и конфигурация. Можем лесно да разпечатаме свойствата:

System.getProperties() .forEach((key, value) -> System.out.println(key + ": " + value));

Както виждаме, свойствата включват информация като текущия потребител, текущата версия на Java среда за изпълнение и разделител на име на път на файл:

java.version: 1.8.0_221 file.separator: / user.home: /Users/baeldung os.name: Mac OS X ...

Също така можем да зададем свои собствени системни свойства, като използваме метода System.setProperty . Трябва да се внимава при работа със системни свойства от нашите тестове, тъй като тези свойства са JVM-глобални.

Например, ако зададем системно свойство, трябва да гарантираме, че възстановяваме свойството до първоначалната му стойност, когато тестът приключи или ако възникне повреда. Това понякога може да доведе до тромава настройка и разрушаване на кода. Ако обаче пренебрегнем това, това може да доведе до неочаквани странични ефекти в нашите тестове.

В следващия раздел ще видим как можем да предоставим, почистим и да се уверим, че възстановяваме стойностите на системните свойства, след като тестовете ни завършат по кратък и лесен начин.

4. Предоставяне на системни свойства

Нека си представим, че имаме системно свойство log_dir, което съдържа местоположението, където трябва да бъдат записани нашите дневници и нашето приложение задава това местоположение, когато се стартира:

System.setProperty("log_dir", "/tmp/baeldung/logs");

4.1. Предоставете един имот

Сега нека помислим, че от нашия тестов блок искаме да предоставим различна стойност. Можем да направим това, като използваме правилото ProvideSystemProperty :

public class ProvidesSystemPropertyWithRuleUnitTest { @Rule public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources"); @Test public void givenProvideSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() { assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir")); } // unit test definition continues } 

Използвайки правилото ProvideSystemProperty , можем да зададем произволна стойност за дадено системно свойство за използване от нашите тестове. В този пример ние задаваме свойството log_dir на нашата директория за тест / ресурси и от нашия единичен тест просто твърдим, че стойността на тестовото свойство е предоставена успешно.

Ако след това отпечатаме стойността на свойството log_dir, когато нашият тестов клас завърши:

@AfterClass public static void tearDownAfterClass() throws Exception { System.out.println(System.getProperty("log_dir")); } 

Виждаме, че стойността на имота е възстановена до първоначалната му стойност:

/tmp/baeldung/logs

4.2. Предоставяне на множество свойства

Ако трябва да предоставим множество свойства, можем да използваме метода и , за да веригираме толкова стойности на свойствата, колкото изискваме за нашия тест:

@Rule public final ProvideSystemProperty providesSystemPropertyRule = new ProvideSystemProperty("log_dir", "test/resources").and("another_property", "another_value")

4.3. Предоставяне на свойства от файл

По същия начин, ние също имаме възможността да предоставяме свойства от файл или ресурс на classpath, използвайки правилото ProvideSystemProperty :

@Rule public final ProvideSystemProperty providesSystemPropertyFromFileRule = ProvideSystemProperty.fromResource("/test.properties"); @Test public void givenProvideSystemPropertyFromFile_whenGetName_thenNameIsProvidedSuccessfully() { assertEquals("name should be provided", "baeldung", System.getProperty("name")); assertEquals("version should be provided", "1.0", System.getProperty("version")); }

В горния пример предполагаме, че имаме файл test.properties на пътя на класа:

name=baeldung version=1.0

4.4. Предоставяне на свойства с JUnit5 и Lambdas

Както споменахме по-рано, бихме могли да използваме и системата Lambda версия на библиотеката, за да внедрим тестове, съвместими с JUnit5.

Нека да видим как да приложим нашия тест, използвайки тази версия на библиотеката:

@BeforeAll static void setUpBeforeClass() throws Exception { System.setProperty("log_dir", "/tmp/baeldung/logs"); } @Test void givenSetSystemProperty_whenGetLogDir_thenLogDirIsProvidedSuccessfully() throws Exception { restoreSystemProperties(() -> { System.setProperty("log_dir", "test/resources"); assertEquals("log_dir should be provided", "test/resources", System.getProperty("log_dir")); }); assertEquals("log_dir should be provided", "/tmp/baeldung/logs", System.getProperty("log_dir")); }

В тази версия можем да използваме метода restoreSystemProperties за изпълнение на даден оператор. Вътре в това изявление можем да настроим и предоставим стойностите, които изискваме за нашите системни свойства . Както можем да видим, след като този метод завърши изпълнението, стойността на log_dir е същата като преди / tmp / baeldung / logs .

За съжаление няма вградена поддръжка за предоставяне на свойства от файлове, използващи метода restoreSystemProperties .

5. Изчистване на системни свойства

Понякога може да искаме да изчистим набор от системни свойства, когато тестът ни стартира и да възстановим първоначалните им стойности, когато тестът приключи, независимо дали преминава или не.

За тази цел можем да използваме правилото ClearSystemProperties :

@Rule public final ClearSystemProperties userNameIsClearedRule = new ClearSystemProperties("user.name"); @Test public void givenClearUsernameProperty_whenGetUserName_thenNull() { assertNull(System.getProperty("user.name")); }

Системното свойство user.name е едно от предварително дефинираните системни свойства, което съдържа името на потребителския акаунт. Както се очаква в горния модулен тест, изчистваме това свойство и проверяваме дали е празно от нашия тест.

Удобно е, че можем да предадем множество имена на свойства на конструктора ClearSystemProperties .

6. Подигравателна система.в

От време на време може да създаваме интерактивни приложения от командния ред, които да четат от System.in .

За този раздел ще използваме много прост пример, който чете име и фамилия от стандартния вход и ги обединява заедно:

private String getFullname() { try (Scanner scanner = new Scanner(System.in)) { String firstName = scanner.next(); String surname = scanner.next(); return String.join(" ", firstName, surname); } }

System Rules contains the TextFromStandardInputStream rule which we can use to specify the lines that should be provided when calling System.in:

@Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() { systemInMock.provideLines("Jonathan", "Cook"); assertEquals("Names should be concatenated", "Jonathan Cook", getFullname()); }

We accomplish this by using the providesLines method, which takes a varargs parameter to enable specifying more than one value.

In this example, we provide two values before calling the getFullname method, where System.in is referenced. Our two provided line values will be returned each time we call scanner.next().

Let's take a look at how we can achieve the same in a JUnit 5 version of the test using System Lambda:

@Test void givenTwoNames_whenSystemInMock_thenNamesJoinedTogether() throws Exception { withTextFromSystemIn("Jonathan", "Cook").execute(() -> { assertEquals("Names should be concatenated", "Jonathan Cook", getFullname()); }); }

In this variation, we use the similarly named withTextFromSystemIn method, which lets us specify the provided System.in values.

It is important to mention in both cases that after the test finishes, the original value of System.in will be restored.

7. Testing System.out and System.err

In a previous tutorial, we saw how to use System Rules to unit test System.out.println().

Conveniently, we can apply an almost identical approach for testing code which interacts with the standard error stream. This time we use the SystemErrRule:

@Rule public final SystemErrRule systemErrRule = new SystemErrRule().enableLog(); @Test public void givenSystemErrRule_whenInvokePrintln_thenLogSuccess() { printError("An Error occurred Baeldung Readers!!"); Assert.assertEquals("An Error occurred Baeldung Readers!!", systemErrRule.getLog().trim()); } private void printError(String output) { System.err.println(output); }

Nice! Using the SystemErrRule, we can intercept the writes to System.err. First, we start logging everything written to System.err by calling the enableLog method on our rule. Then we simply call getLog to get the text written to System.err since we called enableLog.

Now, let's implement the JUnit5 version of our test:

@Test void givenTapSystemErr_whenInvokePrintln_thenOutputIsReturnedSuccessfully() throws Exception { String text = tapSystemErr(() -> { printError("An error occurred Baeldung Readers!!"); }); Assert.assertEquals("An error occurred Baeldung Readers!!", text.trim()); }

In this version, we make use of the tapSystemErr method, which executes the statement and lets us capture the content passed to System.err.

8. Handling System.exit

Command-line applications typically terminate by calling System.exit. If we want to test such an application, it is likely that our test will terminate abnormally before it finishes when it encounters the code which calls System.exit.

Thankfully, System Rules provides a neat solution to handle this using the ExpectedSystemExit rule:

@Rule public final ExpectedSystemExit exitRule = ExpectedSystemExit.none(); @Test public void givenSystemExitRule_whenAppCallsSystemExit_thenExitRuleWorkssAsExpected() { exitRule.expectSystemExitWithStatus(1); exit(); } private void exit() { System.exit(1); }

Using the ExpectedSystemExit rule allows us to specify from our test the expected System.exit() call. In this simple example, we also check the expected status code using the expectSystemExitWithStatus method.

We can achieve something similar in our JUnit 5 version using the catchSystemExit method:

@Test void givenCatchSystemExit_whenAppCallsSystemExit_thenStatusIsReturnedSuccessfully() throws Exception { int statusCode = catchSystemExit(() -> { exit(); }); assertEquals("status code should be 1:", 1, statusCode); }

9. Заключение

За да обобщим, в този урок разгледахме подробно библиотеката на системните правила.

Първо започнахме с обяснение как да тестваме код, който използва системни свойства. След това разгледахме как да тестваме стандартния изход и стандартния вход. Накрая разгледахме как да се справим с кода, който извиква System.exit от нашите тестове.

Библиотеката на системните правила също осигурява поддръжка за предоставяне на променливи на околната среда и специални мениджъри за сигурност от нашите тестове . Не забравяйте да проверите пълната документация за подробности.

Както винаги, пълният изходен код на статията е достъпен в GitHub.