单元测试(模拟)数据库,如何使用模拟验证数据库方法?

Unit Testing(Mocking) Databases, How To Verify Database Methods With Mocking?

作为一个没有单元测试和模拟经验的人,我按照关于 JUnit 和 Mockito 的初学者教程进行了一些练习。

现在,我需要对一些 class 方法进行单元测试,这些方法在 MySQL 数据库上执行基本的数据库操作。我不想对数据库进行真正的更改。

在我的代码中:

@InjectMocks private DBConnection dbConnection;
@Mock private DBConnection mockDBConnection;

@Test
public void testDropTable() {

    Mockito.when(mockDBConnection.dropTable()).thenReturn(0);

    // dropTable() returns 0 if table dropped
    int result = dbConnection.dropTable();
    Assert.assertEquals(result, 0);
}

为了验证(使用 assertEquals() )测试结果,我调用了真正的 dropTable() 方法,它确实下降了 table(其实很明显,因为我正在调用真正的 DBConnection实例)

有没有办法在不访问真实数据库的情况下验证这样的方法?还是我误解了单元测试和模拟的概念?

对于@Florian Schaetz 的评论,我需要做这样的事情吗:

AuthenticatorApplicationInterface.java

public interface AuthenticatorInterface {

        public boolean authenticateUser(String username, String password) throws EmptyCredentialsException;
}

AuthenticatorApplication.java

public class AuthenticatorApplication {

    private AuthenticatorInterface authenticator;

    public AuthenticatorApplication(AuthenticatorInterface authenticator)                           {
            this.authenticator = authenticator;
    }

    public boolean authenticate(String username, String password) throws NotAuthenticatedException {
       boolean authenticated;
       authenticated = this.authenticator.authenticateUser(username, password);
        return authenticated;
        }
    }

AuthenticatorApplicationTest.java

public class AuthenticatorApplicationTest {

        @Test
        public void testAuthenticate() throws EmptyCredentialsException {
            AuthenticatorInterface authenticatorMock;
            AuthenticatorApplication authenticator;
            String username = "username";
            String password = "noncorrectpassword";

            authenticatorMock = Mockito.mock(AuthenticatorInterface.class);
            authenticator = new AuthenticatorApplication(authenticatorMock);

            when(authenticatorMock.authenticateUser(username, password)).thenReturn(false);

            boolean actual = authenticator.authenticate(username, password);

            assertFalse(actual);
    }
}

想象一下...您有两辆车,一辆是真车,另一辆是假(模拟)车。现在你进入真正的汽车并开车离开。为什么你希望不动?你没有对假车做任何事情,所以即使你告诉它 "Do not move if I start driving",这也不会改变真车在你开车时会移动的事实。

同样的事情,你创建了一个假的 DBConnection 和一个真实的。真正的人会做真正的人所做的一切。假的可以配置为做不同的事情,就像你对 Mockito.when(...) 所做的那样。但这并没有改变真实的行为。

您的测试实际上毫无意义,因为您在这里唯一的选择就是摆脱模拟,因为它没有任何意义。你不测试模拟。做什么的?这就像制造一辆假车并对其进行测试 - 它不会告诉您有关真车的任何信息。如果您在该测试中告诉它做 X,您不需要测试假车来发现它会做 X。

有两种方法可以测试您的 DBConnection class:

a) 您可以将它连接到数据库并检查它是否执行应有的操作。当然,这将是一个集成测试,而不是单元测试。在某些情况下,您可以使用内存数据库(如 HSQLDB)来加速此测试。但至少最后你可以合理地确定你的代码做了它应该做的事情。

b) 如果且仅当 DBConnection 内部有一些对象与数据库进行实际对话时,您或许可以模拟这些对象,然后测试 DBConnection。当然,这只会将您的问题转移到另一层,因为那样您就不确定这些对象是否有效——您只会知道 DBConnection 在这些对象有效时有效。虽然这很高兴知道,但它并没有回答您的数据库代码最终是否能正常工作的问题。

最后,您只能通过连接到数据库来完全测试数据库连接。其他一切都不是完整的测试。

为什么不考虑使用内存数据库进行单元测试。也许使用 DAO 模式可以轻松地在真实内存和内存中切换。

保持实际的数据库交互层尽可能薄,以确保您在单独的 类 中抽象出您的逻辑,这使测试更容易,因为在这种情况下,您可以模拟 DB 对象并专注于测试您的逻辑。

还要确定您要测试的内容,很可能您想要测试您的逻辑,而不是第 3 方数据库连接器的逻辑,因此请将您的测试重点放在这上面。

如果您需要这种级别的测试,请考虑使用一套集成或系统测试来针对真实数据库测试应用程序功能,但对于单元测试,内存中应该没问题。

您的应用程序的整体信心可以通过结合使用单元测试和集成测试来实现。