Заменить слова из файла словами из словарного файла

Я работаю над проблемой кодирования, которая ставит передо мной задачу взять файл со словарем, прочитать из него, а затем заменить совпадающие слова из входного файла на то, что найдено в словаре.

У меня есть несколько проблем с моим кодом.

  1. Я не хочу чрезмерно разрабатывать задачу, которая кажется простой, но я также не хочу упрощать сложность определенных операций ввода-вывода или проводить для нее тривиальные тесты. В результате я создал один класс данных, который обрабатывает словарь из входного файла, и два служебных класса, реализующих два интерфейса. См. Изображение ниже.

введите описание изображения здесь

  1. При этом у меня есть опасения по поводу моего класса бизнес-логики FileContentReplacementService, потому что код кажется беспорядочным, и я чувствую, что есть некоторые вещи, которые можно улучшить, но я просто не могу понять их в данный момент. Ниже представлен полный класс:

FileContentReplacementService

import java.io.*;
import java.util.Map;

import Challenge2.model.FileBasedDictionary;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

public class FileContentReplacementService implements ContentReplacementService {

    private static final Logger LOGGER = LogManager.getLogger(FileContentReplacementService.class);
    private final RetrieverService retrieverService;
    private final FileBasedDictionary fileBasedDictionary;
    private final String outputFileName;

    public FileContentReplacementService(RetrieverService retrieverService,
                                         FileBasedDictionary fileBasedDictionary,
                                         String outputFileName) {
        this.retrieverService = retrieverService;
        this.fileBasedDictionary = fileBasedDictionary;
        this.outputFileName = outputFileName;
    }

    public boolean areMatchingWordsReplace(){
        String fileName = "src/main/resources/" + outputFileName;
        try {
            applyDictionaryToFile(fileBasedDictionary, retrieverService.getBufferedReader(), fileName);
        } catch (IOException ioe) {
            LOGGER.error(String.format("File not reachable n %s", ExceptionUtils.getStackTrace(ioe)));
            return false;
        }

        return true;
    }

    private void applyDictionaryToFile(FileBasedDictionary fileBasedDictionary,
                                       BufferedReader bufferedReader,
                                       String fileName) throws IOException
    {
        FileOutputStream fileOutputStream = new FileOutputStream(fileName);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        LineIterator lineIterator = new LineIterator(bufferedReader);
        while (lineIterator.hasNext()) {
            String line = lineIterator.nextLine();
            bufferedOutputStream.write(replaceWords(line, fileBasedDictionary.getDictionary()));
        }
    }

    private byte[] replaceWords(String line, Map<String, String> dictionary) {
        if (line.isEmpty()) {
            return "nn".getBytes();
        }

        for (Map.Entry<String, String> entry : dictionary.entrySet()) {
            line = line.replaceAll(entry.getKey(), entry.getValue());
        }

        return line.getBytes();
    }
}
  1. Есть ли способ вызвать исключение из блока try / catch FileContentReplacementService? Я сделал несколько попыток, но я мог вызвать его, только закрыв BufferedReader и затем сбросив его, но я могу сделать это только внутри метода. Мой тест, см. Ниже, в настоящее время не охватывает эту часть, и я хотел бы знать, есть ли что-то, что можно сделать, чтобы это исправить.

FileContentReplacementServiceTest

import Challenge2.model.FileBasedDictionary;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.io.*;
import java.util.Map;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class FileContentReplacementServiceTest {
    @Mock
    FileBasedDictionary fileBasedDictionary;
    @Mock
    FileRetrieverService fileRetrieverService;
    @Mock
    BufferedReader bufferedReader;

    FileContentReplacementService fileContentReplacementService;

    @Test
    public void givenValidFile_WhenWordsAreReplaced_ThenReplaceMatchingWordsIsCalled() throws IOException {
        when(fileRetrieverService.getBufferedReader()).thenReturn(bufferedReader);
        when(bufferedReader.readLine()).thenReturn("Test2", "", null);
        when(fileBasedDictionary.getDictionary()).thenReturn(Map.of("Test1", "Test2"));
        fileContentReplacementService = new FileContentReplacementService(fileRetrieverService, fileBasedDictionary, "\testOutput\test.txt");
        assertTrue(fileContentReplacementService.areMatchingWordsReplace());
    }
}
  1. Моя FileRetrieverService практически не поддается проверке, и, несмотря на все мои усилия, я просто не могу найти способ протестировать ее и поддерживать приличное качество кода и / или не нарушать ряд принципов SOLID.

