关于如何测试BufferedReader和FileReader的建议,它们接受字符串并将它们放入ArrayList中



我有一个类,它有一个方法,逐行读取文本文件,然后将每一行放入字符串的ArrayList。下面是我的代码:

public class ReadFile {
    public List<String> showListOfCourses() throws IOException {
        String filename = "countriesInEurope.txt";
        FileReader fr = new FileReader(filename);
        BufferedReader br = new BufferedReader(fr);
        List<String> courseList = new ArrayList<>();
        while (true) {
            String line = br.readLine();
            if (line == null) {
                break;
            }
            courseList.add(line);
        }
        br.close();
        return courseList;
    }
}

我希望有一些关于如何通过Mockito测试这个方法的建议,包括安排/行为/断言。我听说读者涉及文本文件可能是棘手的测试,并创建一个临时文件是不是最佳实践,因为它占用内存?如有任何建议,我将不胜感激。

由于文件名countriesInEurope.txt是硬编码在您的实现,这是不可测试的。使其可测试的一个好方法是重构该方法,以Reader作为参数:

public List<String> showListOfCourses(Reader reader) throws IOException {
    BufferedReader br = new BufferedReader(reader);
    List<String> courseList = new ArrayList<>();
    // ...
    return courseList;
}

你的主实现可以传递一个FileReader给this。另一方面,在测试时,您的测试方法可以传递一个StringReader实例,这很容易创建一个示例内容作为一个简单字符串,不需要临时文件,例如:

@Test
public void showListOfCourses_should_read_apple_orange_banana() {
    Reader reader = new StringReader("applenorangenbanana");
    assertEquals(Arrays.asList("apple", "orange", "banana"), showListOfCourses(reader));
}

顺便说一句,这个方法的名字不好,因为它没有"显示"任何东西。readListOfCourses更有意义

有问题的测试行是

String filename = "countriesInEurope.txt";
FileReader fr = new FileReader(filename);

因为
  1. 文件名是硬编码的,不能为测试
  2. 替换
  3. FileReader使用底层系统io,这是很难模拟的

尽管如此,还是有一些方法可以使你的代码可测试

1。引入一个构造函数来参数化ReadFile对象创建

public class ReadFile {
    private String filename;
    public ReadFile(String filename) {
        this.filename = filename;
    }
    public List<String> showListOfCourses() throws IOException {
        FileReader fr = new FileReader(filename);
        ...
        return courseList;
    }
}

在您的测试中,您可以创建一个ReadFile对象,它使用一些测试文件。使用这种策略,您可以实现100%的行覆盖率,但是您的测试必须访问文件系统上的真实文件。所以你不能把它写成一个纯粹的单元测试。

2。将有问题的行提取到一个可重写的方法

public class ReadFile {
    public List<String> showListOfCourses() throws IOException {
        Reader courcesReader = openCoursesFile();
        BufferedReader br = new BufferedReader(courcesReader);
        List<String> courseList = new ArrayList<>();
        // ...
        return courseList;
    }
    protected Reader openCoursesFile() throws FileNotFoundException {
       return new FileReader("countriesInEurope.txt");
    }
}

在您的测试中,您可以子类化ReadFile类并覆盖Reader openCoursesFile()方法。例如

@Test
public void showCources() throws IOException {
    ReadFile readFile = new ReadFile() {
        protected Reader openCoursesFile() throws java.io.FileNotFoundException {
            return new StringReader("GermanynItalynFrance");
        };
    };
    List<String> showListOfCourses = readFile.showListOfCourses();
    Assert.assertEquals(Arrays.asList("Germany", "Italy", "France"), showListOfCourses);
}
使用此策略,您可以将测试编写为纯单元测试,因为您用StringReader(仅在内存中)替换了文件访问。唯一不能测试的行是
return new FileReader("countriesInEurope.txt");

所以没有100%的行覆盖率

编辑

3。引入构造函数并向其传递Reader对象创建

public class ShowListOfCoursesReader {
    private Reader reader;
    public ReadFile(Reader reader) {
        this.reader = reader;
    }
    public List<String> read() throws IOException {
        // read with reader and transform each line to the
        // output object.
        // In your case just the line you read, but it could
        // also be a date or a address object
        ...
        return courseList;
    }
}
在您的测试中,您可以创建一个ShowListOfCoursesReader对象,该对象使用通过的读取器。阅读器也可以是StringReader。使用此策略,您可以实现100%的行覆盖率和纯单元测试。

提取依赖项,以便在测试时可以模拟/存根并注入它们。它还有助于将类的作用域缩小到它的核心职责。

public class CourseReader {
    private BufferedReader reader;
    public CourseReader(BufferedReader br) {
        this.reader = br;
    }
    public List<String> GetListOfCourses() throws IOException {
        List<String> courseList = new ArrayList<>();
        String line;
        while((line = reader.readLine()) != null) {   
            courseList.add(line);
        }
        return courseList;
    }    
}

现在要测试这个类,可以预先安排依赖项。

@Test
public void GetListOfCourses_should_read_3_Courses() {
    //Arrange
    List<String> expected = Arrays.asList("course1", "course2", "course3");
    Reader reader = new StringReader("course1ncourse2ncourse3");
    BufferedReader bufferedReader = new BufferedReader(reader);
    CourseReader sut = new CourseReader(bufferedReader);
    //Act
    List<String> actual = sut.GetListOfCourses();
    //Assert
    assertEquals(expected, actual);
}

可以进一步重构以抽象出实现细节。

public interface IReaderWrapper {
    String readLine();
    void close();
}

并使用它作为依赖项

public class CourseReader {
    private IReaderWrapper reader;
    public CourseReader(IReaderWrapper reader) {
        this.reader = reader;
    }
    public List<String> GetListOfCourses() throws IOException {
        List<String> courseList = new ArrayList<>();
        String line;
        while((line = reader.readLine()) != null) {   
            courseList.add(line);
        }
        reader.close();
        return courseList;
    }    
}

这样在测试时只需要模拟接口。接口的实现将考虑如何实际读取数据。

@Test
public void GetListOfCourses_should_read_3_Courses() {
    //Arrange
    List<String> expected = Arrays.asList("course1", "course2", "course3");
    IReaderWrapper mockedReader = mock(IReaderWrapper.class);
    when(mockedReader.readLine())
        .thenReturn(expected[0], expected[1], expected[2], null);
    CourseReader sut = new CourseReader(mockedReader);
    //Act
    List<String> actual = sut.GetListOfCourses();
    //Assert
    assertEquals(expected, actual);
    //verify that the close method was called.
    verify(mockedReader).close();
}

嗯,看起来你是在测试框架,在这个特殊的例子中是JDK。我会考虑更方便的API:

Files.readAllLines(Paths.get("blablabla.txt"));

Files.lines(Paths.get("blablabla.txt"));

和cover由测试的更高抽象层-使用字符串列表的地方。

相关内容

  • 没有找到相关文章

最新更新