【问题标题】:How to validate a boolean method using test driven development approach?如何使用测试驱动开发方法验证布尔方法?
【发布时间】:2014-04-10 15:35:11
【问题描述】:

我正在尝试学习 Java,同时实施测试驱动开发 (TDD) 方法。我在面向对象的编程概念方面有一些经验,可以理解 Java,但不是 TDD。我编写了一个非常简单的 Java 程序以及单元测试,但在解决问题时遇到了一些问题。我认为这里的某个人可能能够澄清我的问题。我已经列出了问题、我的最终单元测试类、类实现逻辑、我解决问题的方法以及我遇到的问题。

问题:

我利用CodingBat 网站上列出的第一个问题SleepIn 来尝试实现简单的单元测试。这是网站上所述的问题。

如果是工作日,则参数 weekday 为 true,如果我们正在休假,则参数 Vacation 为 true。如果不是工作日,或者我们正在度假,我们就睡在外面。如果我们睡着了,则返回 true。

单元测试:

我编写了单元测试类并创建了四种方法来测试上述问题的可用输入的结果。

package com.codingbat.practice.unit;

import static org.junit.Assert.*;
import org.junit.Test;
import com.codingbat.practice.SleepIn;

public class SleepInTest {

    @Test
    public void weekend_and_not_vacation() {
        SleepIn sleepIn = new SleepIn();
        sleepIn.setWeekday(false);
        sleepIn.setVacation(false);
        assertTrue(sleepIn.allow());
    }

    @Test
    public void weekend_and_vacation() {
        SleepIn sleepIn = new SleepIn();
        sleepIn.setWeekday(false);
        sleepIn.setVacation(true);
        assertTrue(sleepIn.allow());
    }

    @Test
    public void weekday_and_not_vacation() {
        SleepIn sleepIn = new SleepIn();
        sleepIn.setWeekday(true);
        sleepIn.setVacation(false);
        assertFalse(sleepIn.allow());
    }

    @Test
    public void weekday_and_vacation() {
        SleepIn sleepIn = new SleepIn();
        sleepIn.setWeekday(true);
        sleepIn.setVacation(true);
        assertTrue(sleepIn.allow());
    }
}

类实现:

这是解决上述问题的类实现。

package com.codingbat.practice;

public class SleepIn {
    boolean weekday;
    boolean vacation;

    public SleepIn() {
    }

    public void setWeekday(boolean weekday) {
        this.weekday = weekday;
    }

    public void setVacation(boolean vacation) {
        this.vacation = vacation;
    }

    public boolean allow() {
        return (!this.weekday || this.vacation);
    }
}

我采取的方法:

  1. 编写了方法SleepIn.allow() 来简单地返回false

  2. 实现了四个单元测试并传递了适当的参数,并根据我认为应该是类实现的结果使用了必要的断言方法。

  3. 执行了测试用例。其中 3 次失败,1 次成功。

  4. 测试 weekday_and_not_vacation() 成功,因为它期望 false 并且实现当前仅返回 false。

  5. 在方法SleepIn.allow()中实现了必要的逻辑来解决问题。

  6. 再次执行测试。 4 项测试全部通过。

  7. 将实现逻辑中的 || 替换为 && 以破坏解决方案并再次运行测试以验证至少有一个测试失败。 4 次测试中有 2 次失败。再次将正确的逻辑放回方法中,以验证所有测试是否成功。

