【问题标题】:Problem with unit tests: Junit and the Mockito framework单元测试的问题:Junit 和 Mockito 框架
【发布时间】:2021-07-07 20:12:39
【问题描述】:

我正在学习使用 Junit 和 Mockito 框架进行单元测试和双重测试,但是在使用“模拟”的特定测试中我没有得到预期的结果。我做了一个assertThat,它应该返回正面测试,相反,它返回一个错误,上面写着Mockito cannot mock this class。它是一个名为“控制台”的类,它必须从用户的键盘打印和收集值,但是当然,在单元测试中,这应该被“模拟”以避免反模式中的“干预测试”,其中测试要求数据开发人员,也就是说,我需要“模拟”用户输入。这个“控制台”类就像典型的 java BufferedReader 类的一个小外观。

我把所涉及的课程传给你:

控制台:

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Console {

    private BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

    public String readString(String title) {
        String input = null;
        boolean ok = false;
        do {
            this.write(title);
            try {
                input = this.bufferedReader.readLine();
                ok = true;
            } catch (Exception ex) {
                this.writeError("characte string");
            }
        } while (!ok);
        return input;
    }

    public int readInt(String title) {
        int input = 0;
        boolean ok = false;
        do {
            try {
                input = Integer.parseInt(this.readString(title));
                ok = true;
            } catch (Exception ex) {
                this.writeError("integer");
            }
        } while (!ok);
        return input;
    }

    public char readChar(String title) {
        char charValue = ' ';
        boolean ok = false;
        do {
            String input = this.readString(title);
            if (input.length() != 1) {
                this.writeError("character");
            } else {
                charValue = input.charAt(0);
                ok = true;
            }
        } while (!ok);
        return charValue;
    }

    public void writeln() {
        System.out.println();
    }

    public void write(String string) {
        System.out.print(string);
    }

    public void writeln(String string) {
        System.out.println(string);
    }

    public void write(char character) {
        System.out.print(character);
    }

    public void writeln(int integer) {
        System.out.println(integer);
    }

    private void writeError(String format) {
        System.out.println("FORMAT ERROR! " + "Enter a " + format + " formatted value.");
    }
}

控制台测试:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.io.BufferedReader;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;

public class ConsoleTest {

    @Mock
    private BufferedReader bufferedReader;
    @InjectMocks
    private Console console;

    @BeforeEach
    public void before(){
        initMocks(this);
        //this.console = new Console();
    }
    @Test
    public void givenConsoleWhenReadStringThenValue() throws IOException {
        String string = "yes";
        when(this.bufferedReader.readLine()).thenReturn(string);
        assertThat(this.console.readString("title"), is(string));
    }
}

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://maven.apache.org/POM/4.0.0"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <modules>

    </modules>

    <artifactId>solution.java.swing.socket.sql</artifactId>
    <groupId>usantatecla.tictactoe</groupId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>${project.groupId}.${project.artifactId}</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.6.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>3.6.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>3.6.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest</artifactId>
            <version>2.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.3</version>
                <executions>
                    <execution>
                        <id>default-prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-report</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <dependencies>
                    <dependency>
                        <groupId>org.junit.platform</groupId>
                        <artifactId>junit-platform-surefire-provider</artifactId>
                        <version>1.2.0</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.22.2</version>
                <dependencies>
                    <dependency>
                        <groupId>org.junit.platform</groupId>
                        <artifactId>junit-platform-surefire-provider</artifactId>
                        <version>1.2.0</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

感谢社区的问候!

【问题讨论】:

  • 您好,但是为什么在before() 方法中需要它this.console = new Console();?
  • 我认为没有必要,但如果我评论那一行,它无论如何都会出错
  • 存在一些基本问题。您不能通过 new 模拟您在 sut 中创建的对象。你可以注入它,或者你可以使用工厂来创建它,然后模拟工厂。而且我看不到您是如何创建控制台的。
  • 我尝试了您的代码,它与注释 this.console = new Console(); 并添加了 junit-vintage-engine 库一起使用。
  • 我试过你的代码,它在简单的 spring-boot 项目版本2.4.1 中工作。我也使用了@ExtendWith(MockitoExtension.class) 而不是你的@BeforeEach 方法。

标签: java unit-testing mockito junit5


【解决方案1】:

我个人不太喜欢 Mockito,我更喜欢使用一个接口和两个实现(一个用于生产,一个用于测试)来完全控制我的类。

因此,这不会直接回答您关于 Mockito 的问题,但它允许您完美控制代码的行为,而无需使用其他框架。

你可以定义一个非常简单的接口:

public interface Reader {
    String readLine();
}

然后,您在类控制台中使用该接口:

public class Console {
    private final Reader reader;

    public Console(Reader reader) {
        this.reader = reader;
    }
    
    //Replace all your this.bufferedReader.readLine() by this.reader.readLine();
   
}

因此,在您的生产代码中,您可以使用带有缓冲阅读器的实际实现:

public class ProductionReader implements Reader {
    
    private final BufferedReader bufferedReader = new BufferedReader(...);

    @Override
    public String readLine() {
        this.bufferedReader.readLine();
    }
     
}

Console console = new Console(new ProductionReader());

...在您的测试中,您可以使用测试实现:

public class TestReader implements Reader {
    @Override
    public String readLine() {
        return "Yes";
    }
}

Console console = new Console(new TestReader());

请注意,虽然在您的特定情况下,您可以使用 Mockito 模拟行为,但在许多其他情况下,您将需要更复杂的方法,并且可以让您对代码进行完全控制和完全可调试性无需添加任何依赖。

【讨论】:

  • 感谢 Matteo 的选择,它看起来不错。问题是我不想修改 SUT(被测对象),或者,如有必要,尽量减少。另一个方面是,如果有很多 DOC(Depended On Component),您将不得不过多地修改 SUT。
  • 这只是重新发明 Mockito 可以自动为您做的事情。
  • @DawoodibnKareem 我遇到过许多 Mockito 无法满足我们需求的情况。另外,Mockito 可能是晦涩难懂的(你并没有真正看到和控制存根后面发生的事情)。总而言之,我已经看到这种技术应用了太多次,以至于无法将其归类为简单的“只是重新发明”,这是一种众所周知的传播设计,而不是我凭空发明的东西;)
  • 有趣的答案。无论如何,已经跑题了,但如果图书馆只满足你 98% 的需求,是否有理由抛弃它并自己制作所有东西,而不是缺少 2%?从另一个角度来看:像 Mockito 这样的库可以指导您使代码更具可测试性,而不是坚持需要“每个类解决方案”的设计。
  • @pirho 在我的经验中远远超过 2%。我会说50%。此外,它是一种非常普及的设计(不仅是 java,例如主要用于 C++),我没有发明或从口袋里拿出任何东西。到目前为止,它对我来说总是更方便,而且在改变这种观点之前,我已经使用 Mockito 多年了。但是,是的,这是题外话,我只是想分享 一种方法 来做到这一点,因为坦率地说,当我使用 Mockito 时,我花了很多时间来了解框架在做什么以及如何做(很难调试,我什至没有告诉你我们升级 mockito 版本时发生了什么:)
猜你喜欢
  • 2021-05-28
  • 2023-04-03
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多