Java 如何对void函数进行单元测试?

Java 如何对void函数进行单元测试?,java,unit-testing,junit,Java,Unit Testing,Junit,现在我想测试perform方法。如何使用JUnit对该方法进行单元测试?要测试任何方法,必须通过更改任何变量的状态从方法的外部看到要测试的责任 通常通过从方法返回值来完成。但是如果没有这些,可以通过从方法范围之外修改某些内容,在许多方面完成,以防您在从方法返回某些内容时遇到任何“问题” 在您的情况下,您只记录一些消息。您的代码在某种意义上不是真正可测试的,因为它不会做与更改任何变量的状态直接相关的事情(因为您更改了变量以外的其他资源的状态,而您的代码无法直接访问该资源。您必须编写一些代码来读取该

现在我想测试
perform
方法。如何使用JUnit对该方法进行单元测试?

要测试任何方法,必须通过更改任何变量的状态从方法的外部看到要测试的责任

通常通过从方法返回值来完成。但是如果没有这些,可以通过从方法范围之外修改某些内容,在许多方面完成,以防您在从方法返回某些内容时遇到任何“问题”

在您的情况下,您只记录一些消息。您的代码在某种意义上不是真正可测试的,因为它不会做与更改任何变量的状态直接相关的事情(因为您更改了变量以外的其他资源的状态,而您的代码无法直接访问该资源。您必须编写一些代码来读取该外部资源的更改,因此使您的测试代码也依赖于读取。如果您在读取方面有问题,您的测试用例将无法通过,这与单元测试的精神。其主要思想是尽可能减少对外部代码或库的依赖)。但您的代码可以通过执行以下稍微重构/转移职责来进行测试:

class Elephant extends Animal {   
    public Elephant(String name) {
        super(name);
    }

    void makeNoise() {
        logger.info(" Elephant  make Sound");
    }

    void perform(String day) {
        if (day.equals("thursday") || day.equals("friday")) {
            makeNoise();
        }
    }
}
String makeNoise() {
    return "Elephant  make Sound";
}

String perform(String day) {
    if (day.equals("thursday") || day.equals("friday")) {
      return makeNoise();
    }
}
然后将记录从
perform
方法返回的值的责任转移到使用它的责任上,如下所示:

class Elephant extends Animal {   
    public Elephant(String name) {
        super(name);
    }

    void makeNoise() {
        logger.info(" Elephant  make Sound");
    }

    void perform(String day) {
        if (day.equals("thursday") || day.equals("friday")) {
            makeNoise();
        }
    }
}
String makeNoise() {
    return "Elephant  make Sound";
}

String perform(String day) {
    if (day.equals("thursday") || day.equals("friday")) {
      return makeNoise();
    }
}
void()
函数更改程序的状态。这可以通过修改变量、文件、数据库等来完成

在您的情况下,您正在向记录器写入数据。如果这导致将“大象发出声音”写入文件,则您可以读取该文件,并查看文件中的数据是否包括您发出噪音的大象

但是,如果它不涉及任何您可以检查的内容(即:它只是在控制台上显示输出),那么您可能希望查看某种形式的依赖项注入(DI),您可以将输出设置为文件或其他易于阅读的内容

应该注意的是,您可以通过模拟对象并检查是否调用了适当的方法来绕过DI。

Mockito Spy解决方案 阴性试验:

import org.junit.Test;

import static org.mockito.Mockito.*;

public class ElephantTest {

    @Test
    public void shouldMakeNoise() throws Exception {

        //given
        Elephant elephant = spy(new Elephant("foo"));

        //when
        elephant.perform("friday");

        //then
        verify(elephant).makeNoise();

    }
}

依赖性

@Test
public void shouldDoNotMakeNoisesOnMonday() {

    //given
    Elephant elephant = spy(new Elephant("foo"));

    //when
    elephant.perform("monday");

    then(elephant).should(never()).makeNoise();

}
了解


根据您愿意使用的工具和测试应有的深度,您有各种选择

使用普通Java进行部分模拟

创建一个从elephant扩展而来的类(MockElephant),覆盖
makeNoise
,以便计算调用次数。在测试中使用该类检查调用
makeNoise
的次数是否正确

使用框架进行部分模拟 你基本上和上面做的一样,但不是手动编写MockElephant,而是使用一些模拟框架来创建它。这使得测试更简单,因为你需要更少的代码。而且它更容易阅读。但是如果发生奇怪的事情,它会让你更难理解正在发生的事情。如果是模拟框架,我认为它们是值得的

之所以称为部分模拟,是因为您只模拟类的一部分(本例中为单个方法)

另一种选择是使用普通mock,在您的情况下,这似乎是可行的(但在遗留代码中可能变得很困难)

在这里,您可以将记录器作为依赖项注入。例如,您可以创建一个额外的构造函数,允许您提供记录器。在测试中,您将使用模拟记录器,该记录器将再次计算其调用次数,可能还有它接收到的参数,并检查它是否具有预期值


同样,您可以使用模拟框架或普通的旧Java来实现这一点。

logger来自哪里?可能来自
Animal
;)您需要一个模拟框架。谷歌。我会建议Mockito,但我可能有点偏颇,因为我与该产品有一些关联。我在动物类中使用的记录器,可以。我想使用junit测试,所以在我使用的地方,我想检查perform方法,确保它能在周四或周五为输入正确运行,那么我的junit测试用例中的代码是什么?您似乎在寻找一个我无法给出的确切代码答案。不仅因为我不想;也因为我不能:我不知道
logger.info(string)
做什么。按照我在帖子中说的做:如果它写入文件,请读取您的文件。如果它写入控制台,则重定向输出。一旦你有了一个具体的方法来访问生成的状态数据,你就可以检查结果了。如果您添加了负面测试用例,其中大象在周一不会发出噪音,我会升级-并立即开始使用Mockito:)@ckonig我希望您已经在使用Mockito,附带负面测试用例;)
org.mockito:mockito-core:2.21.0