问题:

  1. 我对 TDD 的理解是在编写实现逻辑之前让测试失败。在这种情况下,该方法返回一个 boolean。因此,我只能返回 truefalse。我不能让所有的测试一开始就失败。推荐的 TDD 方法是否与我解决问题所采取的步骤相匹配?

  2. 在各个网站上阅读有关TDD的信息,我推断每个单元测试应该相互独立。基于此,我在每个单元测试中创建了一个SleepIn 的实例,以将所有测试逻辑限制在每个单元测试方法中。单元测试通常是这样实现的吗?

  3. 在实现类中,我可以创建带有两个参数的构造函数来实例化实例变量。但是,我创建了 setters,因为调用方法名称来传递值似乎更有意义。我知道如果不调用适当的设置器,这种情况下的实例变量将包含 false 。 Java 或一般的面向对象编程实现中推荐的方法是什么?我没有实现 getters,因为我觉得不需要这个问题。

  4. 我假设在编写单元测试时有必要知道任何给定输入的结果是什么,以便我们可以使用适当的断言。在这种情况下,输入是两个 布尔值,我知道输出是什么,因为问题非常简单。所以,我相应地选择了合适的assertTrueassertFalse。这是应该如何编写单元测试的方式吗?当逻辑变得复杂时,例如在现实世界的应用程序中,它将如何扩展?如果它变得复杂,是否意味着问题应该被分解并保持尽可能小,以便我们可以轻松地使用单元测试进行测试?

