<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## [測試驅動開發](https://lingcoder.gitee.io/onjava8/#/book/16-Validating-Your-Code?id=%e6%b5%8b%e8%af%95%e9%a9%b1%e5%8a%a8%e5%bc%80%e5%8f%91) 之所以可以有測試驅動開發(TDD)這種開發方式,是因為如果你在設計和編寫代碼時考慮到了測試,那么你不僅可以寫出可測試性更好的代碼,而且還可以得到更好的代碼設計。 一般情況下這個說法都是正確的。 一旦我想到“我將如何測試我的代碼?”,這個想法將使我的代碼產生變化,并且往往是從“可測試”轉變為“可用”。 純粹的 TDD 主義者會在實現新功能之前就為其編寫測試,這稱為測試優先的開發。 我們采用一個簡易的示例程序來進行說明,它的功能是反轉**String**中字符的大小寫。 讓我們隨意添加一些約束:**String**必須小于或等于30個字符,并且必須只包含字母,空格,逗號和句號(英文)。 此示例與標準 TDD 不同,因為它的作用在于接收**StringInverter**的不同實現,以便在我們逐步滿足測試的過程中來體現類的演變。 為了滿足這個要求,將**StringInverter**作為接口: ~~~ // validating/StringInverter.java package validating; interface StringInverter { String invert(String str); } ~~~ 現在我們通過可以編寫測試來表述我們的要求。 以下所述通常不是你編寫測試的方式,但由于我們在此處有一個特殊的約束:我們要對 \*\*StringInverter \*\*多個版本的實現進行測試,為此,我們利用了 JUnit5 中最復雜的新功能之一:動態測試生成。 顧名思義,通過它你可以使你所編寫的代碼在運行時生成測試,而不需要你對每個測試顯式編碼。 這帶來了許多新的可能性,特別是在明確地需要編寫一整套測試而令人望而卻步的情況下。 JUnit5 提供了幾種動態生成測試的方法,但這里使用的方法可能是最復雜的。 \*\*DynamicTest.stream() \*\*方法采用了: * 對象集合上的迭代器 (versions) ,這個迭代器在不同組的測試中是不同的。 迭代器生成的對象可以是任何類型,但是只能有一種對象生成,因此對于存在多個不同的對象類型時,必須人為地將它們打包成單個類型。 * **Function**,它從迭代器獲取對象并生成描述測試的**String**。 * **Consumer**,它從迭代器獲取對象并包含基于該對象的測試代碼。 在此示例中,所有代碼將在**testVersions()**中進行組合以防止代碼重復。 迭代器生成的對象是對**DynamicTest**的不同實現,這些對象體現了對接口不同版本的實現: ~~~ // validating/tests/DynamicStringInverterTests.java package validating; import java.util.*; import java.util.function.*; import java.util.stream.*; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.DynamicTest.*; 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) ); } } ~~~ 在一般的測試中,你可能認為在進行一個結果為失敗的測試時應該停止代碼構建。 但是在這里,我們只希望系統報告問題,但仍然繼續運行,以便你可以看到不同版本的**StringInverter**的效果。 每個使用**@TestFactory**注釋的方法都會生成一個**DynamicTest**對象的**Stream**(通過**testVersions()**),每個 JUnit 都像常規的**@Test**方法一樣執行。 現在測試都已經準備好了,我們就可以開始實現 \*\*StringInverter \*\*了。 我們從一個僅返回其參數的假的實現類開始: ~~~ // validating/Inverter1.java package validating; public class Inverter1 implements StringInverter { public String invert(String str) { return str; } } ~~~ 接下來我們實現反轉操作: ~~~ // validating/Inverter2.java 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; } } ~~~ 現在添加代碼以確保輸入不超過30個字符: ~~~ // validating/Inverter3.java 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 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; } } ~~~ 你將從測試輸出中看到,每個版本的**Inverter**都幾乎能通過所有測試。 當你在進行測試優先的開發時會有相同的體驗。 **DynamicStringInverterTests.java**僅是為了顯示 TDD 過程中不同**StringInverter**實現的開發。 通常,你只需編寫一組如下所示的測試,并修改單個**StringInverter**類直到它滿足所有測試: ~~~ // validating/tests/StringInverterTests.java package validating; import java.util.*; import java.util.stream.*; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; public class StringInverterTests { StringInverter inverter = new Inverter4(); @BeforeAll static void startMsg() { System.out.println(">>> StringInverterTests <<<"); } @Test void basicInversion1() { String in = "Exit, Pursued by a Bear."; String out = "eXIT, pURSUED BY A bEAR."; assertEquals(inverter.invert(in), out); } @Test void basicInversion2() { expectThrows(Error.class, () -> { assertEquals(inverter.invert("X"), "X"); }); } @Test void disallowedCharacters() { String disallowed = ";-_()*&^%$#@!~`0123456789"; 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("")); assertEquals(result, disallowed); } @Test void allowedCharacters() { String lowcase = "abcdefghijklmnopqrstuvwxyz ,."; String upcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ,."; assertEquals(inverter.invert(lowcase), upcase); assertEquals(inverter.invert(upcase), lowcase); } @Test void lengthNoGreaterThan30() { String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; assertTrue(str.length() > 30); expectThrows(RuntimeException.class, () -> { inverter.invert(str); }); } @Test void lengthLessThan31() { String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; assertTrue(str.length() < 31); inverter.invert(str); } } ~~~ 你可以通過這種方式進行開發:一開始在測試中建立你期望程序應有的所有特性,然后你就能在實現中一步步添加功能,直到所有測試通過。 完成后,你還可以在將來通過這些測試來得知(或讓其他任何人得知)當修復錯誤或添加功能時,代碼是否被破壞了。 TDD的目標是產生更好,更周全的測試,因為在完全實現之后嘗試實現完整的測試覆蓋通常會產生匆忙或無意義的測試。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看