简体   繁体   English

我应该如何对使用 google guava 库的代码进行单元测试,尤其是 io package 中的代码?

[英]How should I unit test code that uses the google guava libraries, especially stuff in the io package?

A lot of the functionality in guava is provided by static methods.番石榴中的许多功能是由 static 方法提供的。 I haven't figured out how to merge the use of guava libraries and good Dependency Injection practice.我还没有弄清楚如何合并使用 guava 库和良好的依赖注入实践。

For example, if I were to use例如,如果我要使用

Files.readLines(File, Charset)

then I find I have a hard time writing a unit test which doesn't touch the filesystem, which I only like to do for integration testing.然后我发现我很难编写一个不涉及文件系统的单元测试,我只喜欢为集成测试做。

I guess it's possible that I could write an adapter for all of the ones I'm interested in?我想我可以为所有我感兴趣的适配器编写一个适配器? But that could possibly end up being a lot of work...但这可能最终会成为很多工作......

I find it odd that the guava libraries come from the same set of people that provide guice and write blog posts like this我觉得奇怪的是 guava 库来自提供 guice 和写这样的博客文章的同一组人

Ugh, the dreaded static methods.呃,可怕的 static 方法。 I have heard that JMockit is capable of mocking out statics, but I've never tried it myself.我听说 JMockit 能够输出静态数据,但我自己从未尝试过。 The solution I typically use is an Adapter.我通常使用的解决方案是适配器。

public class FilesAdapter {

    private final File file;

    public FilesAdapter( File file ) {
        this.file = file;
    }

    public List<String> readLines( Charset charset ) {
        return Files.readLines( file, charset );
    }
}

You can optionally have FilesAdapter implements an interface, although since this is a single purpose object, I typically wouldn't.您可以选择让FilesAdapter实现一个接口,但由于这是一个单一用途的 object,我通常不会。

GUICE is capable of injecting concrete objects, and mocking frameworks such as JMock2 & Mockito are able to mock concretes as well. GUICE 能够注入混凝土对象,并且 JMock2 和 Mockito 等 mocking 框架也能够模拟混凝土。 This is all a matter of academics and different people will have different opinions.这都是学术问题,不同的人会有不同的看法。

If you were using GUICE, you would wrap this guy in a factory for injection goodness.如果您使用的是 GUICE,您会将这个人包裹在工厂中以进行注射。

public class FilesAdapter {

    private final File file;

    @Inject
    protected FilesAdapter( @Assisted File file ) {
        this.file = file;
    }

    public List<String> readLines( Charset charset ) {
        return Files.readLines( file, charset );
    }

    public interface Factory {
        FilesAdapter create( File file );
    }
}

We provided the common.io library as a stopgap until you finally get a real, proper filesystems API in JDK 7. That library will be interface-based and very testing-friendly.我们提供了 common.io 库作为权宜之计,直到您最终在 JDK 7 中获得一个真正的、适当的文件系统 API。该库将基于接口并且非常易于测试。

Powermock allows you to mock static methods . Powermock 允许您模拟 static 方法

We're using it here and there, and I can testify that it works.我们到处使用它,我可以证明它有效。

You can isolate the static dependency into it's own method (which can then be over-ridden for test purposes, as shown below).您可以将 static 依赖项隔离到它自己的方法中(然后可以将其覆盖以进行测试,如下所示)。 Usually, the isolated method would have 'protected' visibility.通常,隔离方法将具有“受保护”的可见性。

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.io.File;

import org.junit.Before;
import org.junit.Test;

public class FileUtilsTest {

    private static final String[] TEST_LINES = { "test 1", "test 2", "test 3" };
    private static final File TEST_FILE = new File("/foo/bar");
    private FileUtils fileUtils;

    /**
     * Configure reusable test components
     */
    @Before
    public void setUp() {
        fileUtils = spy(new FileUtils());
        when(fileUtils.getLines(any(File.class))).thenReturn(TEST_LINES);
    }

    /**
     * Ensure the {@link FileUtils#countLines(File)} method returns the correct line count (without hitting the
     * file-system)
     */
    @Test
    public void testCountLines() {
        assertEquals(3, fileUtils.countLines(TEST_FILE));
    }

    /**
     * The class we want to test
     */
    public static class FileUtils {

        /**
         * Method that we want to test
         */
        public int countLines(final File file) {
            return getLines(file).length;
        }

        /**
         * Static dependency isolated to a method (that we can override, for test purposes)
         */
        public String[] getLines(final File file) {
            return Files.readLines(file);
        }
    }

    /**
     * Poorly written utility class with hard-to-test static methods
     */
    public static class Files {

        public static String[] readLines(final File file) {
            // In reality, this would hit the file-system
            return new String[] { "line 1" };
        }
    }

}

Provide your own stubs for the static methods you call and let the classloader 'inject' them for you.为您调用的 static 方法提供您自己的存根,并让类加载器为您“注入”它们。

In general, it's not really a problem to solve with DI and mocks.一般来说,使用 DI 和模拟来解决并不是真正的问题。 Some dependencies just aren't worth explicitly injecting if they're common enough to be assumed to be dependencies and you're confident enough that they work as advertised.如果某些依赖项足够普遍以被假定为依赖项,并且您有足够的信心相信它们可以像宣传的那样工作,那么它们就不值得显式注入。 I think Guava falls into this category.我认为番石榴属于这一类。 Likewise, mocks are great at eliding state manipulations, which static methods shouldn't be mucking around with anyway, so you're not going to gain much even if you manage to mock them out.同样,模拟非常擅长消除 state 操作,static 方法无论如何都不应该乱用,所以即使你设法模拟它们,你也不会获得太多。

First of all, why not just let your unit tests touch the file system?首先,为什么不让你的单元测试接触文件系统呢? Usually, reading small local test-specific files in a few tests adds no perceptible delay to the test run.通常,在几个测试中读取小型本地测试特定文件不会给测试运行增加可察觉的延迟。

But if you really don't want to touch the file system, then simply mock it.但是如果你真的不想接触文件系统,那么就简单地模拟它。 For example, the following JMockit -based test should work:例如,以下基于JMockit的测试应该可以工作:

@Test
public void someTest()
{
    new Expectations() {
        @Mocked Files files;
        @Input  List<String> lines = asList("Line 1", "Another line", "...");
    };

    // Called from code under test:
    List<String> lines = Files.readLines(someFile, charSet);
    // These "lines" will be the same as the "@Input" lines.

    // asserts...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM