<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ### 實現 @Unit 首先我們需要定義所有的注解類型。這些都是簡單的標簽,并且沒有任何字段。@Test 標簽在本章開頭已經定義過了,這里是其他所需要的注解: ```java // onjava/atunit/TestObjectCreate.java // The @Unit @TestObjectCreate tag package onjava.atunit; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestObjectCreate {} ``` ```java // onjava/atunit/TestObjectCleanup.java // The @Unit @TestObjectCleanup tag package onjava.atunit; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestObjectCleanup {} ``` ```java // onjava/atunit/TestProperty.java // The @Unit @TestProperty tag package onjava.atunit; import java.lang.annotation.*; // Both fields and methods can be tagged as properties: @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface TestProperty {} ``` 所有測試的保留屬性都為 **RUNTIME**,這是因為 **@Unit** 必須在編譯后的代碼中發現這些注解。 要實現系統并運行測試,我們還需要反射機制來提取注解。下面這個程序通過注解中的信息,決定如何構造測試對象,并在測試對象上運行測試。正是由于注解幫助,這個程序才會如此短小而直接: ```java // onjava/atunit/AtUnit.java // An annotation-based unit-test framework // {java onjava.atunit.AtUnit} package onjava.atunit; import java.lang.reflect.*; import java.io.*; import java.util.*; import java.nio.file.*; import java.util.stream.*; import onjava.*; public class AtUnit implements ProcessFiles.Strategy { static Class<?> testClass; static List<String> failedTests= new ArrayList<>(); static long testsRun = 0; static long failures = 0; public static void main(String[] args) throws Exception { ClassLoader.getSystemClassLoader() .setDefaultAssertionStatus(true); // Enable assert new ProcessFiles(new AtUnit(), "class").start(args); if(failures == 0) System.out.println("OK (" + testsRun + " tests)"); else { System.out.println("(" + testsRun + " tests)"); System.out.println( "\n>>> " + failures + " FAILURE" + (failures > 1 ? "S" : "") + " <<<"); for(String failed : failedTests) System.out.println(" " + failed); } } @Override public void process(File cFile) { try { String cName = ClassNameFinder.thisClass( Files.readAllBytes(cFile.toPath())); if(!cName.startsWith("public:")) return; cName = cName.split(":")[1]; if(!cName.contains(".")) return; // Ignore unpackaged classes testClass = Class.forName(cName); } catch(IOException | ClassNotFoundException e) { throw new RuntimeException(e); } TestMethods testMethods = new TestMethods(); Method creator = null; Method cleanup = null; for(Method m : testClass.getDeclaredMethods()) { testMethods.addIfTestMethod(m); if(creator == null) creator = checkForCreatorMethod(m); if(cleanup == null) cleanup = checkForCleanupMethod(m); } if(testMethods.size() > 0) { if(creator == null) try { if(!Modifier.isPublic(testClass .getDeclaredConstructor() .getModifiers())) { System.out.println("Error: " + testClass + " no-arg constructor must be public"); System.exit(1); } } catch(NoSuchMethodException e) { // Synthesized no-arg constructor; OK } System.out.println(testClass.getName()); } for(Method m : testMethods) { System.out.print(" . " + m.getName() + " "); try { Object testObject = createTestObject(creator); boolean success = false; try { if(m.getReturnType().equals(boolean.class)) success = (Boolean)m.invoke(testObject); else { m.invoke(testObject); success = true; // If no assert fails } } catch(InvocationTargetException e) { // Actual exception is inside e: System.out.println(e.getCause()); } System.out.println(success ? "" : "(failed)"); testsRun++; if(!success) { failures++; failedTests.add(testClass.getName() + ": " + m.getName()); } if(cleanup != null) cleanup.invoke(testObject, testObject); } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } } public static class TestMethods extends ArrayList<Method> { void addIfTestMethod(Method m) { if(m.getAnnotation(Test.class) == null) return; if(!(m.getReturnType().equals(boolean.class) || m.getReturnType().equals(void.class))) throw new RuntimeException("@Test method" + " must return boolean or void"); m.setAccessible(true); // If it's private, etc. add(m); } } private static Method checkForCreatorMethod(Method m) { if(m.getAnnotation(TestObjectCreate.class) == null) return null; if(!m.getReturnType().equals(testClass)) throw new RuntimeException("@TestObjectCreate " + "must return instance of Class to be tested"); if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) < 1) throw new RuntimeException("@TestObjectCreate " + "must be static."); m.setAccessible(true); return m; } private static Method checkForCleanupMethod(Method m) { if(m.getAnnotation(TestObjectCleanup.class) == null) return null; if(!m.getReturnType().equals(void.class)) throw new RuntimeException("@TestObjectCleanup " + "must return void"); if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) < 1) throw new RuntimeException("@TestObjectCleanup " + "must be static."); if(m.getParameterTypes().length == 0 || m.getParameterTypes()[0] != testClass) throw new RuntimeException("@TestObjectCleanup " + "must take an argument of the tested type."); m.setAccessible(true); return m; } private static Object createTestObject(Method creator) { if(creator != null) { try { return creator.invoke(testClass); } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException("Couldn't run " + "@TestObject (creator) method."); } } else { // Use the no-arg constructor: try { return testClass.newInstance(); } catch(InstantiationException | IllegalAccessException e) { throw new RuntimeException( "Couldn't create a test object. " + "Try using a @TestObject method."); } } } } ``` 雖然它可能是“過早的重構”(因為它只在書中使用過一次),**AtUnit.java** 使用了 **ProcessFiles** 工具逐步判斷命令行中的參數,決定它是一個目錄還是文件,并采取相應的行為。這可以應用于不同的解決方法,是因為它包含了一個 可用于自定義的 **Strategy** 接口: ```java // onjava/ProcessFiles.java package onjava; import java.io.*; import java.nio.file.*; public class ProcessFiles { public interface Strategy { void process(File file); } private Strategy strategy; private String ext; public ProcessFiles(Strategy strategy, String ext) { this.strategy = strategy; this.ext = ext; } public void start(String[] args) { try { if(args.length == 0) processDirectoryTree(new File(".")); else for(String arg : args) { File fileArg = new File(arg); if(fileArg.isDirectory()) processDirectoryTree(fileArg); else { // Allow user to leave off extension: if(!arg.endsWith("." + ext)) arg += "." + ext; strategy.process( new File(arg).getCanonicalFile()); } } } catch(IOException e) { throw new RuntimeException(e); } } public void processDirectoryTree(File root) throws IOException { PathMatcher matcher = FileSystems.getDefault() .getPathMatcher("glob:**/*.{" + ext + "}"); Files.walk(root.toPath()) .filter(matcher::matches) .forEach(p -> strategy.process(p.toFile())); } } ``` **AtUnit** 類實現了 **ProcessFiles.Strategy**,其包含了一個 `process()` 方法。在這種方式下,**AtUnit** 實例可以作為參數傳遞給 **ProcessFiles** 構造器。第二個構造器的參數告訴 **ProcessFiles** 如尋找所有包含 “class” 拓展名的文件。 如下是一個簡單的使用示例: ```java // annotations/DemoProcessFiles.java import onjava.ProcessFiles; public class DemoProcessFiles { public static void main(String[] args) { new ProcessFiles(file -> System.out.println(file), "java").start(args); } } ``` 輸出為: ```java .\AtUnitExample1.java .\AtUnitExample2.java .\AtUnitExample3.java .\AtUnitExample4.java .\AtUnitExample5.java .\AUComposition.java .\AUExternalTest.java .\database\Constraints.java .\database\DBTable.java .\database\Member.java .\database\SQLInteger.java .\database\SQLString.java .\database\TableCreator.java .\database\Uniqueness.java .\DemoProcessFiles.java .\HashSetTest.java .\ifx\ExtractInterface.java .\ifx\IfaceExtractorProcessor.java .\ifx\Multiplier.java .\PasswordUtils.java .\simplest\Simple.java .\simplest\SimpleProcessor.java .\simplest\SimpleTest.java .\SimulatingNull.java .\StackL.java .\StackLStringTst.java .\Testable.java .\UseCase.java .\UseCaseTracker.java ``` 如果沒有命令行參數,這個程序會遍歷當前的目錄樹。你還可以提供多個參數,這些參數可以是類文件(帶或不帶.class擴展名)或目錄。 回到我們對 **AtUnit.java** 的討論,因為 **@Unit** 會自動找到可測試的類和方法,所以不需要“套件”機制。 **AtUnit.java** 中存在的一個我們必須要解決的問題是,當它發現類文件時,類文件名中的限定類名(包括包)不明顯。為了發現這個信息,必須解析類文件 - 這不是微不足道的,但也不是不可能的。 找到 .class 文件時,會打開它并讀取其二進制數據并將其傳遞給 `ClassNameFinder.thisClass()`。 在這里,我們正在進入“字節碼工程”領域,因為我們實際上正在分析類文件的內容: ```java // onjava/atunit/ClassNameFinder.java // {java onjava.atunit.ClassNameFinder} package onjava.atunit; import java.io.*; import java.nio.file.*; import java.util.*; import onjava.*; public class ClassNameFinder { public static String thisClass(byte[] classBytes) { Map<Integer,Integer> offsetTable = new HashMap<>(); Map<Integer,String> classNameTable = new HashMap<>(); try { DataInputStream data = new DataInputStream( new ByteArrayInputStream(classBytes)); int magic = data.readInt(); // 0xcafebabe int minorVersion = data.readShort(); int majorVersion = data.readShort(); int constantPoolCount = data.readShort(); int[] constantPool = new int[constantPoolCount]; for(int i = 1; i < constantPoolCount; i++) { int tag = data.read(); // int tableSize; switch(tag) { case 1: // UTF int length = data.readShort(); char[] bytes = new char[length]; for(int k = 0; k < bytes.length; k++) bytes[k] = (char)data.read(); String className = new String(bytes); classNameTable.put(i, className); break; case 5: // LONG case 6: // DOUBLE data.readLong(); // discard 8 bytes i++; // Special skip necessary break; case 7: // CLASS int offset = data.readShort(); offsetTable.put(i, offset); break; case 8: // STRING data.readShort(); // discard 2 bytes break; case 3: // INTEGER case 4: // FLOAT case 9: // FIELD_REF case 10: // METHOD_REF case 11: // INTERFACE_METHOD_REF case 12: // NAME_AND_TYPE case 18: // Invoke Dynamic data.readInt(); // discard 4 bytes break; case 15: // Method Handle data.readByte(); data.readShort(); break; case 16: // Method Type data.readShort(); break; default: throw new RuntimeException("Bad tag " + tag); } } short accessFlags = data.readShort(); String access = (accessFlags & 0x0001) == 0 ? "nonpublic:" : "public:"; int thisClass = data.readShort(); int superClass = data.readShort(); return access + classNameTable.get( offsetTable.get(thisClass)).replace('/', '.'); } catch(IOException | RuntimeException e) { throw new RuntimeException(e); } } // Demonstration: public static void main(String[] args) throws Exception { PathMatcher matcher = FileSystems.getDefault() .getPathMatcher("glob:**/*.class"); // Walk the entire tree: Files.walk(Paths.get(".")) .filter(matcher::matches) .map(p -> { try { return thisClass(Files.readAllBytes(p)); } catch(Exception e) { throw new RuntimeException(e); } }) .filter(s -> s.startsWith("public:")) // .filter(s -> s.indexOf('$') >= 0) .map(s -> s.split(":")[1]) .filter(s -> !s.startsWith("enums.")) .filter(s -> s.contains(".")) .forEach(System.out::println); } } ``` 輸出為: ```java onjava.ArrayShow onjava.atunit.AtUnit$TestMethods onjava.atunit.AtUnit onjava.atunit.ClassNameFinder onjava.atunit.Test onjava.atunit.TestObjectCleanup onjava.atunit.TestObjectCreate onjava.atunit.TestProperty onjava.BasicSupplier onjava.CollectionMethodDifferences onjava.ConvertTo onjava.Count$Boolean onjava.Count$Byte onjava.Count$Character onjava.Count$Double onjava.Count$Float onjava.Count$Integer onjava.Count$Long onjava.Count$Pboolean onjava.Count$Pbyte onjava.Count$Pchar onjava.Count$Pdouble onjava.Count$Pfloat onjava.Count$Pint onjava.Count$Plong onjava.Count$Pshort onjava.Count$Short onjava.Count onjava.CountingIntegerList onjava.CountMap onjava.Countries onjava.Enums onjava.FillMap onjava.HTMLColors onjava.MouseClick onjava.Nap onjava.Null onjava.Operations onjava.OSExecute onjava.OSExecuteException onjava.Pair onjava.ProcessFiles$Strategy onjava.ProcessFiles onjava.Rand$Boolean onjava.Rand$Byte onjava.Rand$Character onjava.Rand$Double onjava.Rand$Float onjava.Rand$Integer onjava.Rand$Long onjava.Rand$Pboolean onjava.Rand$Pbyte onjava.Rand$Pchar onjava.Rand$Pdouble onjava.Rand$Pfloat onjava.Rand$Pint onjava.Rand$Plong onjava.Rand$Pshort onjava.Rand$Short onjava.Rand$String onjava.Rand onjava.Range onjava.Repeat onjava.RmDir onjava.Sets onjava.Stack onjava.Suppliers onjava.TimedAbort onjava.Timer onjava.Tuple onjava.Tuple2 onjava.Tuple3 onjava.Tuple4 onjava.Tuple5 onjava.TypeCounter ``` 雖然無法在這里介紹其中所有的細節,但是每個類文件都必須遵循一定的格式,而我已經盡力用有意義的字段來表示這些從 **ByteArrayInputStream** 中提取出來的數據片段。通過施加在輸入流上的讀操作,你能看出每個信息片的大小。例如每一個類的頭 32 個 bit 總是一個 “神秘數字” **0xcafebabe**,而接下來的兩個 **short** 值是版本信息。常量池包含了程序的常量,所以這是一個可變的值。接下來的 **short** 告訴我們這個常量池有多大,然后我們為其創建一個尺寸合適的數組。常量池中的每一個元素,其長度可能是固定式,也可能是可變的值,因此我們必須檢查每一個常量的起始標記,然后才能知道該怎么做,這就是 switch 語句的工作。我們并不打算精確的分析類中所有的數據,僅僅是從文件的起始一步一步的走,直到取得我們所需的信息,因此你會發現,在這個過程中我們丟棄了大量的數據。關于類的信息都保存在 **classNameTable** 和 **offsetTable** 中。在讀取常量池之后,就找到了 **this_class** 信息,這是 **offsetTable** 的一個坐標,通過它可以找到進入 **classNameTable** 的坐標,然后就可以得到我們所需的類的名字了。 現在讓我們回到 **AtUtil.java** 中,process() 方法中擁有了類的名字,然后檢查它是否包含“.”,如果有就表示該類定義于一個包中。沒有包的類會被忽略。如果一個類在包中,那么我們就可以使用標準的類加載器通過 `Class.forName()` 將其加載進來。現在我們可以對這個類進行 **@Unit** 注解的分析工作了。 我們只需要關注三件事:首先是 **@Test** 方法,它們被保存在 **TestMehtods** 列表中,然后檢查其是否具有 @TestObjectCreate 和 **@TestObjectCleanup****** 方法。從代碼中可以看到,我們通過調用相應的方法來查詢注解從而找到這些方法。 每找到一個 @Test 方法,就打印出來當前類的名字,于是觀察者立刻就可以知道發生了什么。接下來開始執行測試,也就是打印出方法名,然后調用 createTestObject() (如果存在一個加了 @TestObjectCreate 注解的方法),或者調用默認構造器。一旦創建出來測試對象,如果調用其上的測試方法。如果測試的返回值為 boolean,就捕獲該結果。如果測試方法沒有返回值,那么就沒有異常發生,我們就假設測試成功,反之,如果當 assert 失敗或者有任何異常拋出的時候,就說明測試失敗,這時將異常信息打印出來以顯示錯誤的原因。如果有失敗的測試發生,那么還要統計失敗的次數,并將失敗所屬的類和方法加入到 failedTests 中,以便最后報告給用戶。
                  <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>

                              哎呀哎呀视频在线观看