在单元测试中避免外部输入

时间:2019-06-28 16:09:45

标签: java unit-testing junit

我想对class A进行单元测试。该类与其他类一起级联,因此class A创建了class B的{​​{1}}和class B的实例。像这样:

class C

如果现在C类在其构造函数中读取任何外部输入(例如文件或属性),我该如何避免呢? 我看不到如何模拟它的方法,但是在测试时也无法将外部因素排除在其他类之外吗?

4 个答案:

答案 0 :(得分:4)

您遇到的问题是,您的类与其他类的特定实现紧密关联,因此您的“单元”被迫测试所有这些类的行为在一起。

避免这种情况的典型方法是使用Dependency Injection,使您的类与接口(而不是特定的类)耦合。然后,您可以mock注入的接口来对单个类的行为进行单元测试。

public class AImpl {
    public B b;
    public AImpl(B b) {
        this.b = b;
    }
}

public interface B {
    // methods
}

public class BImpl implements B {
    public C c;
    public BImpl (C c) {
        this.c = c;
    }
}

在生产代码中创建AImpl时,现在必须为其提供特定的C实现。您可以使用new AImpl(new BImpl(new CImpl))进行此操作,也可以使用像Spring这样的依赖注入框架来为您找出所有这些细节。

在进行单元测试时,可以创建一个模拟或存根,使其具有希望B在特定测试中展现的行为,然后将该存根传递给构造函数:{{1 }}。

答案 1 :(得分:3)

您不应在类B中创建A的实例-而是在创建B时应提供实例A-它被称为Inversion of Control。流行的方法是通过依赖注入,例如Spring框架。

然后一切变得简单-当您的类需要读取FileReader类的文件时,对于测试,您可以创建一个“假”实现,例如,当您调用FileReader.readFile()时,您的假实现将只需返回硬编码的String或Stream,取决于您想要的内容。

这个概念真的很大,想象一下您的FileReader实际上是DatabaseReader还是ExternalServiceCaller的时候。无需在单元测试中测试真实的数据库(祝您好运),而是创建一个可以在常规Java FakeDatabaseReader上运行的HashMap,并且所有内容都易于测试。

或者当您的代码处理时间/日期时,想象一下仅在29th February处执行某项功能的功能-您可以等待4年对其进行测试,或者改为提供Clock对象进行测试设置为特定日期。 Clock类具有提供各种备用时钟的静态方法,以提供fixed moment,时刻adjusted into the future or the pastclock with an altered cadence

答案 2 :(得分:1)

首先,由于您不能注入B类,因此并非所有类都经过精心测试,请注意,由于类中有新内容,因此与实例的硬编码非常相似。

适当的方法应类似于

  public class A {
    public B b;
    public A(B b) {
        this.b = b;
    }
}

请注意,您可以通过这种方式实例化

A a = new A(b); 

通过这种方式,b可以是模拟或虚拟或存根,也可以是用于测试目的的任何对象。如果您面对的是旧代码,则应对其进行重构以使其能够进行测试。如果是您的设计,则应尽快重构以使其可测试。

控制反转和依赖注入应该有助于存档您想做的事情。

答案 3 :(得分:0)

据我所知,您需要照顾这些外部因素,或者确保在开发A,B或C类本身时,在测试期间不会出现此类问题。

但是,如果您无法控制A,B或C类的源代码,那么我认为您会陷入困境!