java - 我可以出于测试目的修改字段的值吗?

标签 java reflection singleton junit4 java-7

我在出于测试目的更改字段值时遇到问题。

尽管存在单例问题,我还是想将它用于我的日志文件管理器。我实际上考虑过是否应该重新设计我的设计,但很高兴approved我的决定可能是好的。

不过我想测试一下。特别是如果代码在 Windows 和 Linux 上运行,也许在我现在想不到的其他地方运行。

这个想法是在当前目录中创建一个名为“teSTLogs”的测试目录,删除它并使用它。所以我可以确定我至少在当前目录中有写权限,或者我可以通知用户不会写入任何日志文件,或者我可以提示他选择另一个位置等。

在下面的代码中,我不断获取我的“logs”目录。我希望能够保留真正的“日志”(如果存在)。

为什么我无法更改 dirname 的值?

package com.home.app.logger.LogsMngr;
public class LogsMngr {
    private static class Holder {
        public static String dirname = "logs";
        public static final LogsMngr INSTANCE = new LogsMngr(dirname);
    }

    private LogsMngr(String dirname){
         createDirectory(dirname);
    }

    private createDirectory(String dirname) {
         // logic to create a directory on the filesystem
    }

    public static LogsMngr getInstance() {
        return Holder.INSTANCE;
    }
}


@RunWith(SeparateClassLoaderRunner.class)
public class Test {

    LogsMngr lmngr;

    @Test
    public void test() {
        try {
            // Remove existing directory
            TUtils.rmfDirectory("testlogs");

            // Set directory name
            String className = "com.home.app.logger.LogsMngr$Holder";
            Class[] memberClasses = LogsMngr.class.getDeclaredClasses();

            Field dirnamefield = null;

            for(int i=0; i<memberClasses.length; i++) {

                if(memberClasses[i].getName().equals(className)) {

                    dirnamefield = memberClasses[i].getField("dirname");
                }
            }
            //TODO catch NullPointerException because of dirname?

            // To prevent from IllegalAccessException:
            dirnamefield.setAccessible(true);

            // Set test directory
            dirnamefield.set(null, "testlogs");

            // Create
            lmngr = LogsMngr.getInstance();
        }
        catch(Exception e) {

            e.printStackTrace();
        }
    }
}

引用文献:

SeparateClassLoaderRunner.class

Java language docs

What's the proper way to test a class with private methods using JUnit?

Can I discover a Java class' declared inner classes using reflection?

The_solution_of_Bill_Pugh

解决方案:

package com.home.app.logger.LogsMngr;
public class LogsMngr {
    private static String dirname = "logs"; <-- moved out of Holder

    private static class Holder {
        public static final LogsMngr INSTANCE = new LogsMngr(dirname);
    }

    private LogsMngr(String dirname){
         createDirectory(dirname);
    }

    private createDirectory(String dirname) {
         // logic to create a directory on the filesystem
    }

    public static LogsMngr getInstance() {
        return Holder.INSTANCE;
    }
}


@RunWith(SeparateClassLoaderRunner.class)
public class Test {

    LogsMngr lmngr;

    @Test
    public void test() {
        try {
            // Remove existing directory
            TUtils.rmfDirectory("testlogs");

            Field dirnamefield = LogsMngr.class.getDeclaredField("dirname");

            // To prevent from IllegalAccessException:
            dirnamefield.setAccessible(true);

            // Set test directory
            dirnamefield.set(null, "testlogs");

            // Create
            lmngr = LogsMngr.getInstance();
        }
        catch(Exception e) {

            e.printStackTrace();
        }
    }
}

最佳答案

这一行:

Class[] memberClasses = LogsMngr.class.getDeclaredClasses();

将导致LogsMngr.Holder被初始化,导致INSTANCE字段也被初始化,因此之后随时更改dirname不会有任何影响。

要完成您想要完成的任务,您必须能够设置dirname字段,而无需首先初始化包含它的类,我相信这在Java中是不可能的,尽管我有兴趣在这一点上得到纠正。不过,我相信您只需将 dirnameHolder 移出并移入 LogsMngr 即可获得您想要的效果。然后只需将 LogsMngr.dirname 设置为您想要的任何值,然后再使用 LogsMngr.Holder 进行任何操作。

关于java - 我可以出于测试目的修改字段的值吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15352202/

相关文章:

c++ - 从类成员返回智能指针的正确方法?

java - 静态单例类变为 null

java - 如何阻止 jUnit 将时区数据从我的 PC 提取到测试中?

java - 我怎样才能进入我的记录器,每个 hibernate 查询的持续时间(以毫秒为单位)

java - 我们如何在 java 中使用数组元素作为计数器?

java - 如何在 Java 中获取方法的所有可能调用者 - 如调用层次结构

java - 动态命名对象?

java - 在 Java 中实现 Mixin?

java - 使用反射确定字段是否是我创建的类型

angular - 服务在 APP_INITIALIZER 之后实例化了两次