如何改进我的FizzBu​​zz解决方案以获得TDD角色?

sea*_*ges 7 java tdd fizzbuzz

我最近接受了采访,要求我制作传统的FizzBu​​zz解决方案:

输出1到100之间的数字列表.

  • 对于3和5的所有倍数,该数字将替换为"FizzBu​​zz"
  • 对于所有剩余的3的倍数,该数字将替换为"Fizz"
  • 对于所有剩余的5的倍数,该数字将替换为"Buzz"

我的解决方案是用Java编写的,但这不是必需的.面试官很想看到一些TDD的证据,所以本着这种精神,我开始制作我自己的本土FizzBu​​zz单元测试:

public class FizzBuzzTest {

    @Test
    public void testReturnsAnArrayOfOneHundred() {
        String[] result = FizzBuzz.getResultAsArray();
        assertEquals(100, result.length);
    }

    @Test
    public void testPrintsAStringRepresentationOfTheArray() {
        String result = FizzBuzz.getResultAsString();
        assertNotNull(result);
        assertNotSame(0, result.length());
        assertEquals("1, 2", result.substring(0, 4));
    }

    @Test
    public void testMultiplesOfThreeAndFivePrintFizzBuzz() {
        String[] result = FizzBuzz.getResultAsArray();

        // Check all instances of "FizzBuzz" in array
        for (int i = 1; i <= 100; i++) {
            if ((i % 3) == 0 && (i % 5) == 0) {
                assertEquals("FizzBuzz", result[i - 1]);
            }
        }
    }

    @Test
    public void testMultiplesOfThreeOnlyPrintFizz() {
        String[] result = FizzBuzz.getResultAsArray();

        // Check all instances of "Fizz" in array
        for (int i = 1; i <= 100; i++) {
            if ((i % 3) == 0 && !((i % 5) == 0)) {
                assertEquals("Fizz", result[i - 1]);
            }
        }
    }

