简体   繁体   English

如何对无效函数进行单元测试?

[英]How can I unit test void functions?

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();
        }
    }
}

Now i want to test the perform method. 现在我要测试perform方法。 How can I unit test this method using JUnit? 如何使用JUnit对这种方法进行单元测试?

Solution with Mockito Spy 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();

    }
}

Negative tests: 阴性测试:

@Test
public void elephantShouldDontMakeNoisesOnMonday() {

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

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

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

}

or 要么

@Test
public void shouldDoNotMakeNoisesOnMonday() {

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

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

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

}

Dependency 相依性

org.mockito:mockito-core:2.21.0

Read about 阅读

void() functions change the state of a program. void()函数更改程序的状态。 This can be done by modifying a variable, a file, a database, etc. 这可以通过修改变量,文件,数据库等来完成。

In your case you're writing to a logger. 就您而言,您是在写记录器。 If this results in writing " Elephant make Sound" to a file then you can read that file and see if the data in the file includes your noisy elephant. 如果这导致将“ Elephant make Sound”写入文件,则可以读取该文件并查看文件中的数据是否包含您的嘈杂的大象。

If it however doesn't involve anything you can check (ie: it simply displays the output on the console) then you might want to look at some form of dependency injection (DI) where you can set the output to a file or something else you can easily read. 但是,如果它不涉及任何内容,您可以检查(即:它仅在控制台上显示输出),则可能需要查看某种形式的依赖项注入(DI),您可以在其中将输出设置为文件或其他内容您可以轻松阅读。

It should be noted that you can bypass DI by mocking the object and checking the appropriate methods are getting called. 应该注意的是,您可以通过模拟对象并检查是否调用了适当的方法来绕过DI。

To test any method, the responsibility to be tested must be visible from the out side of the method by changing state of any variable. 要测试任何方法,必须通过更改任何变量的状态从方法的外部看待测试的责任。

Typically it is done by returning value from the method. 通常,它是通过从方法返回值来完成的。 But without that, it can be done in many ways by modifying something from outside of the method scope, in case you have any "problem" to return something from the method! 但是,如果没有任何“问题”可以从方法返回值,则可以通过多种方法来修改方法范围之外的内容,从而完成此操作!

In your case, you only log some message. 就您而言,您只记录一些消息。 And your code is not really testable in a sense that it does not do something that is directly related to changing the state of any variable (Because you change the state of other resource other than variable, that is not directly accessible by your code. You have to write some code to read the changes from that external resource, hence makes your testing code dependent to the reading also. If you have some problem with reading, your test case will not pass and that does not go with the spirit of the unit testing. The main idea is to reduce the dependency on external codes or libraries as much as possible). 从某种意义上说,您的代码实际上不是可测试的,因为它不会执行与更改任何变量的状态直接相关的操作(因为更改了变量以外的其他资源的状态,因此您的代码无法直接访问。)必须编写一些代码以读取来自该外部资源的更改,从而使您的测试代码也依赖于阅读。如果您对阅读有任何疑问,则您的测试用例将无法通过,这与单元的精神不符。测试:主要思想是尽可能减少对外部代码或库的依赖。 But your code can be testable by doing a slight refactoring / shifting responsiblity like below: 但是,您可以通过执行如下所示的轻微重构/移动职责来测试代码:

String makeNoise() {
    return "Elephant  make Sound";
}

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

And then you shift the responsibility of logging the value returned from perform method to the one using it like below: 然后,您将将perform方法返回的值的记录职责转移到使用它的方法,如下所示:

 logger.info(perform(day));

You have various options depending on the tools you are willing to use and the depth your tests should have. 根据您愿意使用的工具和测试的深度,您可以有多种选择。

Partial Mocking with plain Java 用纯Java进行部分模拟

Create a class (MockElephant) that extends from elephant, overwrite makeNoise so it counts the number of invocations. 创建一个从Elephant扩展的类(MockElephant),覆盖makeNoise以便计算调用次数。 Use that class in your test to check that makeNoise was called the correct number of times 在测试中使用该类来检查makeNoise已正确调用makeNoise次数

Partial Mocking with a Framework You basically do the same as above but instead of manually coding the MockElephant you create it using some mocking framework. 部分嘲讽与框架你基本同上,但代替手工编码MockElephant创建使用一些模拟框架它。 Makes the test much simpler, since you need less code. 由于您需要更少的代码,因此使测试更加简单。 And it is easier to read. 而且更容易阅读。 But if strange things happen it makes it harder to understand what is going on. 但是,如果发生奇怪的事情,则将很难理解发生了什么。 In case of Mocking frameworks I think they are worth it. 对于模拟框架,我认为它们是值得的。

The reasons why this is called Partial Mocking is that you mock only parts of a class (a single method in this case). 之所以称为“部分模拟”,是因为您仅模拟类的一部分(在这种情况下为单个方法)。

The alternative is to use Normal Mocks , which in your case seems feasible (but can become tough in legacy code). 另一种方法是使用Normal Mocks ,在您看来,这是可行的(但在旧代码中可能会变得很难)。

Here you would inject the Logger as a dependency. 在这里,您需要将Logger注入为依赖项。 For example you could create an additional constructor which allows you to provide the Logger. 例如,您可以创建一个附加的构造函数,该构造函数允许您提供Logger。 In your test you would then use a mocked Logger, which again counts it's invocations, probably along with the parameter it received and check that it has the expected values. 然后,在测试中,您将使用模拟的Logger,该Logger再次计算其调用次数,并可能连同接收到的参数一起计数,并检查其是否具有预期值。

Again you can do that with a Mocking Framework or with plain old Java. 同样,您可以使用Mocking Framework或普通的Java来实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM