| Симуляция реального мира в песочнице |
В это же время появилась интересная задача связанная с доступом к файловой системе. Алгоритм:
1. получаем список файлов
2. каким-то образом фильтруем
3. обрабатываем отфильтрованные файлы.
Работа с ФС (по крайней мере в 6-й java) сосредоточена в классе File: list, delete, работа с путями. По сути эти методы относятся к сервису. Хочется иметь интерфейс чтобы убрать зависимость и получить возможность для подмены реализации, ну или хотя бы подмены mock-ами для тестирования.
Конечно можно выйти из положения, создавая нужную иерархию в определенном месте файловой системы и работать с настоящим доступом, но что, если хочется просто протестировать алгоритм.
Постановка задачи
По заданному путиудалить пять файлов
с учетом сортировки по возрастанию
имя которых начинается с цифр
содержащие первой строкой ШИББОЛЕТ.
Решение
Сервис
public class FileService {
/**
* Get files in path
*/
public Collection dir(File path) {
return Arrays.asList(path.listFiles());
}
/**
* Delete path
*/
public void delete(File file) {
if (!file.delete()) {
log.debug("Cannot delete " + file.getPath());
}
}
/**
* Open file
*/
public InputStream openStream(File path) throws FileNotFoundException {
return new FileInputStream(path);
}
public Reader openReader(File path) throws FileNotFoundException {
return new FileReader(path);
}
public boolean isDir(File path) {
return path.isDirectory();
}
}
Фильтрация
Фильтрация осуществляется при помощи удобнейшего FluentIterable:public static Iterablefilter(FileService fileService, File dir, int count, String mask, String prefix) { return FluentIterable .from(Ordering.natural().immutableSortedCopy(fileService.dir(dir))) .filter(new NamePredicate(mask)) .filter(new ContentPredicate(fileService, prefix)) .limit(count) .toImmutableList(); }
И пары предикатов для фильтрации по имени и содержимому. В предикат фильтрации по содержимому передается сервис файловой системы, плюс в Java 7 можно использовать очень удобный try для работы с Closeable ресурсами:
public class ContentPredicate implements Predicate{ private FileService fileService; private String prefix; public ContentPredicate(FileService fileService, String prefix) { this.fileService = fileService; this.prefix = prefix; } @Override public boolean apply(@Nullable File file) { try (BufferedReader br = new BufferedReader(fileService.openReader(file))) { return prefix.equals(br.readLine()); } catch (IOException ioe) { ContentPredicate.log.debug("Cannot read file " + file); } return false; } }
Тестирование
С помощью mockito подменяем возвращаемые значения при тестировании предикатов и фильтра. Сами тесты получаются очень простыми и быстрыми:
@Test
public void FilterTest() throws FileNotFoundException {
FileService fileService = Mockito.mock(FileService.class);
File dir = new File("/test/dir");
List files = Arrays.asList(
new File(dir, "100file.name"), // 0
new File(dir, "bl"), // 1
new File(dir, "099file.name"), // 2
new File(dir, "bla"), // 3
new File(dir, "098file.name"), // 4
new File(dir, "blah"), // 5
new File(dir, "097file.name")); // 6
Mockito.when(fileService.dir(dir)).thenReturn(files);
Mockito.when(fileService.openReader(files.get(6))).thenReturn(new StringReader("TEST"));
Mockito.when(fileService.openReader(files.get(4))).thenReturn(new StringReader("TEST"));
Mockito.when(fileService.openReader(files.get(2))).thenReturn(new StringReader("!!!!"));
Mockito.when(fileService.openReader(files.get(0))).thenReturn(new StringReader("TEST"));
Iterable result = Application.filter(fileService, dir, 3, "^\\d+.*$", "TEST");
Iterator i = result.iterator();
Assert.assertEquals(files.get(6), i.next());
Assert.assertEquals(files.get(4), i.next());
Assert.assertEquals(files.get(0), i.next());
Assert.assertFalse(i.hasNext());
}