【问题标题】:Unit Testing(Mocking) Databases, How To Verify Database Methods With Mocking?单元测试(模拟)数据库,如何使用模拟验证数据库方法?
【发布时间】:2017-11-29 07:30:29
【问题描述】:

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

现在,我需要对一些在 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() 方法,它确实丢弃了表(实际上很明显,因为我正在调用真正的 DBConnection 实例)

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

【问题讨论】:

  • 代码看起来很奇怪。为什么你有两个DBConnection 对象?您正在“真实”实例上调用 dropTable() 。 dbConnection 是否以某种方式包含它委托的另一个 DBConnection 字段?如果没有,您的代码将毫无意义,因为您创建了一个模拟 DBConnection 和一个真实的。如果DBConnection 中有一个DBConnection 字段,这将不起作用。也许提供一些DBConnection 代码是个好主意。
  • 是的,我有 2 个实例。一个是真实对象,另一个是模拟对象。 DBConnection 类中没有成员类。 DBConnection 类只有 dropTable()、addEntry() 等方法,并且有私有字段,这些字段是提供给 DriverManager 的主机、数据库和端口信息(基本 JDBC 连接)
  • 好的,这真的很解释,感谢您的评论。我也期待这样的结果。但是,我如何在不接触数据库的情况下测试它呢?有什么办法吗?唯一的解决方案是创建与开发数据库具有相同配置的测试数据库吗?从您的评论中我了解到,我需要驾驶真正的汽车才能确保它能够正常工作?
  • 一旦您的代码与另一个系统交互,如果没有其他系统就无法可靠地测试它。这可以是数据库的副本或内存版本,但其他所有内容都是不完整的。也许有间接的测试方法,但这些方法需要创建高度复杂的测试,实际上可能只是模拟外部系统的很大一部分。对于这些东西,存在集成测试来测试您的系统与其他系统的集成程度。

标签: unit-testing junit mocking mockito tdd


【解决方案1】:

想象一下……你有两辆车,一辆是真车,另一辆是假(模拟)车。现在你进入真正的汽车并开走。为什么你期望不动?你没有对假车做任何事情,所以即使你告诉它“如果我开始开车就不要动”,这并不能改变你开车时真车会移动的事实。

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

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

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

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

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

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

【讨论】:

  • 所以这意味着,我不需要 Mocking(没有意义)来测试真正的数据库连接器类是否有效。模拟只是为我们的输入提供“假”所谓的输出。
  • 因为完全模拟它需要你有效地模拟整个数据库,所以这不是一个好的计划。如果你想测试一个所有依赖项都被模拟的单元,那么模拟是很棒的——例如,如果你有一个基于数据库数据的服务,那么你可以模拟 DBConnector 并将模拟数据提供给服务,因为你只想测试服务对数据的作用——确保 DBConnector 本身工作是另一个问题,应该单独进行单元测试。
  • 就像我需要从数据库中查询用户名密码集以在登录页面中使用它们。如果数据库尚未完成,我可以模拟并使用它来测试我的登录页面是否可以正常工作,而无需查询真实页面(因为它还没有工作)。我使用模拟来不测试模拟对象,测试对象使用模拟。
  • 宾果游戏。您可以单元!(!)通过为数据库相关对象提供模拟来测试您的整个登录相关的假设数据库的东西可以工作。实际的数据库内容将在其他地方进行(单元?)测试。当然,添加集成测试来测试所有内容以查看它们是否适合在一起也很好。
  • @FlorianSchaetz 仅供参考,在大型组织中有一种叫做声纳扫描的东西,如果您正在为某个组织工作,那么您应该进行代码覆盖(单元测试)。而如果项目中充满了 jdbc 调用,则很难编写单元测试用例来满足 sonar。所以嘲讽是为了声纳……所谓的法规……如果你想避免这种情况,我们应该停止为公司工作并建立自己的公司。创业是完全不同的话题。
【解决方案2】:

为什么不考虑为您的单元测试使用内存数据库。也许使用 DAO 模式在真实和内存之间轻松切换。

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

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

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

结合使用单元测试和集成测试,可以实现应用的整体信心。

【讨论】:

  • 我建议我的同事使用 In-Memory 数据库,但我们同意使用测试数据库进行单元测试。尽管我在我的问题中提供了非 DAO 模式数据库建模,但我们使用的是 DAO 方法(我也不熟悉)。我不确定它是否会影响我将使用的解决方案。我想我不明白 Mocking 的目的,但现在我明白了。我不负责整个测试过程(我希望),据我所知,其他人会做集成测试。
【解决方案3】:

对于@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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-08
    • 1970-01-01
    • 2010-09-23
    • 2021-05-22
    • 2020-05-16
    相关资源
    最近更新 更多