使用 JUnit 进行 JAVA 单元测试最佳实践

问题描述 投票:0回答:2

我目前正在使用以下方法以及该方法的单元测试。我认为测试可以/应该分解为更多测试,但我不确定为此编写多少测试,或者更重要的部分是什么,特别是考虑到该方法涉及建立

Connection
、使用 sql 查询等...感谢所有帮助。

JAVA方法:

public static ArrayList<HashMap<String, String>> executeSelect(
        Connection conn, Statement stmt, Query query) {

    ResultSet rs = null;
    ArrayList<HashMap<String, String>> serviceRequests = new ArrayList<HashMap<String, String>>();

    try {
        long queryStart = System.nanoTime();
        rs = stmt.executeQuery(query.getQuery());
        long queryEnd = System.nanoTime();
        long queryDuration = queryEnd-queryStart;
        queryTime = String.valueOf(queryDuration);

        while (rs.next()) {

            HashMap<String, String> serviceRequestData = new HashMap<>();

            if (QueryUtil.hasColumn(rs, "ID")) {
                String id = rs.getString("ID");
                serviceRequestData.put("ID", id);
            }
            else{
                serviceRequestData.put("ID", " ");
            }
            if (QueryUtil.hasColumn(rs, "FN_Contact")) {
                String firstName = rs.getString("FN_Contact");
                serviceRequestData.put("FN_Contact", firstName);
            }
            else{
                serviceRequestData.put("FN_Contact", " ");
            }
            if (QueryUtil.hasColumn(rs, "LN_Contact")) {
                String lastName = rs.getString("LN_Contact");
                serviceRequestData.put("LN_Contact", lastName);
            }
            else{
                serviceRequestData.put("LN_Contact", " ");
            }
            if (QueryUtil.hasColumn(rs, "Notes")) {
                String notes = rs.getString("Notes");
                serviceRequestData.put("Notes", notes);
            }
            else{
                serviceRequestData.put("Notes", " ");
            }
            if (QueryUtil.hasColumn(rs, "Email")) {
                String email = rs.getString("Email");
                serviceRequestData.put("Email", email);
            }
            else{
                serviceRequestData.put("Email", " ");

            }

            serviceRequests.add(serviceRequestData);

        }
    } catch (SQLException e) {
        e.printStackTrace();
        sqlException = true;
    }
    return serviceRequests;
}

JUnit 测试:

@Test
public void testFirstName() {
    ArrayList<HashMap<String, String>> testMap = new ArrayList<HashMap<String,String>>();
    Connection conn = null;
    Statement stmt = null;
    try {
        Class.forName("com.mysql.jdbc.Driver");

        String connectionUrl = "jdbc:mysql://localhost:3306/gc_image";
        String connectionUser = "root";
        String connectionPassword = "GCImage";
        conn = DriverManager.getConnection(connectionUrl, connectionUser,
                connectionPassword);
        conn.
        stmt = conn.createStatement();
        Query testQuery = new Query();
        testQuery
                .setQuery("select * from service_request where FN_contact = 'Trevor'");
        testMap = QueryController.executeSelect(conn, stmt, testQuery);

        assertEquals("Janke", testMap.get(0).get("LN_Contact"));
        assertEquals("Hello World", testMap.get(0).get("Notes"));
        assertEquals("[email protected]", testMap.get(0).get("Email"));
        assertEquals("ID", testMap.get(0).get("7"));

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        try {
            stmt.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}
java unit-testing junit
2个回答
4
投票

您应该首先确定该测试设置的哪些部分并不是测试方法的真正一部分;即,可以提取到

@Before
@After
方法中的样板代码是什么。这可能需要您将一些局部变量放入类变量中。这使得每个
@Test
方法不再那么冗长,并允许您专注于测试中的功能。

接下来,您应该删除所有

catch
块,或者如果您的代码或测试代码引发意外异常,则测试会失败并出现类似
fail(exception.getMessage())
之类的内容。如果删除它们,很容易将单元测试的方法签名更改为
throws Exception
,以允许它在抛出异常时通常失败。就像现在一样,您可能完全无法连接到设置中的数据库,并且 Junit 测试仍然会变绿!

理想情况下,您应该有一个涵盖每个

if...else
块的单元测试。这会给你 10 次测试。其中,重点(
Assert
)是确认
serviceRequests
中是否存在找到/未找到的值。您还应该测试异常处理,因此您需要一个强制捕获
SQLException
的测试。

我最后建议,为了减少开销,您可以考虑使用模拟框架,例如 Mockito 来完全消除数据库 I/O。这个SO问题有一些模拟数据库的好例子。

我注意到

executeSelect()
中有一些可以修复的快速问题:

  • 删除
    Connection
    参数 - 它未使用,因为
    Statement
    已实例化。
  • 考虑抛出 SQLException 而不是返回列表——它会给出一个错误的广告,表明数据库读取已成功执行。
  • 优先选择接口和抽象类而不是具体类:将方法签名和
    serviceRequests
    ArrayList<HashMap<String, String>>
    更改为
    List<Map<String, String>>
    。在众多资源中,Josh Bloch 的《Effective Java》是该主题的一个很好的参考。
  • 您可以通过将以下代码移动到另一个方法中来进一步简化此方法 - 这将使测试更加简单:

long queryStart = System.nanoTime(); rs = stmt.executeQuery(query.getQuery()); long queryEnd = System.nanoTime(); long queryDuration = queryEnd-queryStart; queryTime = String.valueOf(queryDuration);

然后仅将 
ResultSet

传递到

executeSelect()
...或任何将重命名为的内容:)。
    


0
投票

您的单元测试应该是描述性的

当我阅读您的单元测试时,我必须花费太多精力来理解您想要验证的内容。使用良好的约定命名,不要害怕使用长方法名称(例如:executeSelect_Should_Return_Empty_List_When_No_Results())。

一次测试一件事

与上一点相同,尝试一次只测试一件事,这件事可能是:

我有结果吗
  • 如果没有结果怎么办?
  • 如果无法连接怎么办
  • 如果出现 SQL 错误(测试异常)会发生什么。
编写独立的单元测试

您的单元测试似乎依赖于真实的数据库。您的测试不应该依赖于外部资源,例如数据库(甚至是本地数据库)、文件系统、REST 或 SOAP 服务等。相反,您可以“模拟”您的数据库。 此外,外部资源可能会减慢您的测试速度。

避免单元测试中的逻辑

逻辑使您的测试可读性较差,并且可能会在测试中引入错误。测试是你最不想出现错误的地方。确实,您没有像“if、for、switch 等”这样的结构,但是 try...catch 在那里不是必需的,这是您应该分解为多个测试用例的东西。更喜欢向方法测试签名添加例外。如前所述,进行其他测试来验证异常(即:

assertThrows

)。

使用常量而不是“魔法值”

更喜欢使用常量而不是字符串值,尝试使用描述性常量名称来表达测试的意图(即:EXPECTED_FIRST_NAME)

来源:

单元测试最佳实践

© www.soinside.com 2019 - 2024. All rights reserved.