如何在Android单元测试中读取仅用于测试的文件

35

我正在编写一些单元测试来测试我的Android应用程序,这些测试需要读取一些文件。由于这些文件仅供测试使用,我不希望它们出现在我的res文件夹中,因为我不希望它们最终出现在我的.apk文件中。

我想要做类似于这个问题的事情,但是使用新添加的(在Gradle 1.1中)单元测试支持(与仪器测试相对)。

我的项目结构如下:

/app
   /src
      /main
         /java/my.module/myClass.java
         /res/production_resources_go_here
      /test
         /java/my.module/myClassTest.java
         /resources/testFile.txt

为了成功读取testFile.txt,我的myClassTest测试应该长什么样?


我认为我们需要看更多的myClass.java代码才能对此发表评论。您想将测试testFile.txt传递给在myClass中处理文本文件的任何方法,然后检查(使用AssertJ进行断言)结果是否符合预期。研究Robolectric和Mockito可能会给您一些想法。 - TTransmit
myClass.java 不重要。假设它有一个消耗 InputStream 的方法,我希望流中包含我的测试文件内容。这样说清楚了吗? - Krzysztof Kozmic
如果您想对此进行一些黑客操作,那么请阅读我的帖子。您可以在手机内部打开apk文件并将其作为流进行阅读。 - Randyka Yudhistira
6个回答

61

在提出这个问题的时候,它根本不起作用。 幸运的是,此后已经得到解决。

您必须将文本文件放在app/src/test/resources文件夹下,就像OP试图做的那样。 此外,它必须与测试类位于同一软件包中。 因此,如果您在app/src/test/java文件夹中的com.example.test软件包中有ReadFileTest.java,则您的测试文件应位于app/src/test/resources/com/example/test中。

test folder structure

然后,您可以像这样访问您的文本文件:

getClass().getResourceAsStream("testFile.txt")

这将为文本文件打开一个InputStream。如果你不确定如何处理它,以下是一些你可以使用它的方式:读取/将一个InputStream转换为字符串


就我所知,现在这个程序可以正常工作了。如果有兴趣的话,我可以再次检查并编辑答案,以展示如何加载文件。 - Marcin Koziński
2
如果可以的话,我会给这个点赞100次。关键是要把路径搞对。谢谢,谢谢!:D - JRG-Developer
9
如果某人在获取 InputStream 时遇到返回 null 的问题,可以在路径前加上斜杠,例如 getClass().getResourceAsStream("/testFile.txt")。请注意,本翻译保留了原文的意思和语气,并尽可能使翻译通俗易懂。 - Youngjae
4
对于那些输入流为空的人,这里有一个额外的提示:在创建资源文件夹路径时,确保不要添加点号(例如com.example.test)。使用点号创建时,Android Studio会自动将文件夹创建在Java文件夹内部,但对于资源文件夹则不会。请注意避免这个问题。 - Ismail Iqbal
5
在 Kotlin 中,使用 this.javaClass.getResourceAsStream("testFile.txt") 可以获取名为 "testFile.txt" 的文件的输入流。 - user1960422
显示剩余5条评论

19

请将以下内容添加至你的build.gradle文件:

android {
    sourceSets {
        test {
           resources.srcDirs += ['src/test/resources']
        }
        androidTest {
           resources.srcDirs += ['src/androidTest/resources']
        }
    }
}

如果要让资源能够被单元测试访问,请将文件添加到:src/test/resources。而对于仪器化测试,请将文件添加到:src/androidTest/resources


我在Android测试中使用java.nio.file.Paths.get("foo", "bar")读取json文件时遇到了问题,因为Paths指向了.idea/foo/bar,而使用test {resources.srcDirs += ['src/test/resources']}解决了我的问题,非常感谢!!! - Hermandroid

13

在 @Deepansu 的回答之后,我将 TestAndroidTest 的测试数据合并到了 {project root}/sampledata 目录下。这是 Android Studio New > Sample Data Directory 命令的默认位置。

1. 在你的项目中,右键点击并选择 New > Sample Data Directory。这将在 app 中创建一个 sampledata 目录,该目录与 build.gradle 文件、srcbuild 目录具有相同的层次结构。

2.build.gradle 中添加以下脚本;

android {
    sourceSets {
        test {
           resources.srcDirs += ['sampledata']
        }
        androidTest {
           resources.srcDirs += ['sampledata']
        }
    }
}

