Java DAO 实现测试
Posted
技术标签:
【中文标题】Java DAO 实现测试【英文标题】:Java DAO implementation testing 【发布时间】:2012-11-17 19:16:45 【问题描述】:这是我想分享的一个非常简单的 DAO 尝试。
我的问题是这是否是测试 DAO 的正确方法。我的意思是我正在验证 SQL 查询并给它返回一个模拟。然后告诉模拟返回这些特定的值并断言它们?
我已将 DAO 更新为使用 prepared statement 而不是简单的 Statement。谢谢。
public class PanelDao implements IO
private final static Logger LOGGER = Logger.getLogger(PanelDao.class);
private Connection connection;
public PanelDao() throws SQLException
this(MonetConnector.getConnection());
public PanelDao(Connection connection) throws SQLException
this.connection = connection;
@Override
public void save(Panel panel) throws SQLException
final String query = "INSERT INTO panels VALUES ( ?, ?, ?, ?, ?, ?, ? )";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, panel.getId());
statement.setString(2, panel.getColor());
statement.setDate(3, (new Date(panel.getPurchased().getTime())) );
statement.setDouble(4, panel.getCost());
statement.setDouble(5, panel.getSellingPrice());
statement.setBoolean(6, panel.isOnSale());
statement.setInt(7, panel.getUserId());
LOGGER.info("Executing: "+query);
statement.executeUpdate();
@Override
public void update(Panel object)
throw new UnsupportedOperationException();
@Override
public void delete(Panel object)
throw new UnsupportedOperationException();
@Override
public Panel find(String id) throws SQLException
final String query = "SELECT * FROM panels WHERE id = ? ";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, id);
LOGGER.info("Executing: "+query);
final ResultSet result = statement.executeQuery();
final Panel panel = new Panel();
if (result.next())
panel.setId(result.getString("id"));
panel.setColor(result.getString("color"));
return panel;
还有测试类
public class PanelDaoTest
@InjectMocks
private PanelDao panelDao;
@Mock
private Connection connection;
@Mock
private Statement statement;
@Mock
private ResultSet result;
private Panel panel;
@BeforeClass
public static void beforeClass()
BasicConfigurator.configure();
@Before
public void init() throws SQLException
MockitoAnnotations.initMocks(this);
Mockito.when(connection.createStatement()).thenReturn(statement);
panel = new Panel("AZ489", "Yellow", new Date(), 10.00, 7.50, true, 1);
@Test
public void testSave() throws SQLException
Mockito.when(connection.prepareStatement("INSERT INTO panels VALUES ( ?, ?, ?, ?, ?, ?, ? )")).thenReturn(statement);
panelDao.save(panel);
Mockito.verify(statement).executeUpdate();
@Test
public void testFind() throws SQLException
Mockito.when(connection.prepareStatement("SELECT * FROM panels WHERE id = ? ")).thenReturn(statement);
Mockito.when(statement.executeQuery()).thenReturn(result);
Mockito.when(result.next()).thenReturn(true);
Mockito.when(result.getString("id")).thenReturn("AZ489");
Mockito.when(result.getString("color")).thenReturn("Yellow");
Panel panel = panelDao.find("AZ489");
assertEquals("AZ489",panel.getId());
assertEquals("Yellow",panel.getColor());
Mockito.verify(statement).executeQuery();
2.0 使用 HSQLDB 测试 DAO
考虑到您的反馈后,我决定使用 HSQLDB 进行真实的数据库测试。如果解决类似问题,请将此作为资源。
public class PanelDao implements IO
private final static Logger LOGGER = Logger.getLogger(PanelDao.class);
private Connection connection;
/**
* Default constructor is using Monet connector
*/
public PanelDao() throws SQLException
this(MonetConnector.getConnection());
public PanelDao(Connection connection) throws SQLException
this.connection = connection;
@Override
public void save(Panel panel) throws SQLException
final String query = "INSERT INTO panels VALUES ( ?, ?, ?, ?, ?, ?, ? )";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, panel.getId());
statement.setString(2, panel.getColor());
statement.setDate(3, (new Date(panel.getPurchased().getTime())) );
statement.setDouble(4, panel.getCost());
statement.setDouble(5, panel.getSellingPrice());
statement.setBoolean(6, panel.isOnSale());
statement.setInt(7, panel.getUserId());
LOGGER.info("Executing: "+query);
statement.executeUpdate();
@Override
public void update(Panel object)
throw new UnsupportedOperationException();
@Override
public void delete(Panel object)
throw new UnsupportedOperationException();
@Override
public Panel find(String id) throws SQLException
final String query = "SELECT * FROM panels WHERE id = ? ";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, id);
LOGGER.info("Executing: "+query);
final ResultSet result = statement.executeQuery();
if (result.next())
final Panel panel = new Panel();
panel.setId(result.getString("id"));
panel.setColor(result.getString("color"));
panel.setPurchased(new Date(result.getDate("purchased").getTime()));
panel.setCost(result.getDouble("cost"));
panel.setSellingPrice(result.getDouble("selling_price"));
panel.setOnSale(result.getBoolean("on_sale"));
panel.setUserId(result.getInt("user_id"));
return panel;
return null;
和测试类:
public class PanelDaoTest
private PanelDao panelDao;
private Panel panel;
/* HSQLDB */
private static Server server;
private static Statement statement;
private static Connection connection;
@BeforeClass
public static void beforeClass() throws SQLException
BasicConfigurator.configure();
server = new Server();
server.setAddress("127.0.0.1");
server.setDatabaseName(0, "bbtest");
server.setDatabasePath(0, ".");
server.setPort(9000);
server.start();
PanelDaoTest.connection = DriverManager.getConnection("jdbc:hsqldb:hsql://127.0.0.1:9000/bbtest", "SA", "");
PanelDaoTest.statement = PanelDaoTest.connection.createStatement();
@Before
public void createDatabase() throws SQLException
PanelDaoTest.statement.execute(SqlQueries.CREATE_PANEL_TABLE);
panelDao = new PanelDao(PanelDaoTest.connection);
@Test
public void testSave() throws SQLException
panel = new Panel();
panel.setId("A1");
panel.setPurchased(new Date());
panelDao.save(panel);
assertNotNull(panelDao.find("A1"));
@Test
public void testFind() throws SQLException
final String id = "45ZZE6";
panel = Panel.getPanel(id);
Panel received = panelDao.find(id);
assertNull(received);
panelDao.save(panel);
received = panelDao.find(id);
assertNotNull(received);
assertEquals(panel.getId(), received.getId());
assertEquals(panel.getColor(), received.getColor());
assertEquals(panel.getPurchased().getDate(), received.getPurchased().getDate());
assertEquals(panel.getPurchased().getMonth(), received.getPurchased().getMonth());
assertEquals(panel.getPurchased().getYear(), received.getPurchased().getYear());
assertEquals(panel.getCost(), received.getCost(),0.001);
assertEquals(panel.getSellingPrice(), received.getSellingPrice(),0.001);
assertEquals(panel.getUserId(), received.getUserId());
@After
public void tearDown() throws SQLException
statement.executeUpdate(SqlQueries.DROP_PANEL_TABLE);
@AfterClass
public static void stopServer()
server.shutdown();
【问题讨论】:
是的,我将使用准备好的语句谢谢 如果您只使用字符串连接而不是 StringBuilder,那将更容易阅读,而性能没有任何变化。逗号和引号变量也让它变得更加困难。 append(comma).append(quote) 而不是 append(",'")。使用 [+ "'" + panel.getXXX() + "',"] 每一行都会更易读 【参考方案1】:首先,您不应该通过连接创建 SQL 查询,因为它容易受到 SQL 注入的攻击。请改用PreparedStatement
s。
实际上,以这种方式测试 DAO 并没有多大意义。您的测试仅验证您的 DAO 是否正确地来回传递值,但它并未涵盖由 DAO 发出的 SQL 查询的正确性的真正复杂性。
换句话说,如果您想测试您的 DAO,您需要创建涉及真实数据库的集成测试。这样您就可以验证您的 DAO 发出的 SQL 查询是否正确。
【讨论】:
不应该模拟 DAO 而是使用真实的数据库或数据库测试框架进行测试?我看到使用模拟你不能测试 sql 查询只验证我们正在调用一个方法,该方法正在返回一个你要返回的对象.. 毫无意义? @locke 标准模拟技术在测试您的服务层(例如)时更有用,其中 代码 是构成困难逻辑的地方,或者您需要创建模拟来测试的地方单独测试你的类(例如,你想模拟你的 DAO)。通常,大多数 DAO 包含非常简单的代码,而查询构成了大部分复杂性。在这种情况下,传统的模拟就没那么有用了。【参考方案2】:我真的不认为这种测试方法真的能买到任何东西,而且测试代码非常脆弱。我会使用像DBUnit 这样的东西,它可以让你“模拟”你的数据库。这实际上可以让您测试查询的正确性。
【讨论】:
【参考方案3】:我会使用内存数据库(例如 H2)来测试您的 SQL 是否真正有效。
当你测试你的save
方法时,你的测试应该调用save
,然后从数据库中选择行并断言那里确实有东西。
当您测试 find
方法时,您的测试应该直接在数据库中插入一些行,然后调用 find
并断言实际找到了所需的行。
【讨论】:
H2 可能会有一点风险,因为 SQL 方言和数据库实现的差异。我建议在您将要使用的实际数据库平台上设置一个单元测试实例...如果您决定使用 H2,请确保设置适当的 compatibility mode。 @ach 这是非常中肯的建议。我的经验表明,H2 和其他平台在 SQL 等方面的差异相当小,尤其是如果您选择了正确的兼容模式。如果您的 SQL 中有错误,那么针对 H2 的测试会发现它们,100 次中有 99 次。相比之下,需要设置“真实”数据库实例会使您的 JUnit 或 TestNG 测试更笨拙、更慢、并且更难管理。当然,这两种方法都有优缺点。这可能是“做对你有用的事”的情况之一。 @DavidWallace,用你的句子(如果你的 SQL 中有错误,那么针对 H2 的测试会发现它们,100 次中有 99 次。)它不再是单元测试。要成为正确的单元测试,您必须在每次运行时都信任它。 @frantakocourek 你说得对。这就是为什么我没有将其称为单元测试。另外,我的意思是使用 H2 的测试将发现 100 个 SQL 错误中的 99 个 - 我并不是说这些测试的行为是不确定的。使用内存数据库的好处之一是您可以使您的测试具有确定性。以上是关于Java DAO 实现测试的主要内容,如果未能解决你的问题,请参考以下文章