    @Test
    public void testMultiplesOfFiveOnlyPrintBuzz() {
        String[] result = FizzBuzz.getResultAsArray();

        // Check all instances of "Buzz" in array
        for (int i = 1; i <= 100; i++) {
            if ((i % 5) == 0 && !((i % 3) == 0)) {
                assertEquals("Buzz", result[i - 1]);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的结果变为:

public class FizzBuzz {

    private static final int MIN_VALUE = 1;
    private static final int MAX_VALUE = 100;


    private static String[] generate() {
        List<String> items = new ArrayList<String>();

        for (int i = MIN_VALUE; i <= MAX_VALUE; i++) {

            boolean multipleOfThree = ((i % 3) == 0);
            boolean multipleOfFive = ((i % 5) == 0);

            if (multipleOfThree && multipleOfFive) {
                items.add("FizzBuzz");
            }
            else if (multipleOfThree) {
                items.add("Fizz");
            }
            else if (multipleOfFive) {
                items.add("Buzz");
            }
            else {
                items.add(String.valueOf(i));
            }
        }

        return items.toArray(new String[0]);
    }

    public static String[] getResultAsArray() {
        return generate();
    }

    public static String getResultAsString() {
        String[] result = generate();
        String output = "";
        if (result.length > 0) {
            output = Arrays.toString(result);
            // Strip out the brackets from the result
            output = output.substring(1, output.length() - 1);
        }
        return output;
    }

    public static final void main(String[] args) {
        System.out.println(getResultAsString());
    }
}
Run Code Online (Sandbox Code Playgroud)

整个解决方案在一天晚上花了大约20分钟,包括在提交之前紧张地检查我的代码的时间比需要的时间长得多:)

回顾我最初提交的内容:早期我决定将我的"倍数"计算合并到generate()方法中以避免过度工程,我现在认为这是一个错误; 另外,单独的getResultAsArray/generate方法显然是OTT.getResultAsString也可以与main()方法合并,因为一个只是委托给另一个.

我对TDD仍然缺乏经验,我觉得这可能让我失望了.我正在寻找其他方法,我可能会改进这种方法,特别是在TDD实践方面?


更新

基于下面非常有用的建议,我已经重新设计了我现在认为更"TDD友好"的答案:

变化:

  • 将FizzBu​​zz逻辑与输出生成分开,使解决方案更具可扩展性

  • 每次测试只需一个断言,以简化它们

  • 在每种情况下仅测试最基本的逻辑单元

  • 还验证了确认弦乐构建的最终测试

代码:

public class FizzBuzzTest {

    @Test
    public void testMultipleOfThreeAndFivePrintsFizzBuzz() {
        assertEquals("FizzBuzz", FizzBuzz.getResult(15));
    }

    @Test
    public void testMultipleOfThreeOnlyPrintsFizz() {
        assertEquals("Fizz", FizzBuzz.getResult(93));
    }

    @Test
    public void testMultipleOfFiveOnlyPrintsBuzz() {
        assertEquals("Buzz", FizzBuzz.getResult(10));
    }

    @Test
    public void testInputOfEightPrintsTheNumber() {
        assertEquals("8", FizzBuzz.getResult(8));
    }

    @Test
    public void testOutputOfProgramIsANonEmptyString() {
        String out = FizzBuzz.buildOutput();
        assertNotNull(out);
        assertNotSame(0, out.length());
    }
}

public class FizzBuzz {

    private static final int MIN_VALUE = 1;
    private static final int MAX_VALUE = 100;

    public static String getResult(int input) {
        boolean multipleOfThree = ((input % 3) == 0);
        boolean multipleOfFive = ((input % 5) == 0);

        if (multipleOfThree && multipleOfFive) {
            return "FizzBuzz";
        }
        else if (multipleOfThree) {
            return "Fizz";
        }
        else if (multipleOfFive) {
            return "Buzz";
        }
        return String.valueOf(input);
    }

    public static String buildOutput() {
        StringBuilder output = new StringBuilder();

        for (int i = MIN_VALUE; i <= MAX_VALUE; i++) {
            output.append(getResult(i));

            if (i < MAX_VALUE) {
                output.append(", ");
            }
        }

        return output.toString();
    }

    public static final void main(String[] args) {
        System.out.println(buildOutput());
    }
}
Run Code Online (Sandbox Code Playgroud)

APC*_*APC 6

TDD与XP和敏捷哲学密切相关的原因是有道理的.它驱使我们使用可测试代码的小单元.因此,像TheSimplestThingWhichCouldPossiblyWork或单一责任原则这样的概念不属于测试驱动的方法.

在您的方案中显然没有发生这种情况.你注意的是数字数组,而不是FizzBu​​zz位(线索确实存在于问题中).

显然你处于完全人为的状态,并且很难伪造TDD.但我希望"真正的"TDD代码能够暴露翻译方法.这个:

@Test     
public void testOtherNumber() {        
     String result = FizzBuzz.translateNumber(23);
     assertEquals("23", result);
 } 

@Test     
public void testMultipleOfThree() {        
     String result = FizzBuzz.translateNumber(3);
     assertEquals("Fizz", result);
 } 

@Test     
public void testMultipleOfFive() {        
     String result = FizzBuzz.translateNumber(25);
     assertEquals("Buzz", result);
 } 

@Test     
public void testMultipleOfFifteen() {        
     String result = FizzBuzz.translateNumber(45);
     assertEquals("FizzBuzz", result);
 } 
Run Code Online (Sandbox Code Playgroud)

关键在于每个产生清晰的结果,并且很容易从失败的测试开始.

完成FizzBu​​zz位后,可以轻松完成数组操作.关键是要避免硬编码.最初我们可能不想要一个完整的实现:生成相对少量的元素就足够了,比如15.这样做的好处是可以产生更好的设计.毕竟,如果面试官回来说"实际上我想要一个121个元素的阵列",你需要改变多少代码?多少次测试?


TDD面临的挑战之一就是知道从哪里开始.Gojko Adzic写了一篇发人深思的文章,描述了一个实现Go游戏的Coding Dojo.


"有没有机会暴露我的翻译方法会因为以后封装的原因而对我不利?"

TDD中最激烈争论的话题之一.可能的答案是:

  1. 保持方法私有并将单元测试嵌入到类中.
  2. 编写针对公共方法的测试,然后将测试通过的方法设为私有,并重构测试.
  3. 上面的变化:使用条件编译(或类似)来公开或隐藏方法.
  4. 只是让他们公开

没有正确的答案,通常取决于具体要求或个人心血来潮.例如,虽然FizzBu​​zz本身很简单,但我们经常需要编写代码来获取数据,应用业务规则并返回验证结果.有时,规则需要应用于单个数据项,有时针对整个记录集,有时针对任何一个.

因此,暴露这两种方法的API不一定是错误的.当然,在面试的情况下,它让您有机会讨论API设计的细微差别,这是一个很好的对话话题.