簡體   English   中英

Junit Mockito 測試一切

[英]Junit Mockito test everything

我現在正在搜索更多小時但沒有結果。 請幫忙...

這是我要測試的課程:

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)而不是局部變量時。 但我不希望它們(甚至連接)不是全球性的。

=== 編輯 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));
    }

我認為它在真正的方法中被覆蓋了......因為它不會拋出異常並且該方法不會失敗。

您的測試代碼有多個問題。

  • 測試冗長而脆弱
  • 多個測試需要相同的(詳細)設置
  • 您不測試真實對象,而是使用類的模擬進行測試

前兩個問題可以通過將重復代碼提取到設置方法中來解決(我為 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以返回連接模擬。 被測試的模擬類可能會導致不同的難以發現的問題。

要解決問題,您有 3 個選擇:

  1. 重構您的代碼並注入一些連接工廠。 因此,您將能夠在測試中模擬它。

  2. 在您的測試中擴展類 DBSelectSchema 並覆蓋方法mensaDB()以便它將返回mensaDB()連接

  3. 在調用 getVersionFromDB() 之前,使用 H2 等嵌入式數據庫並將測試數據放入“數字”表中

選項1

將創建的連接提取到單獨的類並在您的DBSelectSchema使用它:

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

然后將其注入您的 DBSelectSchema:

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

現在你的測試,你可以使用真正的DBSelectSchema類嘲笑的ConnectionFactory

    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 方言可能與生產中使用的 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();
}

然后在您的測試中,您只需將所需的記錄添加到數據庫:

 @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。 使用您將編寫的測試(使用模擬),即使它沒有真正測試是否正在檢索 2 個新列,它仍然會通過。 你明白我的意思嗎?

我建議您查看此線程以獲取一些可能的替代方法來測試您的數據訪問層。 對你的數據訪問層使用模擬最終會導致脆弱的測試,它不會真正測試任何東西

你的測試太大了,你似乎測試太多了。

沿着它的自然中斷拆分您的代碼,以便進行數據檢索的代碼與操作它的邏輯分開。

只是想測試你寫的代碼,而不是第三方的代碼。 它超出了您的需求范圍,如果您不信任它,請不要使用它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM