Junit Mockito测试一切



我正在搜索更多的小时,但没有结果。请帮忙。。。

这是我要测试的类:

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

我正在努力进入每个分支,以获得100%的代码覆盖率。我如何模拟ResultSet rs、JSONObject版本和PreparedStatement查询以执行/返回我想要的内容:

目前我正在这样测试:

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

但是,当这3个变量是类变量(如Connection conn)而不是局部变量时,这才有效。但我不希望它们(甚至是Connection)不是全球性的。

===编辑1===

如果所有变量都是本地的,它的工作原理是这样的:

@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"));
    }

但我无法再在另一个测试中模拟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));
    }

我认为它在实际方法中被覆盖了。。。因为它不会抛出异常,并且该方法不会失败。

您的测试代码有多个问题。

  • 测试冗长且脆弱
  • 多个测试需要相同的(详细的)设置
  • 您不测试真实对象,而是使用类的mock进行测试

前两个问题可以通过将重复代码提取到设置方法中来解决(我为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
}

现在,在您的每个测试中,您都可以配置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"));
}

现在最重要的问题是:您正在模拟类DBSelectSchema以返回连接mock。测试中的模拟类可能会导致不同的难以发现的问题。

要解决这个问题,您有3个选项:

  1. 重构代码并注入一些连接工厂。所以你会能够在考试中模仿它。

  2. 在测试和重写方法中扩展类DBSelectSchemamensaDB(),因此它将返回模拟连接

  3. 使用像H2这样的嵌入式数据库,并将测试数据放在"数字"表中在调用getVersionFromDB()之前

选项#1

提取到一个单独类的连接创建,并在您的DBSelectSchema:中使用它

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

然后将其注入您的DBSelectSchema:

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

现在您的测试可以使用realDBSelectSchema类和mockeConnectionFactory

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

选项#2

您可以使几乎真正的类在测试中:

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

选项#3

这个选项是最可取的,因为您将调用真正的SQL命令,并模拟整个数据库而不是类。在这里使用普通JDBC需要付出一些努力,但这是值得的。请记住,SQL方言可能与生产中使用的数据库不同。

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

然后在测试中,您只需将所需的记录添加到DB:

 @Test
 public void getVersionFromDB() throws Exception {
    // Given
    conn.prepareStatement("INSERT INTO version(number) VALUES (1)").execute();
    // When
    JSONObject returnObj = dbSel.getVersionFromDB();
    // Then
    assert(...);
}

显然,DBSelectSchema必须使用相同的连接,因此可以与选项#1和#2、

组合使用

您正在通过模拟所有ADO调用来对数据访问层进行单元测试。通过这样做,您将得到一个单元测试,它并没有真正测试任何逻辑。

以您的代码为例:假设您正在使用以下sql来检索版本号:SELECT number FROM version。现在假设列名已更改,您应该从sql中检索另外2列。您最终会得到一个类似于SELECT number, newColumn1, newColumn2 FROM version的sql。使用您编写的测试(使用mock),它仍然可以通过,即使它并没有真正测试是否正在检索2个新列。你明白我的意思吗?

我建议你看看这个线程,寻找一些可能的替代方案来测试你的数据访问层。对数据访问层使用mock将导致脆性测试,该测试不会真正测试任何

您的测试太大了,而且您似乎测试得太多了。

按照代码的自然中断来分割代码,这样进行数据检索的代码就与操作它的逻辑分离了

您只想测试您编写的代码,而不是第三方代码。它超出了你的需求范围,如果你不能信任它,就不要使用它。

相关内容

  • 没有找到相关文章