FileRetrieverService

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Objects;

public class FileRetrieverService implements RetrieverService {

    private final String fileName;

    public FileRetrieverService(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public BufferedReader getBufferedReader(){
        return new BufferedReader(new InputStreamReader(Objects.requireNonNull(
                getClass().
                getClassLoader().
                getResourceAsStream(fileName))));
    }
}

FileRetrieverServiceTest

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.io.BufferedReader;

import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class FileRetrieverServiceTest{
    @Mock
    BufferedReader bufferedReader;
    @Mock
    FileRetrieverService fileRetrieverService;

    @Test
    public void givenFileName_WhenReturnBufferedReader_ThenReturnReader(){
        when(fileRetrieverService.getBufferedReader()).thenReturn(bufferedReader);
        assertNotNull(fileRetrieverService.getBufferedReader());
    }
    @Test(expected = NullPointerException.class)
    public void givenInvalidFileName_ThenFailFast(){
        doThrow(NullPointerException.class).when(fileRetrieverService).getBufferedReader();
        fileRetrieverService.getBufferedReader();
    }
}

Я был бы очень признателен, если бы кто-нибудь мог взглянуть на приведенный выше код и дать мне несколько указателей относительно того, какие улучшения можно сделать, чтобы сделать код более легко тестируемым и лучше спроектированным.

1 ответ
1

Я не думаю, что вам нужно тестировать все, что связано с открытием входного файла, его закрытием, обработкой исключений и т. Д. Я считаю, что для целей этой задачи вы хотите проверить свою логику и доверять этому FileReader или же FileInputStream и т.д. просто работают.

У меня был бы класс, который может читать формат словаря (не предоставляется, предположительно это FileBasedDictionary), и создайте тест этого класса, который производит ожидаемые Map<String, String>.

Затем создайте класс, который выполняет бизнес-логику (то есть замену слов), который может работать независимо от источника данных (за счет использования гибкого типа, такого как BufferedReader). Вы можете добавить тестовый пример, который демонстрирует, как создать действительный источник данных, а также демонстрирует бизнес-логику — тесты касаются не только документации / демонстрации, но и проверки.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Map;

public class DictionaryReplacementService {
    
    private Map<String, String> dictionary;
    
    public DictionaryReplacementService(Map<String, String> dictionary) {
        this.dictionary = dictionary;
    }

    public void replaceWords(BufferedReader input, BufferedWriter output)
            throws IOException {
        String line;
        while ((line = input.readLine()) != null) {
            for (Map.Entry<String, String> entry : dictionary.entrySet()) {
                line = line.replaceAll(entry.getKey(), entry.getValue());
            }
            output.write(line);
        }
    }
}
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.LinkedHashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;

class DictionaryReplacementServiceTest {

    @Test
    void test() throws IOException {
        String source = "It was the best of times, it was the blurst of times.";

        Map<String, String> dictionary = new LinkedHashMap<String, String>();
        dictionary.put("blurst", "worst");

        StringWriter outString = new StringWriter();
        try (BufferedReader in = new BufferedReader(new StringReader(source));
                BufferedWriter out = new BufferedWriter(outString)) {
            new DictionaryReplacementService(dictionary).replaceWords(in, out);
        }

        assertEquals("It was the best of times, it was the worst of times.",
                outString.toString());
    }

}

В этом случае пользователю вашего класса просто нужно будет заменить StringReader с FileReader если они этого хотели — DictionaryReplacementService не заинтересован в этом.

Последнее замечание об исключениях и возвращаемых значениях. Вместо того, чтобы вернуться true для успеха и false для неудачи я предпочитаю возвращать void для успеха и просто генерировать любые исключения, которые означают, что случай успеха невозможен (в моем случае try просто для обеспечения закрытия ресурсов — он не улавливает никаких исключений, а выброс исключения из теста JUnit означает сбой теста, чего мы и хотим).

Вы сказали, что не хотите упрощать ввод-вывод файлов и хотите проверить, что происходит, когда возникают проблемы, но это не проблема DictionaryReplacementService — нужно только сигнал что что-то пошло не так. В полной программе у вас будет логика на уровне пользовательского интерфейса для попытки восстановления (сообщить пользователю о проблеме, предложить ему повторить попытку или использовать другой файл и т. Д.). Вы не пишете этот слой пользовательского интерфейса сейчас, поэтому я не ожидал увидеть его сейчас тестами.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *