简体   繁体   English

Junit Mockito 测试一切

[英]Junit Mockito test everything

I am searching for more hours now with no result.我现在正在搜索更多小时但没有结果。 Please help...请帮忙...

This is my class to test:这是我要测试的课程:

public class DBSelectSchema extends Database {

    private static final Logger LOG = Logger
            .getLogger(DBSelectSchema.class.getName());
    private Connection conn = null;

    public DBSelectSchema() {
        super();
    }

    /**
     * This method will return the version of the database.
     * 
     * @return version
     * @throws Exception
     */
    public JSONObject getVersionFromDB() throws SQLException {
        ResultSet rs = null;
        JSONObject version = new JSONObject();
        PreparedStatement query = null;

        try {
            conn = mensaDB();
            query = conn.prepareStatement("SELECT number FROM version");

            rs = query.executeQuery();

            if (rs.isBeforeFirst()) {
                rs.next();
                version.put(HTTP.HTTP, HTTP.OK);
                version.put("version", rs.getString("number"));
            } else {
                version.put(HTTP.HTTP, HTTP.NO_CONTENT);
                version.put(HTTP.ERROR, "Die SQL Abfrage lieferte kein Result!");
            }

            rs.close();
            query.close();
            conn.close();

        } catch (SQLException sqlError) {
            String message = ERROR.SQL_EXCEPTION;
            LOG.log(Level.SEVERE, message, sqlError);
            return version;

        } catch (JSONException jsonError) {
            String message = ERROR.JSON_EXCEPTION;
            LOG.log(Level.SEVERE, message, jsonError);
            return version;
        }

        return version;
    }

I am trying to get in each branch for 100% code coverage.我试图在每个分支中获得 100% 的代码覆盖率。 How can I mock ResultSet rs, JSONObject version and PreparedStatement query to do/return what I want:我如何模拟 ResultSet rs、JSONObject 版本和 PreparedStatement 查询来执行/返回我想要的:

Currently I am testing like that:目前我正在这样测试:

@Test
    public void getVersionFromDB_RS_FALSE() throws SQLException, JSONException {
        MockitoAnnotations.initMocks(this);

        Mockito.when(dbSelMocked.mensaDB()).thenReturn(conn);
        Mockito.when(conn.prepareStatement(Mockito.anyString())).thenReturn(query);
        Mockito.when(query.executeQuery()).thenReturn(rs);
        Mockito.when(rs.isBeforeFirst()).thenReturn(false);

        JSONObject returnObj = dbSelMocked.getVersionFromDB();

        assert(...);
    }

But this just works when the 3 variables are class variables (like Connection conn) and not local variables.但这仅适用于 3 个变量是类变量(如 Connection conn)而不是局部变量时。 But I dont want them (even Connection) not to be global.但我不希望它们(甚至连接)不是全球性的。

=== EDIT 1 === === 编辑 1 ===

It works like that if all variables are local:如果所有变量都是本地变量,它的工作方式如下:

@Test
    public void getVersionFromDB_RS_FALSE() throws SQLException, JSONException {
        System.out.println("####################");
        System.out.println("started test: getVersionFromDB_RS_FALSE");
        System.out.println("####################");

        Connection conn = Mockito.mock(Connection.class);
        PreparedStatement query = Mockito.mock(PreparedStatement.class);
        ResultSet rs = Mockito.mock(ResultSet.class);

        MockitoAnnotations.initMocks(this);


        Mockito.when(dbSelMocked.mensaDB()).thenReturn(conn);
        Mockito.when(conn.prepareStatement(Mockito.anyString())).thenReturn(query);
        Mockito.when(query.executeQuery()).thenReturn(rs);
        Mockito.when(rs.isBeforeFirst()).thenReturn(false);

        JSONObject returnObj = dbSelMocked.getVersionFromDB();

        assertTrue(returnObj.has("error"));
    }

But I am not able to mock JSONObject version in another test anymore :( How can I do that?但是我不能再在另一个测试中模拟 JSONObject 版本了 :(我该怎么做?

@Test
    public void getVersionFromDB_JSON_EXCEPTION() throws SQLException, JSONException {
        System.out.println("####################");
        System.out.println("started test: getVersionFromDB_JSON_EXCEPTION");
        System.out.println("####################");
        JSONObject version = Mockito.mock(JSONObject.class);

        MockitoAnnotations.initMocks(this);

        doThrow(new JSONException("DBSelectSchemaIT THROWS JSONException")).when(version).put(anyString(), any());

        JSONObject returnObj = dbSelMocked.getVersionFromDB();

        System.out.println(returnObj.toString());

        assertTrue(returnObj.equals(null));
    }

I think its overwritten in the real method... because it does not throw an exception and the method does not fail.我认为它在真正的方法中被覆盖了......因为它不会抛出异常并且该方法不会失败。

Your test code has multiple issues.您的测试代码有多个问题。

  • The test is verbose and fragile测试冗长而脆弱
  • The same (verbose) setup is required for multiple tests多个测试需要相同的(详细)设置
  • You don't test real object, instead you are using mock of your class for testing您不测试真实对象,而是使用类的模拟进行测试

The first 2 issues can be solved by extracting repeated code to a setup method (I added static import for Mockito to reduce the noise):前两个问题可以通过将重复代码提取到设置方法中来解决(我为 Mockito 添加了静态导入以减少噪音):

@Before
public void setUp() throws Exception {
    Connection conn = mock(Connection.class);
    PreparedStatement query = mock(PreparedStatement.class);
    when(dbSelMocked.mensaDB()).thenReturn(conn);
    when(conn.prepareStatement(anyString())).thenReturn(query);
    when(query.executeQuery()).thenReturn(rs);

    rs = mock(ResultSet.class); // rs is field
}

Now in each of your tests you can configure rs to return whatever you need:现在,在您的每个测试中,您都可以配置rs以返回您需要的任何内容:

@Test
public void getVersionFromDB_RS_FALSE() throws Exception {
    // Given
    when(rs.isBeforeFirst()).thenReturn(false);

    // When
    JSONObject returnObj = dbSelMocked.getVersionFromDB();

    // Then
    assertTrue(returnObj.has("error"));
}

Now the most important issue: you are mocking class DBSelectSchema to return connection mock.现在最重要的问题是:您正在模拟类DBSelectSchema以返回连接模拟。 Mocking class under test can cause different hard-to-spot problems.被测试的模拟类可能会导致不同的难以发现的问题。

To solve this issue you have 3 options:要解决问题,您有 3 个选择:

  1. Refactor your code and inject some connection factory.重构您的代码并注入一些连接工厂。 So you'll be able to mock it in your test.因此,您将能够在测试中模拟它。

  2. Extend class DBSelectSchema in your test and override method mensaDB() so it will return mocked connection在您的测试中扩展类 DBSelectSchema 并覆盖方法mensaDB()以便它将返回mensaDB()连接

  3. Use embedded database like H2 and put test data in 'number' table before calling getVersionFromDB()在调用 getVersionFromDB() 之前,使用 H2 等嵌入式数据库并将测试数据放入“数字”表中

Option #1选项1

Extract creation of connection to a separate class and use it in your DBSelectSchema :将创建的连接提取到单独的类并在您的DBSelectSchema使用它:

public class ConnectionFactory {
    public Connection getConnection() {
       // here goes implementation of mensaDB()
    }
}

Then inject it to your DBSelectSchema:然后将其注入您的 DBSelectSchema:

public DBSelectSchema(ConnectionFactory connFactory) {
    this.connFactory = connFactory;
}

Now your test you can use real DBSelectSchema class with mocked ConnectionFactory现在你的测试,你可以使用真正的DBSelectSchema类嘲笑的ConnectionFactory

    ConnectionFactory connFactory = mock(ConnectionFactory.class);
    dbSel = new DBSelectSchema(connFactory); 

Option #2选项#2

You can make almost real class under test:您可以制作几乎真实的测试类:

    final Connection conn = mock(Connection.class);
    dbSel = new DBSelectSchema() {
        @Override
        public Connection mensaDB() {
            return conn;
        }
    }; 

Option #3选项#3

This option is most preferable, because you will call real SQL commands and you mock the whole database instead of classes.此选项是最可取的,因为您将调用真正的 SQL 命令并模拟整个数据库而不是类。 It requires some effort to use plain JDBC here, but it worth that.在这里使用普通的 JDBC 需要付出一些努力,但这是值得的。 Keep in mind that SQL dialect can differ from the DB used in production.请记住,SQL 方言可能与生产中使用的 DB 不同。

@Before
public void setUp() throws Exception {
    Class.forName("org.h2.Driver");
    conn = DriverManager.getConnection("jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'classpath:schema.sql'");
}

@After
public void tearDown() throws Exception {
    conn.close();
}

Then in your test you simply add required records to DB:然后在您的测试中,您只需将所需的记录添加到数据库:

 @Test
 public void getVersionFromDB() throws Exception {
    // Given
    conn.prepareStatement("INSERT INTO version(number) VALUES (1)").execute();

    // When
    JSONObject returnObj = dbSel.getVersionFromDB();

    // Then
    assert(...);
}

Obviously, DBSelectSchema must use the same connection, so you can use in combination with options #1 and #2,显然,DBSelectSchema 必须使用相同的连接,因此可以结合选项#1 和#2 使用,

You are unit testing data access layer by mocking all ADO calls.您正在通过模拟所有 ADO 调用对数据访问层进行单元测试。 By doing so, you will end up with a unit test that does not really test any logic.通过这样做,您最终将得到一个不真正测试任何逻辑的单元测试。

Taking an example from your code: assume that you are using the following sql to retrieve a version number : SELECT number FROM version .以您的代码为例:假设您使用以下 sql 来检索版本号: SELECT number FROM version Now assume that the column name changed and you should retrieve 2 additional column from your sql.现在假设列名已更改,您应该从 sql 中检索 2 个附加列。 You will eventually end up with an sql like SELECT number, newColumn1, newColumn2 FROM version .你最终会得到一个像SELECT number, newColumn1, newColumn2 FROM version这样的 sql。 With the test you would have written (using mock), it would still pass even though its not really testing whether the 2 new column is being retrieved.使用您将编写的测试(使用模拟),即使它没有真正测试是否正在检索 2 个新列,它仍然会通过。 You get my point?你明白我的意思吗?

I would advise you to have a look at this thread for some possible alternative to test your data access layer.我建议您查看此线程以获取一些可能的替代方法来测试您的数据访问层。 Using mock for your data access layer will end up with brittle test that does not really test anything对你的数据访问层使用模拟最终会导致脆弱的测试,它不会真正测试任何东西

Your test is much too large, and you seem to be testing too much.你的测试太大了,你似乎测试太多了。

Split your code along it's natural breaks, so that the code that does the data retrieval is separate from the logic that manipulates it.沿着它的自然中断拆分您的代码,以便进行数据检索的代码与操作它的逻辑分开。

You only want to test the code that you write, not the 3rd party code.只是想测试你写的代码,而不是第三方的代码。 Its out of scope for your needs, and if you can't trust it, then don't use it.它超出了您的需求范围,如果您不信任它,请不要使用它。

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

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