我正在尝试测试一种使用休眠和嘲笑连接到数据库的方法。
这是我的代码
用户道.java
public interface UserDAO {
public void addUser(String username, String password);
public List<String> getUsers();
}
用户DAOImpl.java
public class UserDAOImpl implements UserDAO {
public static final Logger LOG = LoggerFactory.getLogger(UserDAOImpl.class);
private static Session session;
public UserDAOImpl() {
}
public UserDAOImpl(Session session) {
this.session = session;
}
private static void beginSession() {
session = DbUtils.getSessionFactory().openSession();
session.beginTransaction();
}
@Override
public void addUser(String username, String password) {
String encryptedPassword = Utils.encrypt(password);
User user = new User(username, encryptedPassword);
beginSession();
try {
session.save(user);
System.out.println(user.getPassword());
session.getTransaction().commit();
} catch (SQLGrammarException e) {
session.getTransaction().rollback();
LOG.error("Cannot save user", e);
} finally {
session.close();
}
}
@Override
public List<String> getUsers() {
beginSession();
List<String> results = new ArrayList<String>();
String hql = "select username from User";
Query query = null;
try {
query = session.createQuery(hql);
results = query.list();
} catch (HibernateException e) {
LOG.error("Cannot execute query", e);
}
return results;
}
}
测试用户DAOImpl
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class TestUserDAOImpl {
@Mock
SessionFactory sessionFactory;
@Mock
Session session;
@Before
public void setup() {
when(sessionFactory.getCurrentSession()).thenReturn(session);
}
@Test
public void testCreate() {
// userDAOImpl = new UserDAOImpl(session);
UserDAOImpl userDAOImpl = Mockito.mock(UserDAOImpl.class);
String username = "username";
String password = "password";
userDAOImpl.addUser(username, password);
System.out.println(userDAOImpl.getUsers());
}
}
测试用例向数据库添加了一组用户名和密码,但是当我尝试使用getUsers()
返回结果时,它会返回一个空列表。
任何人都可以帮我解决这个问题吗?
首先,您没有向数据库添加任何用户,因为userDAOImpl
是一个模拟对象,因此,正如Joe C指出的那样,addUser
方法永远不会在真实对象上调用。出于同样的原因(userDAOImpl
被嘲笑),getUsers
方法不返回任何列表。
就像你告诉sessionFactory
做什么,然后调用它的getCurrentSession()
方法一样,你可以告诉userDAOImpl
当它的方法addUser
并getUsers
被调用时该做什么。
作为旁注
:testCreate()
方法不应包含System.out.println
方法,因为JUnit
无法知道测试应该通过还是失败。如果您使用的是Mockito
,则可以使用verify
方法来确保执行某些代码行。
或者,如果要测试存储库,可以使用内存数据库并创建实际对象以将数据插入数据库并从数据库中读取数据。这样,您的主数据库就不会被测试数据污染。这是一篇关于内存中测试数据库的好文章。
更新:使用 Mockito 测试UserDAOImpl
类
我做的第一件事是,稍微改变了UserDAOImpl
类。原因是:你不能用Mockito
来模拟静态方法(至少在写这篇文章的时候不能)。更多关于这个 这里.
我将session
对象传递给UserDAOImpl
并使用该会话开始事务,而不是使用DbUtils
的静态方法。
下面是修改后的UserDAOImpl
类:
package test.mockito;
import java.util.ArrayList;
import java.util.List;
public class UserDAOImpl implements UserDAO {
public static final Logger LOG = LoggerFactory.getLogger(UserDAOImpl.class);
private Session session;
public UserDAOImpl(Session session) {
this.session = session;
}
@Override
public void addUser(String username, String password) {
String encryptedPassword = Utils.encrypt(password);
User user = new User(username, encryptedPassword);
session.beginTransaction();
try {
session.save(user);
System.out.println(user.getPassword());
session.getTransaction().commit();
} catch (SQLGrammarException e) {
session.getTransaction().rollback();
if(LOG != null)LOG.error("Cannot save user", e);
} finally {
session.close();
}
}
@Override
public List<String> getUsers() {
session.beginTransaction();
List<String> results = new ArrayList<String>();
String hql = "select username from User";
Query query = null;
try {
query = session.createQuery(hql);
results = query.list();
} catch (HibernateException e) {
if(LOG != null)LOG.error("Cannot execute query", e);
}
return results;
}
}
让我们看看如何使用模拟对象测试UserDAOImpl
的方法。最初,我们模拟未测试的对象,但我们需要它们来执行待测试代码的语句。
package test.mockito;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class UserDAOImplTest {
@Mock
Session session;
@Mock
Transaction transaction;
@Before
public void setUp() {
when(session.getTransaction()).thenReturn(transaction);
}
@Test
public void addUserTest() {
UserDAO userDAO = new UserDAOImpl(session);
userDAO.addUser("testusername", "testpassword");
try {
verify(session).getTransaction();
verify(session.getTransaction()).commit();
} catch (SQLGrammarException e) {
fail(e.getMessage());
}
verify(session).close();
}
}
注意:我们不是在测试Session
,也不是在测试Transaction
;因此我们使用Mockito
将这些对象提供给我们的UserDAOImpl
类。我们假设方法save(User user)
、commit()
和其他模拟对象的方法工作正常,所以我们只测试addUser
方法。使用Mockito
我们不需要建立与数据库的真实会话,而是模拟对象,这更容易、更快捷(此外,即使我们还没有开发代码的其他部分,也可以测试addUser
方法 - 其他开发人员可能正在研究)。
在上面的测试用例中,addUserTest()
方法测试当模拟对象的方法按预期运行时,此方法是否正确执行。通过使用verify(session.getTransacion()).commit()
我们确保调用commit()
方法,否则在catch
块中测试失败。
以同样的方式,我们可以测试addUser
在抛出异常时是否回滚事务。以下是您可以做到这一点的方法:
@Test
public void addUserTestFails() {
UserDAO userDAO = new UserDAOImpl(session);
try {
doThrow(new SQLGrammarException()).when(session).save(any());
userDAO.addUser("testusername", "testpassword");
verify(transaction, never()).commit();
} catch (SQLGrammarException e) {
verify(transaction).rollback();
}
}
此测试用例测试addUser
方法的行为是否在保存更改时引发异常时除外。您可以通过告诉模拟对象在使用此代码段调用save
方法时抛出异常来模拟异常doThrow(new SQLGrammarException()).when(session).save(any());
,然后确保永远不会使用verify(transaction, never()).commit();
调用commit()
方法。在catch
块中,验证事务是否已回滚:verify(transaction).rollback()
。
下面是包含这两个测试用例的完整类:
package test.mockito;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class UserDAOImplTest {
@Mock
Session session;
@Mock
Transaction transaction;
@Before
public void setUp() {
when(session.getTransaction()).thenReturn(transaction);
}
@Test
public void addUserTest() {
UserDAO userDAO = new UserDAOImpl(session);
userDAO.addUser("testusername", "testpassword");
try {
verify(session).getTransaction();
verify(session.getTransaction()).commit();
} catch (SQLGrammarException e) {
fail(e.getMessage());
}
verify(session).close();
}
@Test
public void addUserTestFails() {
UserDAO userDAO = new UserDAOImpl(session);
try {
doThrow(new SQLGrammarException()).when(session).save(any());
userDAO.addUser("testusername", "testpassword");
verify(transaction, never()).commit();
} catch (SQLGrammarException e) {
verify(transaction).rollback();
}
}
}
建议:退后一步。现在。
使用 JUnit 和 Mockito 来测试休眠和 DOA 是没有意义的......当你缺乏如何编写单元测试的基本理解时。在您的代码中:
@Test
public void testCreate() {
// userDAOImpl = new UserDAOImpl(session);
UserDAOImpl userDAOImpl = Mockito.mock(UserDAOImpl.class);
String username = "username";
String password = "password";
userDAOImpl.addUser(username, password);
System.out.println(userDAOImpl.getUsers());
}
几乎没有什么意义。典型的单元测试更像是:
class UnderTest {
Foo foo;
UnderTest(Foo foo) { this.foo = foo; }
int bar(String whatever) { return foo.bar(whatever); }
}
class UnderTestTest {
@Mock
Foo foo;
UnderTest underTest;
@Before
public void setup() { underTest = new UnderTest(foo); }
@Test
public void testBar() {
when(foo.bar(any()).thenReturn(5);
assertThat(underTest.bar(), is(5);
}
}
笔记:
你- 不会嘲笑你打算测试的类。你模拟那些被测试的类需要完成其工作的对象;但是,您仍然只模拟那些必须模拟才能使测试通过的对象。
- 然后,在被测试对象上调用一个方法;要么断言某些预期结果返回;要么使用模拟验证来检查对模拟对象的预期调用是否发生了。
长话短说:你应该花几天时间阅读有关JUnit和Mockito的教程。换句话说:在参加跨栏比赛之前学会爬行!