【问题标题】:How should I unit-test a long function?我应该如何对长函数进行单元测试?
【发布时间】:2013-07-16 11:57:36
【问题描述】:

如果我有一个很长的代码方法,它从 2 或 3 个不同的来源收集数据并返回结果。如何重构它以使其更具单元测试性?此方法是一种网络服务,我想从客户端代码中调用一次以收集所有数据。

我可以将一些部分重构为更易于测试的更小的方法。但当前方法仍将调用这 5 个方法,并且可测试性较低。假设 Java 作为编程语言,有没有一种模式可以让这样的代码可测试?

【问题讨论】:

    标签: java unit-testing


    【解决方案1】:

    “大”方法的测试看起来像集成测试,可以模拟较小的方法。

    如果您可以将“大”方法分成五个孤立的方法,那么“大”方法可以进一步划分为独立方法的语义/上下文意义组。

    然后您可以为“大”方法模拟更大的隔离方法分组。

    【讨论】:

    • 但是你不是在引入复杂性吗?如果一个函数的使用次数不超过 2 或 3 次,如果它是 inline 是不是更好,让它更简单?
    • @JakeZieve 这完全取决于。您正在用一种复杂性换另一种复杂性——当它全部在一种方法中时,您具有更高的圈复杂度,它更难推理,也更难测试。我更喜欢我可以组成的美味,一口大小的简单块。重构不仅仅是为了重用,它是为了增强你推理它的能力。我认为大多数函数提取(最初)是关于重用,但关于在单个方法中分离关注点和扁平化抽象级别。
    • @JakeZieve 所有方法都应该在单一抽象级别上运行。如果您能够从一种方法中提取多种方法,那么最初的情况不太可能(充其量)。我不知道您为什么要谈论文件-Java 不允许将方法放入文件中。如果您要抽象到一个类,并且这样做是有意义的,那是因为原始设计缺少该类。但是不,这不会增加圈复杂度;在最坏的情况下它保持不变。
    • @JakeZieve 不,您不必一定阅读相同的代码——您只需要阅读代码就可以了解正在发生的事情。如果你有一段代码,你觉得需要评论,那么你就错过了通过纯代码来澄清它的机会,这是功能的最终仲裁者——而不是 cmets。
    • @JakeZieve 你会发现你在保持方法长的方面是少数,但没关系。当然,一个更大的注释可以比一个名称更具描述性——我是说如果你需要一个长注释,那么你需要重构。
    【解决方案2】:

    这是一个非常常见的测试问题,我遇到的最常见的解决方案是将数据的来源与使用依赖注入的数据的代码分开。这不仅支持良好的测试,而且在使用外部数据源时通常是一个很好的策略(良好的职责分离、隔离集成点、促进代码重用是其中的一些原因)。

    您需要进行的更改类似于:

    • 对于每个数据源,创建一个接口来定义如何访问来自该源的数据,然后将返回数据的代码分解到一个单独的类中,该类实现这一点。
    • 依赖项将数据源注入到包含“long”函数的类中。
    • 对于单元测试,注入每个数据源的模拟实现。

    这里是一些代码示例,展示了它的外观 - 请注意,此代码仅用于说明模式,您需要一些更合理的名称。值得研究这种模式并了解更多关于依赖注入和模拟的信息——这是单元测试工具库中最强大的两种武器。

    数据源

    public interface DataSourceOne {
        public Data getData();
    }
    
    public class DataSourceOneImpl implements DataSourceOne {
        public Data getData() {
            ...
            return data;
        }
    }
    
    public interface DataSourceTwo {
        public Data getData();
    }
    
    public class DataSourceTwoImpl implements DataSourceTwo {
        public Data getData() {
            ...
            return data;
        }
    }
    

    长方法类

    public class ClassWithLongMethod {
        private DataSourceOne dataSourceOne;
        private DataSourceTwo dataSourceTwo;
    
        public ClassWithLongMethod(DataSourceOne dataSourceOne,
                                   DataSourceTwo dataSourceTwo) {
            this.dataSourceOne = dataSourceOne;
            this.dataSourceTwo = dataSourceTwo;
        }
    
        public Result longMethod() {
            someData = dataSourceOne.getData();
            someMoreData = dataSourceTwo.getData();
            ...
            return result;
        }
    }
    

    单元测试

    import org.junit.Test;
    
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.when;
    
    public class ClassWithLongMethodTest {
    
        @Test
        public void testLongMethod() {
    
            // Create mocked data sources which return the data required by your test
            DataSourceOne dataSourceOne = mock(DataSourceOne.class);
            when(dataSourceOne.getData()).thenReturn(...);
            DataSourceTwo dataSourceTwo = mock(DataSourceTwo.class);
            when(dataSourceTwo.getData()).thenReturn(...);
    
            // Create the object under test using the mocked data sources
            ClassWithLongMethod sut = new ClassWithLongMethod(dataSourceOne,
                                                              dataSourceTwo);
    
            // Now you can unit test the long method in isolation from it's dependencies
            Result result = sut.longMethod();
    
            // Assertions on result
            ...
        }
    }
    

    请原谅(并纠正)任何语法错误,这些天我写的java不多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-18
      • 2020-11-06
      • 2016-09-18
      • 2011-01-01
      • 2017-04-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多