examples update 2

This commit is contained in:
stvort
2024-10-28 20:24:04 +04:00
parent fe48ee4e6e
commit 928f8a956d
23 changed files with 680 additions and 0 deletions
@@ -0,0 +1,25 @@
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/
@@ -0,0 +1,7 @@
## Пример подмены консольного UI -> Swing UI, с помощью смены реализации IOService
* *В примере есть класс проводящий опрос PollService и использующий для этого IOService*
* *В зависимости от настройки use.console в application.yml создаются либо бины из пакета console, либо бины из пакета swing*
* *Т.к. один из этих бинов это реализация IOService, то получается либо консольный, либо оконный интерфейс программы без семы кода класса PollService*
* *Оконный интерфейс представлен классом PollMainForm, который взаимодействует с основной частью программы, через очереди в сервисе MessageSystem*
* *Через них же (с помощью IOService) основная часть программы взаимодействует с оконным интерфейсом*
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>ru.otus</groupId>
<artifactId>ioservice-replacing-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ioservice-replacing-example</name>
<description>IOService replacing example</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Для работы с Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,15 @@
package ru.otus.ioservice.example;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class IOServiceExampleSpringBootApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(IOServiceExampleSpringBootApplication.class)
.headless(false)
.run(args);
}
}
@@ -0,0 +1,6 @@
package ru.otus.ioservice.example.api;
public interface IOService {
void out(String message);
String readString();
}
@@ -0,0 +1,34 @@
package ru.otus.ioservice.example.console;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import ru.otus.ioservice.example.api.IOService;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Scanner;
@ConditionalOnProperty(name = "use.console", havingValue = "true")
@Service
public class StreamsIOService implements IOService {
private final PrintStream out;
private final Scanner sc;
public StreamsIOService(@Value("#{T(java.lang.System).out}") PrintStream out,
@Value("#{T(java.lang.System).in}") InputStream in) {
this.out = out;
this.sc = new Scanner(in);
}
@Override
public void out(String message) {
out.println(message);
}
@Override
public String readString() {
return sc.nextLine();
}
}
@@ -0,0 +1,18 @@
package ru.otus.ioservice.example.console;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.otus.ioservice.example.poll.PollService;
@ConditionalOnProperty(name = "use.console", havingValue = "true")
@Configuration
public class UIConfig {
@Bean
public CommandLineRunner starter (PollService pollService) {
return args -> pollService.poll();
}
}
@@ -0,0 +1,28 @@
package ru.otus.ioservice.example.poll;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.otus.ioservice.example.api.IOService;
@RequiredArgsConstructor
@Service
public class PollService {
private final IOService ioService;
public void poll() {
ioService.out("Началось!");
ioService.out("Как вас зовут?");
String name = ioService.readString();
ioService.out(String.format("Приятно познакомиться: %s", name));
ioService.out("Как ваши дела? Если хорошо, введите \"OK\"");
String res = ioService.readString();
if (res.equalsIgnoreCase("OK")) {
ioService.out("Это радует");
} else {
ioService.out("Ничего, скоро все наладится!");
}
ioService.out(String.format("До свидания %s", name));
}
}
@@ -0,0 +1,39 @@
package ru.otus.ioservice.example.swing;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Service;
import java.util.concurrent.LinkedBlockingQueue;
@ConditionalOnBean(SwingIOService.class)
@Service
public class MessageSystem {
private final LinkedBlockingQueue<String> inputQueue;
private final LinkedBlockingQueue<String> outputQueue;
public MessageSystem() {
inputQueue = new LinkedBlockingQueue<>();
outputQueue = new LinkedBlockingQueue<>();
}
@SneakyThrows
public void putToInputQueue(String message) {
inputQueue.put(message);
}
@SneakyThrows
public void putToOutputQueue(String message) {
outputQueue.put(message);
}
@SneakyThrows
public String takeFromInputQueue() {
return inputQueue.take();
}
@SneakyThrows
public String takeFromOutputQueue() {
return outputQueue.take();
}
}
@@ -0,0 +1,98 @@
package ru.otus.ioservice.example.swing;
import lombok.RequiredArgsConstructor;
import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
@RequiredArgsConstructor
public class PollMainForm extends JFrame {
private static final int MESSAGE_DISPLAY_DURATION = 1500;
private final JPanel contentPane;
private final JLabel inLabel;
private final JTextField outTextField;
private final JButton outBtn;
private final MessageSystem ms;
private final AtomicBoolean runFlag;
private final ExecutorService uiFlow;
public PollMainForm(MessageSystem ms) throws HeadlessException {
super("Poll");
this.ms = ms;
FontUIResource tahoma16 = new FontUIResource("Tahoma", Font.BOLD, 16);
UIManager.put("Label.font", tahoma16);
UIManager.put("TextField.font", tahoma16);
UIManager.put("Button.font", tahoma16);
runFlag = new AtomicBoolean(true);
uiFlow = Executors.newSingleThreadExecutor();
GridLayout layout = new GridLayout(3, 1);
contentPane = new JPanel(layout);
contentPane.setBackground(Color.decode("#546E7A"));
setContentPane(contentPane);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
inLabel = new JLabel("");
inLabel.setForeground(Color.decode("#FFAB40"));
contentPane.add(inLabel);
outTextField = new JTextField(15);
outTextField.setColumns(20);
outTextField.setForeground(Color.decode("#388E3C"));
contentPane.add(outTextField);
outBtn = new JButton("Ответить");
outBtn.addActionListener(e -> ms.putToInputQueue(outTextField.getText()));
contentPane.add(outBtn);
setOnCloseHandler();
setSize(450, 180);
}
void init() {
setLocationRelativeTo(null);
setVisible(true);
startUiFlow();
}
private void startUiFlow() {
uiFlow.execute(() -> {
while (runFlag.get()) {
try {
String msg = ms.takeFromOutputQueue();
synchronized (inLabel) {
inLabel.setText(msg);
}
Thread.sleep(MESSAGE_DISPLAY_DURATION);
} catch (InterruptedException e) {
return;
}
}
});
}
private void setOnCloseHandler() {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
runFlag.set(false);
ms.putToOutputQueue("exit");
uiFlow.shutdown();
e.getWindow().dispose();
}
});
}
}
@@ -0,0 +1,26 @@
package ru.otus.ioservice.example.swing;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import ru.otus.ioservice.example.api.IOService;
@ConditionalOnProperty(name = "use.console", havingValue = "false")
@RequiredArgsConstructor
@Service
public class SwingIOService implements IOService {
private final MessageSystem ms;
@SneakyThrows
@Override
public void out(String message) {
ms.putToOutputQueue(message);
}
@SneakyThrows
@Override
public String readString() {
return ms.takeFromInputQueue();
}
}
@@ -0,0 +1,30 @@
package ru.otus.ioservice.example.swing;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.otus.ioservice.example.poll.PollService;
import java.awt.*;
@ConditionalOnProperty(name = "use.console", havingValue = "false")
@Configuration
public class UIConfig {
@Bean
public CommandLineRunner starter(PollService pollService, MessageSystem ms) {
return args -> {
EventQueue.invokeLater(() -> {
try {
PollMainForm mainForm = new PollMainForm(ms);
mainForm.init();
} catch (Exception e) {
e.printStackTrace();
}
});
pollService.poll();
};
}
}
@@ -0,0 +1 @@
use.console: true
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.otus</groupId>
<artifactId>qa-lesson-demos</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>ioservice-replacing-example</module>
<module>unit-testing-pure-spring-parallel</module>
</modules>
</project>
@@ -0,0 +1,7 @@
## Пример "не удачного" и "работающего" варианта тестирования IOService в нескольких потоках
В примере демонстрируется:
* *возможность выполнения тестов в нескольких потоках (включается в junit-platform.properties\junit.jupiter.execution.parallel.enabled)*
* *частый подход к тестированию сервиса консольного ввода/вывода, через подмену System.in/out (ClosedIOServiceTest)*
* *проблемы возникающие при включении многопоточного режима исполнения тестов*
* *вариант решения проблем с многопоточным исполнением тестов, через получение потоков ввода/вывода в конструкторе сервиса (OpenedIOServiceTest)*
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>ru.otus</groupId>
<artifactId>unit-testing-pure-spring-parallel</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>unit-testing-pure-spring-parallel</name>
<description>Parallel unit testing example</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Это чистый Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Это для тестирования чистого Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JUnit5 (jupiter) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- AssetrtJ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,24 @@
package ru.otus.testing.example;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import ru.otus.testing.example.services.ClosedConsoleIOService;
import ru.otus.testing.example.services.IOService;
import ru.otus.testing.example.services.OpenedStreamsIOService;
@Configuration
@ComponentScan
public class TestingExampleSpringApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(TestingExampleSpringApplication.class);
IOService closedConsoleIOService = context.getBean(ClosedConsoleIOService.class);
IOService openedConsoleIOService = context.getBean(OpenedStreamsIOService.class);
closedConsoleIOService.out("Hello World");
openedConsoleIOService.out("Hello World 2");
}
}
@@ -0,0 +1,28 @@
package ru.otus.testing.example.services;
import org.springframework.stereotype.Service;
import java.io.PrintStream;
import java.util.Scanner;
@Service
public class ClosedConsoleIOService implements IOService {
private final PrintStream out;
private final Scanner sc;
public ClosedConsoleIOService() {
this.out = System.out;
this.sc = new Scanner(System.in);
}
@Override
public void out(String message) {
out.println(message);
}
@Override
public String readString() {
return sc.nextLine();
}
}
@@ -0,0 +1,6 @@
package ru.otus.testing.example.services;
public interface IOService {
void out(String message);
String readString();
}
@@ -0,0 +1,31 @@
package ru.otus.testing.example.services;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Scanner;
@Service
public class OpenedStreamsIOService implements IOService {
private final PrintStream out;
private final Scanner sc;
public OpenedStreamsIOService(@Value("#{ T(java.lang.System).in}") InputStream in,
@Value("#{ T(java.lang.System).out}") PrintStream out) {
this.out = out;
this.sc = new Scanner(in);
}
@Override
public void out(String message) {
out.println(message);
}
@Override
public String readString() {
return sc.nextLine();
}
}
@@ -0,0 +1,54 @@
package ru.otus.testing.example.services;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.assertj.core.api.Assertions.assertThat;
@Execution(ExecutionMode.CONCURRENT)
@DisplayName("Тест ClosedIOService")
class ClosedIOServiceTest {
private static final String TEXT_TO_PRINT1 = "Ничто не истинно";
private static final String TEXT_TO_PRINT2 = "Все дозволено";
private PrintStream backup;
private ByteArrayOutputStream bos;
private IOService ioService;
@BeforeEach
void setUp() {
System.out.println(Thread.currentThread().getName());
backup = System.out;
bos = new ByteArrayOutputStream();
System.setOut(new PrintStream(bos));
ioService = new ClosedConsoleIOService();
}
@AfterEach
void tearDown() {
System.setOut(backup);
}
@DisplayName("должно печатать \"" + TEXT_TO_PRINT1 + "\"")
@Test
void shouldPrintOnlyFirstCreedLine() throws InterruptedException {
ioService.out(TEXT_TO_PRINT1);
Thread.sleep(1000);
assertThat(bos.toString()).isEqualTo(TEXT_TO_PRINT1 + System.lineSeparator());
}
@DisplayName("должно печатать \"" + TEXT_TO_PRINT2 + "\"")
@Test
void shouldPrintOnlySecondCreedLine() {
ioService.out(TEXT_TO_PRINT2);
assertThat(bos.toString()).isEqualTo(TEXT_TO_PRINT2 + System.lineSeparator());
}
}
@@ -0,0 +1,46 @@
package ru.otus.testing.example.services;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.assertj.core.api.Assertions.assertThat;
@Execution(ExecutionMode.CONCURRENT)
@DisplayName("Тест OpenedConsoleIOService")
class OpenedIOServiceTest {
private static final String TEXT_TO_PRINT1 = "Ничто не истинно";
private static final String TEXT_TO_PRINT2 = "Все дозволено";
private ByteArrayOutputStream bos;
private IOService ioService;
@BeforeEach
void setUp() {
System.out.println(Thread.currentThread().getName());
bos = new ByteArrayOutputStream();
ioService = new OpenedStreamsIOService(System.in, new PrintStream(bos));
}
@DisplayName("должно печатать \"" + TEXT_TO_PRINT1 + "\"")
@Test
void shouldPrintOnlyFirstCreedLine() throws InterruptedException {
ioService.out(TEXT_TO_PRINT1);
Thread.sleep(1000);
assertThat(bos.toString()).isEqualTo(TEXT_TO_PRINT1 + System.lineSeparator());
}
@DisplayName("должно печатать \"" + TEXT_TO_PRINT2 + "\"")
@Test
void shouldPrintOnlySecondCreedLine() {
ioService.out(TEXT_TO_PRINT2);
assertThat(bos.toString()).isEqualTo(TEXT_TO_PRINT2 + System.lineSeparator());
}
}
@@ -0,0 +1,3 @@
#https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution
junit.jupiter.execution.parallel.enabled=false
junit.jupiter.execution.parallel.config.strategy=dynamic