validating - Test-Driven Development

The premise of Test-Driven Development(TDD) is that if we design and write our code with testing in mind, we not only create testable code, it will also be better designed. In general, this seems to hold true. If I'm thinking "how will I test this?" it makes my code different, and oftentimes "testable" translates to "usable".

Example:

interface

// validating/StringInverter.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package validating;

interface StringInverter {
  String invert(String str);
}

four implement classes 

// validating/Inverter1.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package validating;

public class Inverter1 implements StringInverter {
  public String invert(String str) {
    return str;
  }
}
// validating/Inverter2.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package validating;

import static java.lang.Character.*;

public class Inverter2 implements StringInverter {
  public String invert(String str) {
    String result = "";
    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);
      result += isUpperCase(c) ? toLowerCase(c) : toUpperCase(c);
    }
    return result;
  }
}
// validating/Inverter3.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package validating;

import static java.lang.Character.*;

public class Inverter3 implements StringInverter {
  public String invert(String str) {
    if (str.length() > 30) {
      throw new RuntimeException("argument too long!");
    }
    String result = "";
    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);
      result += isUpperCase(c) ? toLowerCase(c) : toUpperCase(c);
    }
    return result;
  }
}
// validating/Inverter4.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package validating;

import static java.lang.Character.*;

public class Inverter4 implements StringInverter {
  static final String ALLOWED = "abcdefghijklmnopqrstuvwxyz ,." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  public String invert(String str) {
    if (str.length() > 30) {
      throw new RuntimeException("argument too long!");
    }
    String result = "";
    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);
      if (ALLOWED.indexOf(c) == -1) {
        throw new RuntimeException(c + " Not allowed");
      }
      result += isUpperCase(c) ? toLowerCase(c) : toUpperCase(c);
    }
    return result;
  }
}

 JUnit5: dynamic test generation

// validating/tests/DynamicStringInverterTests.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package validating;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.DynamicTest.*;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import org.junit.jupiter.api.*;

class DynamicStringInverterTests {
  // Combine operations to prevent code duplication:
  Stream<DynamicTest> testVersions(String id, Function<StringInverter, String> test) {
    List<StringInverter> versions =
        Arrays.asList(
            new Inverter1(), new Inverter2(),
            new Inverter3(), new Inverter4());
    return DynamicTest.stream(
        versions.iterator(),
        inverter -> inverter.getClass().getSimpleName(),
        inverter -> {
          System.out.println(inverter.getClass().getSimpleName() + ": " + id);
          try {
            if (test.apply(inverter) != "fail") {
              System.out.println("Success");
            }
          } catch (Exception | Error e) {
            System.out.println("Exception: " + e.getMessage());
          }
        });
  }

  String isEqual(String lval, String rval) {
    if (lval.equals(rval)) {
      return "success";
    }
    System.out.println("FAIL: " + lval + " != " + rval);
    return "fail";
  }

  @BeforeAll
  static void startMsg() {
    System.out.println(">>> Starting DynamicStringInverterTests <<<");
  }

  @AfterAll
  static void endMsg() {
    System.out.println(">>> Finished DynamicStringInverterTests <<<");
  }

  @TestFactory
  Stream<DynamicTest> basicInversion1() {
    String in = "Exit, Pursued by a Bear.";
    String out = "eXIT, pURSUED BY A bEAR.";
    return testVersions(
        "Basic inversion (should succeed)", inverter -> isEqual(inverter.invert(in), out));
  }

  @TestFactory
  Stream<DynamicTest> basicInversion2() {
    return testVersions(
        "Basic inversion (should fail)", inverter -> isEqual(inverter.invert("X"), "X"));
  }

  @TestFactory
  Stream<DynamicTest> disallowedCharacters() {
    String disallowed = ";-_()*&^%$#@!~`0123456789";
    return testVersions(
        "Disallowed characters",
        inverter -> {
          String result =
              disallowed
                  .chars()
                  .mapToObj(
                      c -> {
                        String cc = Character.toString((char) c);
                        try {
                          inverter.invert(cc);
                          return "";
                        } catch (RuntimeException e) {
                          return cc;
                        }
                      })
                  .collect(Collectors.joining(""));
          if (result.length() == 0) return "success";
          System.out.println("Bad characters: " + result);
          return "fail";
        });
  }

  @TestFactory
  Stream<DynamicTest> allowedCharacters() {
    String lowcase = "abcdefghijklmnopqrstuvwxyz ,.";
    String upcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.";
    return testVersions(
        "Allowed characters (should succeed)",
        inverter -> {
          assertEquals(inverter.invert(lowcase), upcase);
          assertEquals(inverter.invert(upcase), lowcase);
          return "success";
        });
  }

  @TestFactory
  Stream<DynamicTest> lengthNoGreaterThan30() {
    String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    assertTrue(str.length() > 30);
    return testVersions(
        "Length must be less than 31 (throws exception)", inverter -> inverter.invert(str));
  }

  @TestFactory
  Stream<DynamicTest> lengthLessThan31() {
    String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    assertTrue(str.length() < 31);
    return testVersions(
        "Length must be less than 31 (should succeed)", inverter -> inverter.invert(str));
  }
}

references:

1. On Java 8 - Bruce Eckel

2. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/validating/StringInverter.java

3. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/validating/tests/StringInverterTests.java

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值