【问题标题】:Unit testing private fields单元测试私有字段
【发布时间】:2016-10-25 10:55:31
【问题描述】:

我有一个简单的类,其中包含一个列表:

public class SomeClass {

    private AppDataSource appDataSource; // it's interface
    private List<Object> someList;

    ////

    public List<Obejct> loadSomeList() {
        if (someList == null) {
            return appDataSource.getListFromDatabase();
        }
        retrunf someList;
    }
}

重点是 - 我希望该列表仅从数据库加载一次。我想对这个功能进行单元测试。我是 TDD 的菜鸟,我能做的就是为 someList 编写一个公共的 getter 和 setter,并在单元测试中使用它们。但它在概念上是错误的——我不希望类的客户直接使用这个成员变量。

在这种情况下如何正确测试方法?

【问题讨论】:

    标签: java unit-testing junit tdd


    【解决方案1】:

    您可以通过测试夹具中的构造函数初始化您的私有字段。我猜这是最常见的方式。

    另一种选择是在 Groovy 中编写测试,它可以直接访问 Java 类中的私有字段。因此,您无需授予对私有字段的访问权限。

    【讨论】:

      【解决方案2】:

      您可以像这样测试您的loadSomeList()

      public class SomeClass {
      
          private List<Object> someList;
      
          public List<Object> loadSomeList() {
              if (someList == null) {
                  someList = new ArrayList<>();
                  someList.add(new Object());
                  return someList;
              }
              return someList;
          }
      
          public List<Object> getSomeList() {
              return someList;
          }
      
          public void setSomeList(List<Object> someList) {
              this.someList = someList;
          }
      

      测试类应该有两个测试:

      • 在第一个测试中,您可以测试是否有新列表。你创建一个 SomeClass 的新实例并致电您的someClass.loadSomeList() 方法。如果列表不为空,则测试正常。
      • 第二个测试你可以测试你的someClass 实例是否已经有一个列表。在测试中,您只需在列表中添加一个对象并将其设置为 someClass

      public class SomeClassTest {
      
          @Test
          public void testLoadSomeListNewList() {
              SomeClass someClass = new SomeClass();
              List<Object> list = someClass.loadSomeList();
              assertNotNull(list);
          }
      
          @Test
          public void testLoadSomeListGivenList() {
              SomeClass someClass = new SomeClass();
              List<Object> list = new ArrayList<>();
              list.add(new Object());
              someClass.setSomeList(list);
      
              someClass.loadSomeList();
              assertTrue(someClass.getSomeList().size() == 1);
          }
      

      【讨论】:

        【解决方案3】:

        如果你在test目录下创建了一个与你的类包同名的包,并且如果你把这个字段放在protected中,你就可以直接访问这个字段

        【讨论】:

        • @ЕвгенийКравцов 将受保护字段作为类变量是一种不好的做法。 Lookup。此包中的所有其他类现在都可以访问您的字段并能够更改其值。如果值在代码中的任何地方发生了变化,您将无法再确定。
        【解决方案4】:

        您应该在测试到来之前模拟您的列表。使用@Before 来初始化您的列表。

        private List<Object> someList;
        
        @Before 
         public void initialize() {
            someList = new ArrayList<Object>();
            someLisi.add(..);
            someList.add(..);
         }
        

        并使用此模拟列表测试您的方法。您还可以使用@BeforeClass 模拟您的列表。您可以阅读@Before 和@BeforeClass here 之间的区别@

        【讨论】:

        • 没有多大意义 - 您“引用”的代码是他的被测类的一部分。所以你实际上是在告诉他从他的班级之外设置他的私人领域;这当然行不通。
        • @Jägermeister 感谢您的评论。我以为 OP 展示了他的测试课,我就这样回答了。现在我再次阅读了这个问题并编辑了我的答案。
        【解决方案5】:

        您的单元测试出错了。

        单元测试是关于测试你的类的行为;不是实施细节。

        您不测试私有字段是否确实有这个或那个内容。您唯一测试的是方法执行它们应该执行的操作。

        这当然意味着你的班级必须有办法插入“特殊列表”进行测试。

        长话短说:你想退后一步,花了接下来的2、3个小时 学习如何编写“易于测试的代码”;例如观看来自Google Tech on CleanCode 的精彩视频。

        【讨论】:

        • 我真的需要更多地掌握这个单元测试的东西。 Tnx 寻求建议
        【解决方案6】:

        与其公开和测试列表,不如更改您的 appDataSource 以便您可以从类外部设置它。使其成为提供 getListFromDatabase() 方法的接口。然后进行测试,传入一个实现接口的模拟数据源,并有一个计数器,您可以查询该计数器来告诉您调用 getListFromDatabase 方法的次数。

        看看你想测试什么,然后朝着那个方向努力。你没有在你的标准中提到列表本身很重要。重要的是您查询数据库的次数。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-01-15
          • 1970-01-01
          • 2013-09-01
          • 2011-08-20
          • 2014-08-10
          相关资源
          最近更新 更多