【问题标题】:Writing tests that are efficient and not redundant编写高效且不冗余的测试
【发布时间】:2013-02-24 07:59:37
【问题描述】:

首先,我使用Scala,但任何Java 方法都可能有效。

我有一个与数据库连接的应用程序,我想在不修改数据库或数据库脱机的情况下运行我的测试和开发我的应用程序。

假设,我有一个连接到数据库的大型类(或模块),做所有你想做的事情,我如何从外部访问该类或其参数?

例如,如果我希望类正常运行,而不是 statement.executeUpdate( sql ),我想要一个 println( "Did: " + sql ),在此测试中不调用第一个方法。

显然,一种方法是简单地替换这些语句 - 或复制整个文件并替换它们。但它很容易出错,如果我把它改回来,我可能会忘记一些东西。另外,它非常多余。

如何解决这个问题? JUnit怎么办?

免责声明:请不要使用“参数化您的课程”之类的解决方案。我希望我的构造函数有很少的参数,我不想在调用它时指定所有内容。测试类在我的应用程序中是二等公民,它们对实际类/实际开发几乎没有影响。

【问题讨论】:

  • 模拟框架是一个很好的解决方案。它们可以让你交换你集成的对象的模拟版本,例如你的 Statement 或 Connection 对象,并且模拟对象可以配置为期望某些方法调用,如果它们没有发生或使用不正确的参数调用则失败等。Mocking 可以让你的测试更加解耦,这对单元测试来说是一件好事!

标签: java scala testing redundancy


【解决方案1】:

您应该定义一个包含类中所有数据库方法的接口。然后,确保您现有的数据库类实现该接口。

现在您有了一个接口,您可以模拟该类或开发一个存根类进行测试。存根类可以只打印 SQL 或任何您想要的。模拟类更强大,可用于确保您的业务逻辑正常工作。

最后一步是确保使用数据库的任何地方都在构造函数中接受您的接口。例如

public class ClassThatUsesTheDatabase {

    public ClassThatUsesTheDatabase(DatabaseProvider provider) {
      //...
    }
}

DatabaseProvider 是您的界面。这允许您使用存根或模拟测试ClassThatUsesTheDatabase。在生产中,您将使用您的具体实现来构建此类。

在我看来,这是编写依赖外部资源的应用程序的唯一明智的方法。


重新阅读您的问题后,我担心以下段落:

免责声明:请不要使用“参数化您的课程”之类的解决方案。我希望我的构造函数有很少的参数,我不想在调用它时指定所有内容。测试类在我的应用程序中是二等公民,它们对实际类/实际开发几乎没有影响。

测试班不是二等公民。如果您认真对待代码质量,它们与您的生产代码一样重要。是的,您经常必须设计生产代码以使其适合单元测试。这是不可避免的。然而,好处是巨大的。

【讨论】:

  • 二等公民我只是想澄清一下,我不想将测试硬编码到我的应用程序代码中。如if ( isTestPhase ) println( "Did: " + sql ) else execUpdate( sql )。是的,一些参数化似乎是不可避免的,我承认好处是巨大的。谢谢!
  • 我在适当的情况下使用真实的数据库支持测试我的 DAL。模拟很好——但它们只能捕捉到这么多。我通常发现“存根”它的好处是平庸的(尽管我仍然使用接口)。
  • (本地测试数据库和回滚事务很棒。)
  • 现在,我该如何实际应用测试?我已经围绕我的班级制作了一个界面。但我不想替换我的应用程序代码。我的代码应该看起来如何? public static DatabaseConnection get() { if( App.TEST_MODE ) return new MockDatabaseConnection(); else return new RealDatabaseConnection(); }???
  • @Danyel 一些顶级类应该负责创建RealDatabaseConnection 对象并将其传递给您的其他对象。该类应该具有最少的功能,并且不需要测试。您要测试的所有类都应在其构造函数中接受DatabaseConnection。应该没有必要做“if (test) {...代码。
【解决方案2】:

请不要使用“参数化您的课程”之类的解决方案。

那你做错了。您的数据库调用应该在易于用虚拟实现替换的模拟对象中。很有可能,如果您仍然使用 executeStatement,那么您很容易受到 SQL 注入的攻击。你应该重新考虑你的方法。

【讨论】:

    【解决方案3】:

    就我个人而言,我支持 Bob 叔叔,因为您希望将所有 DB 交互隔离到程序中的一个类中,以便您可以在测试中用其他东西替换它。如果不想参数化,还有蛋糕模式和依赖注入。

    现在,如果您坚持对与数据库通信的库的依赖项进行硬编码,您仍然可以做一件事:模拟数据库。这可能很难做到,因为您必须实现数据库接口。

    我实际上是在一个项目中这样做的,虽然它是一个基于 HTTP+JSON 的 NoSQL 数据库,所以模拟数据库就像模拟 Web 服务器一样简单——只需几十行未经过滤的代码即可产生所有所需的响应.

    然后只需在测试期间指向假数据库即可。

    不过,让代码更容易测试会让代码更好。

    【讨论】:

      【解决方案4】:

      对于 JUnit(和任何其他主流单元测试框架),标准的解决方案范围是某种依赖注入,这涉及到您所说的“参数化您的类”。没有王道,抱歉 :-) 但是,您可以通过各种方式来尽量减少或避免生产代码的中断。

      处理这类问题的常用方法是将要测试的逻辑与数据库访问代码分离,方法是提取后者并将其隐藏在可模拟接口后面。然后在被测代码中,statement.executeUpdate(sql) 之类的语句被dataAccessor.updateFooBar(foo, bar) 之类的调用替换。并且执行这些调用的数据访问器对象以某种方式注入到您的类中 - 它可能是一个可选的构造函数参数,它可能是一个默认值是真实数据访问器对象但它有一个具有包访问权限的设置器来设置单元测试期间的模拟数据访问器...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-11-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-07
        • 1970-01-01
        • 2018-02-08
        • 1970-01-01
        相关资源
        最近更新 更多