【问题讨论】:

    标签: java unit-testing tdd


    【解决方案1】:

    首先,单独解决您的问题:

    1.我对TDD的理解是在编写实现逻辑之前让测试失败。在这种情况下,该方法返回一个布尔值。因此,我只能返回真或假。我不能让所有的测试一开始就失败。推荐的 TDD 方法是否与我解决问题所采取的步骤相匹配?

    TDD requires humbly suggests 一次编写一个测试,实现在继续下一个测试之前通过每个测试所需的代码。

    这不仅减少了在任何给定时间编写的代码量,它实际上也影响了设计选择。听听你的测试。

    2.在各个网站上阅读TDD,我推断每个单元测试应该是相互独立的。基于此,我在每个单元测试中创建了一个 SleepIn 实例,以将所有测试逻辑限制在每个单元测试方法中。单元测试通常是这样实现的吗?

    这是在单元测试中处理SUT 的常用方法。主要是尽量减少/避免在测试之间共享资源(例如,类的实例)。如果在测试之间共享状态,那么编写它们就会变得更加困难。 听听你的测试。

    3.在实现类中,我可以创建带有两个参数的构造函数来实例化实例变量。但是,我创建了 setter,因为调用方法名称来传递值似乎更有意义。我知道如果不调用适当的设置器,这种情况下的实例变量将包含 false 。 Java 或一般的面向对象编程实现中推荐的方法是什么?我没有实现 getter,因为我觉得不需要这个问题。

    对此没有一般的经验法则;如果某些东西需要通过设置器配置参数,那么存在一个驱动力。但是,TDD 要求您只编写通过测试所需的代码。通过此测试是否需要设置器?正如您所提到的,还有其他方法可以将这些参数输入 SUT。 聆听您的测试。

    4.我假设在编写单元测试时有必要知道任何给定输入的结果是什么,以便我们可以使用适当的断言。在这种情况下,输入是两个布尔值,我知道输出是什么,因为问题非常简单。因此,我相应地选择了适当的 assertTrue 或 assertFalse。这是应该如何编写单元测试的方式吗?当逻辑变得复杂时,例如在现实世界的应用程序中,它将如何扩展?如果它变得复杂,是否意味着问题应该被分解并保持尽可能小,以便我们可以轻松地使用单元测试进行测试?

    您的测试具有确定性是绝对必要的。 (如果不是,你怎么知道它们是可靠的?)测试需要完全控制 SUT 运行的上下文。

    如果 SUT 需要生成一个随机数,测试应该supply a dummy object to the SUT 生成一个非随机数。

    如果 SUT 需要向系统时钟询问当前时间,则测试应提供一个有效及时冻结的对象。

    如果一个测试开始变得过于复杂而无法维护,它就是在试图告诉你一些事情。

    Listen to your tests.

    ...[W]当代码难以测试时,最可能的原因是我们的设计需要改进。现在使代码难以测试的相同结构将使将来难以更改。到未来到来时,更改将更加困难,因为我们会忘记编写代码时的想法。

    关于 TDD 的问题在于它实际上并不专注于测试,而是设计一个稳健且模块化的系统。测试恰好是验证此类设计的最简单方法之一(即,测试很难编写,除非您的系统是健壮且模块化的)。

    通过要求我们在编写任何相应代码之前编写测试,TDD 实质上迫使我们使用codify our requirements。也就是说,单元测试是系统需求的代码体现。

    考虑到这一点,从需求的角度编写测试很有帮助。 (“有这个要求吗?如果没有,那我为什么要写这些代码?”)

    例如,您的 CodingBat 问题的网页似乎暗示要求存在具有以下签名的方法:

    public boolean sleepIn(boolean weekday, boolean vacation) {
        // TODO
    }
    

    从这个前提出发,我们可以更好地回答您的问题 #3 - 我们可以将参数传递给函数本身,而不是在 setter 或类的构造函数中传递参数。

    现在让我们考虑问题的陈述要求:

    1. 如果是周末,让我们睡个懒觉
    2. 如果我们在度假,请允许我们睡觉

    我们应该能够将这些要求中的每一个映射到一个单独的单元测试中。想象一下,我们在单元测试can_sleep_in_on_weekend 中编写了需求#1。我们可以编写的最少代码量是多少?也许:

    package com.codingbat.practice;
    
    public class SleepIn {
    
        public SleepIn() {
        }
    
        public boolean sleepIn(boolean weekday, boolean vacation) {
            return !weekday;
        }
    }
    

    继续下一个需求,假设我们在单元测试can_sleep_in_during_vacation 中对其进行了编码。通过此测试的最少代码量是多少?

    package com.codingbat.practice;
    
    public class SleepIn {
    
        public SleepIn() {
        }
    
        public boolean sleepIn(boolean weekday, boolean vacation) {
            return !weekday || vacation;
        }
    }
    

    由于在此之后没有更多要求,我们已经完成了编写代码。

    【讨论】:

    • 这是一个非常有用的回复。我想现在我明白了如何思考以编写测试驱动代码。现在,我需要阅读有关 TDD 的更多信息并编写测试驱动代码来掌握它。感谢您花时间写这篇文章,也感谢有用的链接和书籍参考。
    • 很高兴我能帮上忙。掌握测试驱动代码的窍门可能需要一点时间,所以如果一开始感觉很奇怪,请不要灰心。
    【解决方案2】:

    在标准 TDD 中,您一次只应编写一个测试,然后让该测试通过。从正常情况开始,比如 weekday_and_not_vacation。看到它失败,因为你正在返回 true。返回 false,然后看着它通过。现在采取 not_weekday_and_not_vacation。看到它失败了,因为你返回的是假的。放入检查工作日的逻辑;现在两个测试都通过了。现在是weekday_and_vacation,起泡,冲洗,重复。

    1. 因为您首先编写了所有测试,所以您最终通过了一些测试。当您一次编写并通过一项测试时,这种情况不太可能发生。
    2. 为每个测试创建一个新的 SleepIn 实例是非常合理的。没有必要,但合理且绝不违反标准 TDD 实践。
    3. 设置器或参数都可以;在您使用该类的上下文中最有意义的任何内容。
    4. 是的,您应该始终能够提前知道预期结果是什么。如果您的问题太复杂,请将其分解,直到您可以说出测试的下一部分应该做什么。这是设计在测试驱动设计中发挥作用的关键部分。

    【讨论】:

    • 在阅读了您的回复和@Lilshieste 的回复后,我觉得现在我明白了如何继续为一个工作单元编写测试的增量开发风格,并使其通过测试而不是试图弄清楚为给定需求找出所有可能的测试。
    猜你喜欢
    • 1970-01-01
    • 2019-07-05
    • 1970-01-01
    • 2012-07-25
    • 2011-03-11
    • 1970-01-01
    • 2017-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多