3. 在 Gradle 中进行同步。

现在,我们可以将测试资源文件放置在一个目录中,并在测试环境中同时使用它们。

您可以按如下方式读取文件:

// use somewhere at test logic. Note that slash symbol is required (or not).
jsonObject = new JSONObject(readFromFile("/testFile.json"));

// a method to read text file.
public String readFromFile(String filename) throws IOException {
    InputStream is = getClass().getResourceAsStream(filename);
    StringBuilder stringBuilder = new StringBuilder();
    int i;
    byte[] b = new byte[4096];
    while ((i = is.read(b)) != -1) {
        stringBuilder.append(new String(b, 0, i));
    }
    return stringBuilder.toString();
}

2
更好、更通用的方法如下。它也适用于其他项目类型,例如Spring。另一个好处是您不必将文件放置在包结构中的特定位置。不应该对包结构进行依赖。如果您有一个良好的文件名(带有测试文件夹结构),那么这使得代码更易读。
this.getClass().getClassLoader().getResourceAsStream(filename);

一个例子:
private String htmlFromTestResourceFile(String filename) {
    try {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(filename);
        return IOUtils.toString(inputStream, "UTF-8");
    } catch( Exception e) {
        e.printStackTrace();
        return null;
    }
}

1
我正在进行一个与你提到的类似结构的项目。 我将所有服务器响应放在一个文件中,该文件位于resources文件夹下,例如app/src/test/resources/BookingInfo.json
所有的Java测试文件都放在app/src/test/java/PACKAGE_NAME下,与你所说的类似。我在一个包下有一个Fixture类,该类包含资源的名称:
@SuppressWarnings("nls")
public final class Fixtures
{
    public static final String GET_ANNOUNCEMENT = "GetAnnouncement.json";
...
}

最后是FixtureUtils类,该类的一个方法负责读取资源文件并返回结果。
import java.nio.file.Files;
import java.nio.file.Paths;

public class FixtureUtils
    {
        public static final AFixture fixtureFromName(final String fixtureName)
        {
            final String fixtureString = FixtureUtils.stringFromAsset(fixtureName);

            if (StringUtils.isEmpty(fixtureString))
            {
                return null;
            }

            final Gson gson = new Gson();
            final AFixture aFixture = gson.fromJson(fixtureString, AFixture.class);
            return aFixture;
        }

        private static final String stringFromAsset(final String filename)
        {
            try
            {
                final URL resourceURL = ClassLoader.getSystemResource(filename);
                if (resourceURL == null)
                {
                    return null;
                }

                final String result = new String(Files.readAllBytes(Paths.get(resourceURL.toURI())),
                                Charset.forName("UTF-8")); //$NON-NLS-1$
                return result;
            }
            catch (final Exception e)
            {
                e.printStackTrace();
            }

            return null;
        }

        private FixtureUtils()
        {
            // Ensure Singleton
        }
    }

一个 AFixture 类看起来像这样:

public class AFixture
{
    public List<JsonElement> validItems;
    public List<JsonElement> invalidItems;

    public AFixture()
    {
        super();
    }

    public List<JsonElement> getInvalidItems()
    {
        return this.invalidItems;
    }

    public List<JsonElement> getValidItems()
    {
        return this.validItems;
    }

    public void setInvalidItems(final List<JsonElement> invalidItems)
    {
        this.invalidItems = invalidItems;
    }

    public void setValidItems(final List<JsonElement> validItems)
    {
        this.validItems = validItems;
    }

}

注意:如果我没记错的话,JDK8已经移除了java.nio.file,但是如果你使用的是JDK7,那么没有问题。如果你正在使用JDK8,则只需要像FileInputStream(老式风格)或Scanner一样更改stringFromAsset方法即可。

-3

正确的做法是将文件放在您的assets文件夹中。不过,请注意,assets文件夹的内容将被添加到apk文件中。

InputStream is = resources.getAssets().open("test.txt");

你可以欺骗这个系统并遍历到项目中的任何其他文件。请确保在项目的iml文件指定的位置(例如src/main/assets)中创建一个assets目录。

InputStream is = resources.getAssets().open("../../test/resources/testFile.txt");

一个获取资源的例子是:
Resources resources = new Activity().getResources();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接