testing.md 332.7 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 6498 6499 6500 6501 6502 6503 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 6567 6568 6569 6570 6571 6572 6573 6574 6575 6576 6577 6578 6579 6580 6581 6582 6583 6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 7064 7065 7066 7067 7068 7069 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7081 7082 7083 7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 7094 7095 7096 7097 7098 7099 7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 7116 7117 7118 7119 7120 7121 7122 7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 7141 7142 7143 7144 7145 7146 7147 7148 7149 7150 7151 7152 7153 7154 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198 7199 7200 7201 7202 7203 7204 7205 7206 7207 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 7219 7220 7221 7222 7223 7224 7225 7226 7227 7228 7229 7230 7231 7232 7233 7234 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 7250 7251 7252 7253 7254 7255 7256 7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 7271 7272 7273 7274 7275 7276 7277 7278 7279 7280 7281 7282 7283 7284 7285 7286 7287 7288 7289 7290 7291 7292 7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440
# 测试

本章介绍 Spring 对集成测试的支持和单元测试的最佳实践。 Spring 团队提倡测试驱动开发。 Spring 团队已经发现,控制反转的正确使用确实使单元测试和集成测试变得更容易(在在类上存在 setter 方法和适当的构造函数,使得它们更容易在测试中连接在一起,而无需设置服务定位器注册中心和类似的结构)。

## 1. Spring 测试简介

测试是 Enterprise 软件开发中不可缺少的一部分。本章重点讨论了 IOC 原则对[单元测试](#unit-testing)的增值,以及 Spring 框架支持[集成测试](#integration-testing)的好处。(在 Enterprise 中对测试的彻底处理超出了本参考手册的范围。)

## 2. 单元测试

与传统的 爪哇 EE 开发相比,依赖注入应该使你的代码更少地依赖于容器。组成应用程序的 POJO 应该在 JUnit 或 TestNG 测试中是可测试的,使用`new`操作符实例化对象,而不需要 Spring 或任何其他容器。你可以使用[模拟对象](#mock-objects)(与其他有价值的测试技术一起使用)来孤立地测试你的代码。如果你遵循 Spring 的体系结构建议,那么你的代码库的干净分层和组件化将促进更容易的单元测试。例如,你可以通过截断或模拟 DAO 或存储库接口来测试服务层对象,而不需要在运行单元测试时访问持久性数据。

真正的单元测试通常运行得非常快,因为没有要设置的运行时基础设施。强调真正的单元测试作为开发方法的一部分,可以提高你的工作效率。你可能不需要测试章节的这一部分来帮助你为基于 IOC 的应用程序编写有效的单元测试。然而,对于某些单元测试场景, Spring 框架提供了模拟对象和测试支持类,这在本章中进行了描述。

### 2.1.模拟对象

Spring 包括一些专门用于嘲弄的软件包:

* [环境](#mock-objects-env)

* [JNDI](#mock-objects-jndi)

* [Servlet API](#mock-objects-servlet)

* [Spring Web Reactive](#mock-objects-web-reactive)

#### 2.1.1.环境

`org.springframework.mock.env`包包含`Environment``PropertySource`抽象的模拟实现(参见[Bean Definition Profiles](core.html#beans-definition-profiles)和[`PropertySource`抽象](core.html#beans-property-source-abstraction))。`MockEnvironment``MockPropertySource`对于开发依赖于环境特定属性的代码的容器外测试非常有用。

#### 2.1.2.JNDI

`org.springframework.mock.jndi`包包含 JNDI SPI 的部分实现,你可以使用它为测试套件或独立应用程序设置一个简单的 JNDI 环境。例如,如果 JDBC`DataSource`实例在测试代码中与在 爪哇 EE 容器中绑定到相同的 JNDI 名称,则可以在测试场景中重用应用程序代码和配置,而无需进行修改。

|   |在`org.springframework.mock.jndi`包中的模拟 JNDI 支持是<br/>在 Spring Framework5.2 中正式反对的,以支持来自第三方<br/>的完整解决方案,例如[SIMPLE-JNDI](https://github.com/h-thurow/Simple-JNDI)。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 2.1.3. Servlet 空气污染指数

`org.springframework.mock.web`包包含一组完整的 API 模拟对象,这些对象对于测试 Web 上下文、控制器和过滤器非常有用。这些模拟对象针对 Spring 的 Web MVC 框架的使用,并且通常比动态模拟对象(例如[EasyMock](http://easymock.org/))或可选的 Servlet API 模拟对象(例如[模拟对象](http://www.mockobjects.com))更方便地使用。

|   |自 Spring Framework5.0 以来,基于 Servlet 4.0API,`org.springframework.mock.web`中的模拟对象是<br/>。|
|---|--------------------------------------------------------------------------------------------------------------------|

Spring MVC 测试框架构建在模拟 Servlet API 对象上,以提供 Spring MVC 的集成测试框架。见[MockMvc](#spring-mvc-test-framework)

#### 2.1.4. Spring 网络反应

`org.springframework.mock.http.server.reactive`包包含用于 WebFlux 应用程序的`ServerHttpRequest``ServerHttpResponse`的模拟实现。`org.springframework.mock.web.server`包包含一个 mock`ServerWebExchange`,它依赖于这些 mock 请求和响应对象。

`MockServerHttpRequest``MockServerHttpResponse`都从相同的抽象基类扩展为特定于服务器的实现,并与它们共享行为。例如,一旦创建了一个模拟请求,它是不可变的,但是你可以使用`ServerHttpRequest`中的`mutate()`方法来创建一个修改过的实例。

为了让模拟响应正确地实现写契约并返回一个写完成句柄(即`Mono<Void>`),它默认情况下使用`Flux``cache().then()`,这将缓冲数据并使其可用于测试中的断言。应用程序可以设置一个自定义的写函数(例如,测试一个无限的流)。

[WebTestClient](#webtestclient)构建在模拟请求和响应的基础上,为在没有 HTTP 服务器的情况下测试 WebFlux 应用程序提供支持。客户机还可以用于与正在运行的服务器进行端到端测试。

### 2.2.单元测试支持类

Spring 包括许多可以帮助单元测试的类。它们可分为两类:

* [通用测试工具](#unit-testing-utilities)

* [Spring MVC Testing Utilities](#unit-testing-spring-mvc)

#### 2.2.1.通用测试工具

`org.springframework.test.util`包包含几个用于单元和集成测试的通用实用程序。

`ReflectionTestUtils`是一组基于反射的实用方法。你可以在测试场景中使用这些方法,在这些场景中,你需要更改一个常量的值,设置一个非`public`字段,调用一个非`public`setter 方法,或者在测试应用程序代码时调用一个非`public`配置或生命周期回调方法,例如以下情况:

* 允许`private``protected`字段访问的 ORM 框架(例如 JPA 和 Hibernate),而不是用于域实体中属性的`public`setter 方法。

* Spring 对注释的支持(例如`@Autowired``@Inject``@Resource`),它们为`private``protected`字段、setter 方法和配置方法提供依赖注入。

* 对于生命周期回调方法,使用`@PostConstruct``@PreDestroy`之类的注释。

[`AopTestUtils`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/util/aoptestutils.html)是与 AOP 相关的实用方法的集合。可以使用这些方法获得隐藏在一个或多个 Spring 代理后面的底层目标对象的引用。例如,如果你已经通过使用 EasyMock 或 Mockito 之类的库将 Bean 配置为动态模拟,并且该模拟被包装在 Spring 代理中,那么你可能需要直接访问底层模拟以在其上配置期望并执行验证。关于 Spring 的核心 AOP 实用程序,请参见[`AopUtils`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/ AOP/support/aoputils.html)和[`AopProxyUtils`(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/ AOP/aopyutils.html)。

#### 2.2.2. Spring MVC 测试工具

`org.springframework.test.web`包包含[`ModelAndViewAssert`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/web/modelandviewassert.html),你可以将其与 JUnit、TestNG 或处理 Spring MVC<gtr="435"/>对象的任何其他测试框架结合使用。

|   |单元测试 Spring MVC 控制器<br/><br/>以单元测试你的 Spring MVC`Controller`类作为 POJO,使用`ModelAndViewAssert``MockHttpServletRequest`结合,`MockHttpSession`,从 Spring 的[Servlet API mocks](#mock-objects-servlet)以此类推。对于 Spring MVC 的<br/> Spring MVC 和 REST`Controller`类以及`WebApplicationContext`配置,要进行彻底的集成测试,请使用[Spring MVC Test Framework](#spring-mvc-test-framework)代替。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 3. 集成测试

本节(本章其余部分的大部分内容)涵盖了 Spring 应用程序的集成测试。它包括以下主题:

* [Overview](#integration-testing-overview)

* [集成测试的目标](#integration-testing-goals)

* [JDBC 测试支持](#integration-testing-support-jdbc)

* [注解](#integration-testing-annotations)

* [Spring TestContext Framework](#testcontext-framework)

* [MockMvc](#spring-mvc-test-framework)

### 3.1.概述

重要的是能够执行一些集成测试,而不需要部署到应用程序服务器或连接到其他 Enterprise 基础设施。这样做可以让你测试以下内容:

* 你的 Spring IOC 容器上下文的正确接线。

* 使用 JDBC 或 ORM 工具进行数据访问。这可以包括诸如 SQL 语句的正确性、 Hibernate 查询、 JPA 实体映射等等。

Spring 框架为`spring-test`模块中的集成测试提供了一流的支持。实际 jar 文件的名称可能包括发布版本,也可能是长`org.springframework.test`格式,这取决于你从哪里获得它(有关解释,请参见[抚养管理一节](core.html#dependency-management))。这个库包括`org.springframework.test`包,其中包含用于使用 Spring 容器进行集成测试的有价值的类。此测试不依赖于应用程序服务器或其他部署环境。这样的测试比单元测试运行得慢,但比同等的 Selenium 测试或依赖于部署到应用程序服务器的远程测试快得多。

单元和集成测试支持是以注释驱动的[Spring TestContext Framework](#testcontext-framework)的形式提供的。TestContext 框架与实际使用的测试框架无关,该框架允许在各种环境中测试,包括 JUnit、TestNG 和其他环境。

### 3.2.集成测试的目标

Spring 的集成测试支持具有以下主要目标:

* 管理测试之间的[Spring IoC container caching](#testing-ctx-management)

* 提供[测试夹具实例的依赖注入](#testing-fixture-di)

* 提供适合于集成测试的[事务管理](#testing-tx)

* 提供[Spring-specific base classes](#testing-support-classes),以帮助开发人员编写集成测试。

接下来的几节描述了每个目标,并提供了实现和配置细节的链接。

#### 3.2.1.上下文管理和缓存

Spring TestContext 框架提供 Spring `ApplicationContext`实例和`WebApplicationContext`实例的一致加载以及这些上下文的缓存。对加载上下文的缓存的支持很重要,因为启动时间可能会成为一个问题——这不是因为 Spring 本身的开销,而是因为 Spring 容器实例化的对象需要时间来实例化。例如,一个包含 50 到 100 个 Hibernate 映射文件的项目可能需要 10 到 20 秒的时间来加载映射文件,而在每个测试装置中运行每个测试之前产生的成本会导致总体测试运行速度较慢,从而降低开发人员的工作效率。

测试类通常声明 XML 的资源位置数组或 Groovy 配置元数据(通常在 Classpath 中),或者用于配置应用程序的组件类数组。这些位置或类与`web.xml`或用于生产部署的其他配置文件中指定的位置或类相同或相似。

默认情况下,一旦加载,配置的`ApplicationContext`将在每个测试中重用。因此,每个测试套件只产生一次安装成本,并且随后的测试执行要快得多。在这种情况下,术语“测试套件”是指在相同的 JVM 中运行的所有测试——例如,针对给定项目或模块的 Ant、 Maven 或 Gradle 构建的所有测试。在不太可能的情况下,测试会破坏应用程序上下文并需要重新加载(例如,通过修改 Bean 定义或应用程序对象的状态),可以将 TestContext 框架配置为重新加载配置并在执行下一个测试之前重新构建应用程序上下文。

请参阅使用 TestContext 框架的[上下文管理](#testcontext-ctx-management)[上下文缓存](#testcontext-ctx-management-caching)

#### 3.2.2.测试夹具的依赖注入

当 TestContext 框架加载应用程序上下文时,它可以通过使用依赖项注入来配置测试类的实例。这提供了一种方便的机制,可以通过使用应用程序上下文中预先配置的 bean 来设置测试装置。这里的一个很大的好处是,你可以跨各种测试场景重用应用程序上下文(例如,用于配置 Spring-托管对象图、事务代理、`DataSource`实例和其他),从而避免了为单个测试用例重复复杂的测试固定设置的需要。

例如,考虑一个场景,其中我们有一个类(`HibernateTitleRepository`),它实现了`Title`域实体的数据访问逻辑。我们希望编写测试以下领域的集成测试:

* Spring 配置:基本上,与`HibernateTitleRepository` Bean 的配置相关的一切都是正确的和存在的吗?

* Hibernate 映射文件配置:是否所有映射都正确,以及是否存在正确的延迟加载设置?

* `HibernateTitleRepository`的逻辑:这个类的配置实例是否如预期的那样执行?

参见使用[TestContext 框架](#testcontext-fixture-di)的测试固定件的依赖注入。

#### 3.2.3.事务管理

在访问真实数据库的测试中,一个常见的问题是它们对持久性存储状态的影响。即使在使用开发数据库时,对状态的更改也可能会影响将来的测试。此外,许多操作(例如插入或修改持久数据)无法在事务之外执行(或验证)。

TestContext 框架解决了这个问题。默认情况下,框架为每个测试创建并回滚一个事务。你可以编写可以假设存在事务的代码。如果你在测试中调用事务性代理对象,那么根据它们配置的事务语义,它们的行为是正确的。此外,如果一个测试方法在为测试而管理的事务中运行时删除了选定的表的内容,则默认情况下事务会回滚,并且数据库会返回到执行测试之前的状态。通过使用在测试的应用程序上下文中定义的`PlatformTransactionManager` Bean 向测试提供事务支持。

如果你想要提交一个事务(这是不寻常的,但在你想要填充或修改数据库的特定测试时偶尔会有用),那么你可以通过使用[`@Commit`](#Integration-Testing-Annotations)注释,告诉 TestContext 框架使事务提交,而不是回滚。

参见事务管理[TestContext 框架](#testcontext-tx)

#### 3.2.4.集成测试的支持类

Spring TestContext 框架提供了几个`abstract`支持类,这些类简化了集成测试的编写。这些基本测试类为测试框架提供了定义良好的挂钩,以及方便的实例变量和方法,使你能够访问:

* `ApplicationContext`,用于执行显式 Bean 查找或测试整个上下文的状态。

* a`JdbcTemplate`,用于执行查询数据库的 SQL 语句。可以使用这样的查询来确认与数据库相关的应用程序代码执行之前和之后的数据库状态,并且 Spring 确保这样的查询在与应用程序代码相同的事务范围内运行。当与 ORM 工具一起使用时,请务必避免[误报](#testcontext-tx-false-positives)

此外,你可能希望创建你自己的定制的、应用程序范围的超类,其中包含特定于你的项目的实例变量和方法。

参见[TestContext 框架](#testcontext-support-classes)的支持类。

### 3.3.JDBC 测试支持

`org.springframework.test.jdbc`包包含`JdbcTestUtils`,这是与 JDBC 相关的实用程序函数的集合,旨在简化标准数据库测试场景。具体地说,`JdbcTestUtils`提供了以下静态实用方法。

* `countRowsInTable(..)`:计算给定表中的行数。

* `countRowsInTableWhere(..)`:使用提供的`WHERE`子句计算给定表中的行数。

* `deleteFromTables(..)`:从指定的表中删除所有行。

* `deleteFromTableWhere(..)`:使用提供的`WHERE`子句从给定表中删除行。

* `dropTables(..)`:删除指定的表。

|   |[`AbstractTransactionalJUnit4SpringContextTests`](#testcontext-support-classes-junit4)和[`AbstractTransactionalTestNGSpringContextTests`](#testcontext-support-classes-testng)提供了方便的方法,这些方法在`JdbcTestUtils`中委托给上述方法。<br/>`spring-jdbc`模块提供了对配置和启动嵌入式<br/>数据库的支持,你可以在与数据库交互的集成测试中使用该数据库。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 3.4.注解

本节介绍了在测试 Spring 应用程序时可以使用的注释。它包括以下主题:

* [Spring Testing Annotations](#integration-testing-annotations-spring)

* [标准注释支持](#integration-testing-annotations-standard)

* [Spring JUnit 4 Testing Annotations](#integration-testing-annotations-junit4)

* [Spring JUnit Jupiter Testing Annotations](#integration-testing-annotations-junit-jupiter)

* [测试的元注释支持](#integration-testing-annotations-meta)

#### 3.4.1. Spring 测试注释

Spring 框架提供了以下一组 Spring 特定的注释,你可以在与 TestContext 框架结合的单元和集成测试中使用这些注释。有关更多信息,请参见相应的 爪哇doc,包括默认属性值、属性别名和其他详细信息。

Spring 的测试注释包括以下内容:

* [`@BootstrapWith`](# Spring-testing-annotation-bootstrapwith)

* [`@ContextConfiguration`](# Spring-testing-annotation-contextconfiguration)

* [`@WebAppConfiguration`](# Spring-testing-annotation-webappconfiguration)

* [`@ContextHierarchy`](# Spring-testing-annotation-contexthierarchy)

* [`@ActiveProfiles`](# Spring-testing-annotation-ActiveProfiles)

* [`@TestPropertySource`](# Spring-testing-annotation-testPropertySource)

* [`@DynamicPropertySource`](# Spring-testing-annotation-dynamicpropertysource)

* [`@DirtiesContext`](# Spring-testing-annotation-dirtiescontext)

* [`@TestExecutionListeners`](# Spring-testing-annotation-testexecutionlisteners)

* [`@RecordApplicationEvents`](# Spring-testing-annotation-recordapplicationEvents)

* [`@Commit`](# Spring-testing-annotation-commit)

* [`@Rollback`](# Spring-testing-annotation-rollback)

* [`@BeforeTransaction`](# Spring-testing-annotation-beforeTransaction)

* [`@AfterTransaction`](# Spring-testing-annotation-aftertransaction)

* [`@Sql`](# Spring-testing-annotation-sql)

* [`@SqlConfig`](# Spring-testing-annotation-sqlconfig)

* [`@SqlMergeMode`](# Spring-testing-annotation-sqlmergemode)

* [`@SqlGroup`](# Spring-testing-annotation-sqlgroup)

##### `@BootstrapWith`

`@BootstrapWith`是一个类级注释,你可以使用它来配置如何引导 Spring TestContext 框架。具体地说,你可以使用`@BootstrapWith`来指定自定义的`TestContextBootstrapper`。有关更多详细信息,请参见[引导 TestContext 框架](#testcontext-bootstrapping)一节。

##### `@ContextConfiguration`

`@ContextConfiguration`定义了类级元数据,用于确定如何加载和配置用于集成测试的`ApplicationContext`。具体地说,`@ContextConfiguration`声明用于加载上下文的应用程序上下文资源`locations`或组件`classes`

资源位置通常是位于 Classpath 中的 XML 配置文件或 Groovy 脚本,而组件类通常是`@Configuration`类。然而,资源位置也可以引用文件系统中的文件和脚本,并且组件类可以是`@Component`类、`@Service`类等等。有关更多详细信息,请参见[组件类](#testcontext-ctx-management-javaconfig-component-classes)

下面的示例显示了引用 XML 文件的`@ContextConfiguration`注释:

爪哇

```
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
    // class body...
}
```

|**1**|指一个 XML 文件。|
|-----|-------------------------|

Kotlin

```
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
    // class body...
}
```

|**1**|指一个 XML 文件。|
|-----|-------------------------|

下面的示例显示了引用类的`@ContextConfiguration`注释:

爪哇

```
@ContextConfiguration(classes = TestConfig.class) (1)
class ConfigClassApplicationContextTests {
    // class body...
}
```

|**1**|指的是一门课。|
|-----|---------------------|

Kotlin

```
@ContextConfiguration(classes = [TestConfig::class]) (1)
class ConfigClassApplicationContextTests {
    // class body...
}
```

|**1**|指的是一门课。|
|-----|---------------------|

作为一种替代方法,或者除了声明资源位置或组件类之外,还可以使用`@ContextConfiguration`声明`ApplicationContextInitializer`类。下面的示例展示了这样一个案例:

爪哇

```
@ContextConfiguration(initializers = CustomContextIntializer.class) (1)
class ContextInitializerTests {
    // class body...
}
```

|**1**|声明初始化程序类。|
|-----|-------------------------------|

Kotlin

```
@ContextConfiguration(initializers = [CustomContextIntializer::class]) (1)
class ContextInitializerTests {
    // class body...
}
```

|**1**|声明初始化程序类。|
|-----|-------------------------------|

你也可以选择使用`@ContextConfiguration`来声明`ContextLoader`策略。但是,请注意,你通常不需要显式地配置加载器,因为默认加载器支持`initializers`和资源`locations`或组件`classes`

下面的示例既使用了位置,也使用了加载器:

爪哇

```
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) (1)
class CustomLoaderXmlApplicationContextTests {
    // class body...
}
```

|**1**|配置位置和自定义加载器。|
|-----|------------------------------------------------|

Kotlin

```
@ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) (1)
class CustomLoaderXmlApplicationContextTests {
    // class body...
}
```

|**1**|配置位置和自定义加载器。|
|-----|------------------------------------------------|

|   |`@ContextConfiguration`提供了对继承资源位置或<br/>配置类的支持,以及由超类<br/>声明的上下文初始化器或包含类。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

请参阅[上下文管理](#testcontext-ctx-management),[`@Nested`测试类配置](#testcontext-junit-jupiter-nested-test-configuration),以及`@ContextConfiguration`爪哇docs 以获取更多详细信息。

##### `@WebAppConfiguration`

`@WebAppConfiguration`是一个类级注释,你可以使用它来声明为集成测试加载的`ApplicationContext`应该是`WebApplicationContext`。在测试类上仅存在`@WebAppConfiguration`就可以确保为测试加载`WebApplicationContext`,使用缺省值`"file:src/main/webapp"`作为 Web 应用程序的根路径(即资源基路径)。后台使用资源库路径创建`MockServletContext`,它是测试的`ServletContext``ServletContext`

下面的示例展示了如何使用`@WebAppConfiguration`注释:

爪哇

```
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
    // class body...
}
```

Kotlin

```
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
    // class body...
}
```

|**1**|`@WebAppConfiguration`注释。|
|-----|--------------------------------------|

要覆盖默认值,可以使用隐式`value`属性指定不同的基本资源路径。同时支持`classpath:``file:`资源前缀。如果没有提供资源前缀,则假定路径是一个文件系统资源。下面的示例展示了如何指定 Classpath 资源:

爪哇

```
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
    // class body...
}
```

|**1**|指定 Classpath 资源。|
|-----|--------------------------------|

Kotlin

```
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
    // class body...
}
```

|**1**|指定 Classpath 资源。|
|-----|--------------------------------|

请注意,`@WebAppConfiguration`必须与`@ContextConfiguration`一起使用,无论是在单个测试类中还是在测试类层次结构中。有关更多详细信息,请参见[`@WebAppConfiguration`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/web/webappconfiguration.html)爪哇doc。

##### `@ContextHierarchy`

`@ContextHierarchy`是一种类级注释,用于为集成测试定义`ApplicationContext`实例的层次结构。`@ContextHierarchy`应该使用一个或多个`@ContextConfiguration`实例的列表来声明,每个实例在上下文层次结构中定义一个级别。下面的示例演示了在单个测试类中使用`@ContextHierarchy``@ContextHierarchy`也可以在测试类层次结构中使用):

爪哇

```
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
    // class body...
}
```

Kotlin

```
@ContextHierarchy(
    ContextConfiguration("/parent-config.xml"),
    ContextConfiguration("/child-config.xml"))
class ContextHierarchyTests {
    // class body...
}
```

爪哇

```
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = AppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
    // class body...
}
```

Kotlin

```
@WebAppConfiguration
@ContextHierarchy(
        ContextConfiguration(classes = [AppConfig::class]),
        ContextConfiguration(classes = [WebConfig::class]))
class WebIntegrationTests {
    // class body...
}
```

如果需要合并或覆盖测试类层次结构中上下文层次结构的给定级别的配置,则必须在类层次结构中的每个对应级别上,通过向`@ContextConfiguration`中的`name`属性提供相同的值,显式地命名该级别。参见[上下文层次结构](#testcontext-ctx-management-ctx-hierarchies)和[`@ContextHierarchy`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/contexthierarchy.html)爪哇doc 以获取更多示例。

##### `@ActiveProfiles`

`@ActiveProfiles`是一种类级注释,用于声明在为集成测试加载`ApplicationContext`时哪些 Bean 定义配置文件应该处于活动状态。

以下示例表明`dev`配置文件应该处于活动状态:

爪哇

```
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
    // class body...
}
```

|**1**|指示`dev`配置文件应该处于活动状态。|
|-----|-------------------------------------------------|

Kotlin

```
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
    // class body...
}
```

|**1**|表示`dev`配置文件应该处于活动状态。|
|-----|-------------------------------------------------|

下面的示例表明,`dev``integration`配置文件都应该处于活动状态:

爪哇

```
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
class DeveloperIntegrationTests {
    // class body...
}
```

|**1**|指示`dev``integration`配置文件应该处于活动状态。|
|-----|--------------------------------------------------------------------|

Kotlin

```
@ContextConfiguration
@ActiveProfiles(["dev", "integration"]) (1)
class DeveloperIntegrationTests {
    // class body...
}
```

|**1**|指示`dev``integration`配置文件应该处于活动状态。|
|-----|--------------------------------------------------------------------|

|   |`@ActiveProfiles`提供了对继承活动 Bean 定义配置文件<br/>的支持,该配置文件由超类声明并默认包含类。还可以通过实现自定义[`ActiveProfilesResolver`](#TestContext-CTX-Management-ENV-Profiles-ActiveProfilesResolver)并使用`resolver``@ActiveProfiles`的属性来以编程方式解析活动的<br/> Bean 定义配置文件。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

参见[具有环境配置文件的上下文配置](#testcontext-ctx-management-env-profiles),[`@Nested`测试类配置](#TestContext-JUnit-Jupiter-Nested-Test-Configuration),以及[`@ActiveProfiles`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/org/springframework/test/context/actevepries.html)的示例和进一步详细信息。

##### `@TestPropertySource`

`@TestPropertySource`是一种类级注释,你可以使用它来配置要添加到`PropertySources`中的`Environment`集合中的属性文件和内联属性的位置,用于为集成测试加载`ApplicationContext`

下面的示例演示了如何从 Classpath 声明属性文件:

爪哇

```
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|从 Classpath 根中的`test.properties`获取属性。|
|-----|-------------------------------------------------------------------|

Kotlin

```
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|从 Classpath 根中的`test.properties`获取属性。|
|-----|-------------------------------------------------------------------|

下面的示例演示了如何声明内联属性:

爪哇

```
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|声明`timezone``port`属性。|
|-----|-----------------------------------------|

Kotlin

```
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|声明`timezone``port`属性。|
|-----|-----------------------------------------|

有关示例和更多详细信息,请参见[具有测试属性源的上下文配置](#testcontext-ctx-management-property-sources)

##### `@DynamicPropertySource`

`@DynamicPropertySource`是一种方法级别的注释,你可以使用它来注册要添加到`PropertySources`中的`ApplicationContext`集合中的 *dynamic*properties,用于为集成测试加载`ApplicationContext`。当你不知道属性的初始值时,动态属性是有用的——例如,如果属性是由外部资源管理的,比如由[测试容器](https://www.testcontainers.org/)项目管理的容器。

下面的示例演示了如何注册动态属性:

爪哇

```
@ContextConfiguration
class MyIntegrationTests {

    static MyExternalServer server = // ...

    @DynamicPropertySource (1)
    static void dynamicProperties(DynamicPropertyRegistry registry) { (2)
        registry.add("server.port", server::getPort); (3)
    }

    // tests ...
}
```

|**1**|用`@DynamicPropertySource`注释`static`方法。|
|-----|---------------------------------------------------------------------------------|
|**2**|接受`DynamicPropertyRegistry`作为参数。|
|**3**|注册一个动态`server.port`属性,以便从服务器上懒洋洋地检索。|

Kotlin

```
@ContextConfiguration
class MyIntegrationTests {

    companion object {

        @JvmStatic
        val server: MyExternalServer = // ...

        @DynamicPropertySource (1)
        @JvmStatic
        fun dynamicProperties(registry: DynamicPropertyRegistry) { (2)
            registry.add("server.port", server::getPort) (3)
        }
    }

    // tests ...
}
```

|**1**|用`@DynamicPropertySource`注释`static`方法。|
|-----|---------------------------------------------------------------------------------|
|**2**|接受`DynamicPropertyRegistry`作为参数。|
|**3**|注册一个动态`server.port`属性,以便从服务器上懒洋洋地检索。|

有关更多详细信息,请参见[具有动态属性源的上下文配置](#testcontext-ctx-management-dynamic-property-sources)

##### `@DirtiesContext`

`@DirtiesContext`表示底层 Spring `ApplicationContext`在测试的执行过程中被弄脏了(也就是说,测试以某种方式修改或损坏了它——例如,通过更改单例 Bean 的状态),并且应该关闭。当一个应用程序上下文被标记为 dirty 时,它将从测试框架的缓存中删除并关闭。因此,对于需要具有相同配置元数据的上下文的任何后续测试,都会重新构建基础 Spring 容器。

可以在同一个类或类层次结构中同时使用`@DirtiesContext`作为类级和方法级的注释。在这种情况下,根据配置的`methodMode``classMode`,在任何此类注释方法之前或之后以及在当前测试类之前或之后将`ApplicationContext`标记为 dirty。

下面的示例解释了各种配置场景的上下文何时会被弄脏:

* 在当前测试类之前,当在类上声明时,将类的模式设置为`BEFORE_CLASS`

  爪哇

  ```
  @DirtiesContext(classMode = BEFORE_CLASS) (1)
  class FreshContextTests {
      // some tests that require a new Spring container
  }
  ```

  |**1**|在当前测试类之前弄脏上下文。|
  |-----|------------------------------------------------|

  Kotlin

  ```
  @DirtiesContext(classMode = BEFORE_CLASS) (1)
  class FreshContextTests {
      // some tests that require a new Spring container
  }
  ```

  |**1**|在当前测试类之前弄脏上下文。|
  |-----|------------------------------------------------|

* 在当前的测试类之后,当在一个类上声明具有设置为`AFTER_CLASS`的类模式时(即默认的类模式)。

  爪哇

  ```
  @DirtiesContext (1)
  class ContextDirtyingTests {
      // some tests that result in the Spring container being dirtied
  }
  ```

  |**1**|在当前测试类之后,将上下文弄脏。|
  |-----|-----------------------------------------------|

  Kotlin

  ```
  @DirtiesContext (1)
  class ContextDirtyingTests {
      // some tests that result in the Spring container being dirtied
  }
  ```

  |**1**|在当前测试类之后,将上下文弄脏。|
  |-----|-----------------------------------------------|

* 在当前测试类中的每个测试方法之前,当在类上声明时,将类模式设置为`BEFORE_EACH_TEST_METHOD.`

  爪哇

  ```
  @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
  class FreshContextTests {
      // some tests that require a new Spring container
  }
  ```

  |**1**|在每个测试方法之前弄脏上下文。|
  |-----|------------------------------------------|

  Kotlin

  ```
  @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1)
  class FreshContextTests {
      // some tests that require a new Spring container
  }
  ```

  |**1**|在每个测试方法之前弄脏上下文。|
  |-----|------------------------------------------|

* 在当前测试类中的每个测试方法之后,当在类上声明时,将类模式设置为`AFTER_EACH_TEST_METHOD.`

  爪哇

  ```
  @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
  class ContextDirtyingTests {
      // some tests that result in the Spring container being dirtied
  }
  ```

  |**1**|在每种测试方法之后都要弄脏上下文。|
  |-----|-----------------------------------------|

  Kotlin

  ```
  @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1)
  class ContextDirtyingTests {
      // some tests that result in the Spring container being dirtied
  }
  ```

  |**1**|在每种测试方法之后都要弄脏上下文。|
  |-----|-----------------------------------------|

* 在当前测试之前,当对方法进行声明时,方法模式设置为`BEFORE_METHOD`

  爪哇

  ```
  @DirtiesContext(methodMode = BEFORE_METHOD) (1)
  @Test
  void testProcessWhichRequiresFreshAppCtx() {
      // some logic that requires a new Spring container
  }
  ```

  |**1**|在当前测试方法之前弄脏上下文。|
  |-----|-------------------------------------------------|

  Kotlin

  ```
  @DirtiesContext(methodMode = BEFORE_METHOD) (1)
  @Test
  fun testProcessWhichRequiresFreshAppCtx() {
      // some logic that requires a new Spring container
  }
  ```

  |**1**|在当前测试方法之前弄脏上下文。|
  |-----|-------------------------------------------------|

* 在当前测试之后,当对方法进行声明时,方法模式设置为`AFTER_METHOD`(即默认的方法模式)。

  爪哇

  ```
  @DirtiesContext (1)
  @Test
  void testProcessWhichDirtiesAppCtx() {
      // some logic that results in the Spring container being dirtied
  }
  ```

  |**1**|在当前测试方法之后,将上下文弄脏。|
  |-----|------------------------------------------------|

  Kotlin

  ```
  @DirtiesContext (1)
  @Test
  fun testProcessWhichDirtiesAppCtx() {
      // some logic that results in the Spring container being dirtied
  }
  ```

  |**1**|在当前测试方法之后,将上下文弄脏。|
  |-----|------------------------------------------------|

如果你在一个测试中使用`@DirtiesContext`,该测试的上下文被配置为带有`@ContextHierarchy`的上下文层次结构的一部分,那么你可以使用`hierarchyMode`标志来控制如何清除上下文缓存。默认情况下,使用穷举算法来清除上下文缓存,不仅包括当前级别,还包括共享当前测试共有的祖先上下文的所有其他上下文层次结构。所有位于公共祖先上下文的子层次结构中的`ApplicationContext`实例都将从上下文缓存中删除并关闭。如果穷举算法对特定的用例来说过于强大,那么你可以指定更简单的当前级别算法,如下例所示。

爪哇

```
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class BaseTests {
    // class body...
}

class ExtendedTests extends BaseTests {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
    void test() {
        // some logic that results in the child context being dirtied
    }
}
```

|**1**|使用当前级别的算法。|
|-----|--------------------------------|

Kotlin

```
@ContextHierarchy(
    ContextConfiguration("/parent-config.xml"),
    ContextConfiguration("/child-config.xml"))
open class BaseTests {
    // class body...
}

class ExtendedTests : BaseTests() {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
    fun test() {
        // some logic that results in the child context being dirtied
    }
}
```

|**1**|使用当前级别的算法。|
|-----|--------------------------------|

有关`EXHAUSTIVE``CURRENT_LEVEL`算法的更多详细信息,请参见[`DirtiesContext.HierarchyMode`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/annotation/dirtiescontext.therymode.html)爪哇doc。

##### `@TestExecutionListeners`

`@TestExecutionListeners`定义了用于配置应该在`TestContextManager`中注册的`TestExecutionListener`实现的类级元数据。通常,`@TestExecutionListeners``@ContextConfiguration`一起使用。

下面的示例显示了如何注册两个`TestExecutionListener`实现:

爪哇

```
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
```

|**1**|注册两个`TestExecutionListener`实现。|
|-----|-----------------------------------------------------|

Kotlin

```
@ContextConfiguration
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) (1)
class CustomTestExecutionListenerTests {
    // class body...
}
```

|**1**|注册两个`TestExecutionListener`实现。|
|-----|-----------------------------------------------------|

默认情况下,`@TestExecutionListeners`支持从超类或封闭类继承侦听器。参见[`@Nested`测试类配置](#TestContext-JUnit-Jupiter-Nested-Test-Configuration)和[`@TestExecutionListeners`爪哇doc](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/javadoc-api/org/SpringFramework/test/contexecutionListeners.html)中的示例和更多详细信息。

##### `@RecordApplicationEvents`

`@RecordApplicationEvents`是一种类级注释,用于指示 * Spring TestContext Framework* 记录在执行单个测试期间在`ApplicationContext`中发布的所有应用程序事件。

可以通过测试中的`ApplicationEvents`API 访问记录的事件。

参见[应用程序事件](#testcontext-application-events)和[`@RecordApplicationEvents`爪哇doc](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/event/recordapplicationevents.html)以获取示例和更多详细信息。

##### `@Commit`

`@Commit`表示事务性测试方法的事务应在测试方法完成后提交。你可以使用`@Commit`作为`@Rollback(false)`的直接替换,以更明确地传达代码的意图。类似于`@Rollback``@Commit`也可以声明为类级或方法级注释。

下面的示例展示了如何使用`@Commit`注释:

爪哇

```
@Commit (1)
@Test
void testProcessWithoutRollback() {
    // ...
}
```

|**1**|将测试结果提交给数据库。|
|-----|----------------------------------------------|

Kotlin

```
@Commit (1)
@Test
fun testProcessWithoutRollback() {
    // ...
}
```

|**1**|将测试结果提交给数据库。|
|-----|----------------------------------------------|

##### `@Rollback`

`@Rollback`指示在测试方法完成后是否应该回滚事务测试方法的事务。如果`true`,则回滚事务。否则,事务将被提交(参见[`@Commit`](# Spring-testing-annotation-commit))。 Spring TestContext 框架中集成测试的回滚默认为`true`,即使没有显式声明`@Rollback`

当声明为类级注释时,`@Rollback`为测试类层次结构中的所有测试方法定义了默认的回滚语义。当声明为方法级别的注释时,`@Rollback`为特定的测试方法定义了回滚语义,可能会覆盖类级别的`@Rollback``@Commit`语义。

下面的示例导致测试方法的结果不会被回滚(即,结果被提交到数据库中):

爪哇

```
@Rollback(false) (1)
@Test
void testProcessWithoutRollback() {
    // ...
}
```

|**1**|不要回滚结果。|
|-----|----------------------------|

Kotlin

```
@Rollback(false) (1)
@Test
fun testProcessWithoutRollback() {
    // ...
}
```

|**1**|不要回滚结果。|
|-----|----------------------------|

##### `@BeforeTransaction`

`@BeforeTransaction`表示对于已通过使用 Spring 的`@Transactional`注释配置为在事务中运行的测试方法,在事务启动之前应该运行带注释的`void`方法。`@BeforeTransaction`方法不需要是`public`,并且可以在基于 爪哇8 的接口默认方法上声明。

下面的示例展示了如何使用`@BeforeTransaction`注释:

爪哇

```
@BeforeTransaction (1)
void beforeTransaction() {
    // logic to be run before a transaction is started
}
```

|**1**|在事务之前运行此方法。|
|-----|-------------------------------------|

Kotlin

```
@BeforeTransaction (1)
fun beforeTransaction() {
    // logic to be run before a transaction is started
}
```

|**1**|在事务之前运行此方法。|
|-----|-------------------------------------|

##### `@AfterTransaction`

`@AfterTransaction`表示在事务结束后应该运行带注释的`void`方法,用于通过使用 Spring 的`@Transactional`注释配置为在事务中运行的测试方法。`@AfterTransaction`方法不需要是`public`,并且可以在基于 爪哇8 的接口默认方法上声明。

爪哇

```
@AfterTransaction (1)
void afterTransaction() {
    // logic to be run after a transaction has ended
}
```

|**1**|在事务之后运行此方法。|
|-----|------------------------------------|

Kotlin

```
@AfterTransaction (1)
fun afterTransaction() {
    // logic to be run after a transaction has ended
}
```

|**1**|在事务之后运行此方法。|
|-----|------------------------------------|

##### `@Sql`

`@Sql`用于对测试类或测试方法进行注释,以配置在集成测试期间针对给定数据库运行的 SQL 脚本。下面的示例展示了如何使用它:

爪哇

```
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) (1)
void userTest() {
    // run code that relies on the test schema and test data
}
```

|**1**|为此测试运行两个脚本。|
|-----|------------------------------|

Kotlin

```
@Test
@Sql("/test-schema.sql", "/test-user-data.sql") (1)
fun userTest() {
    // run code that relies on the test schema and test data
}
```

|**1**|为此测试运行两个脚本。|
|-----|------------------------------|

有关更多详细信息,请参见[使用 @sql 声明式执行 SQL 脚本](#testcontext-executing-sql-declaratively)

##### `@SqlConfig`

`@SqlConfig`定义了元数据,用于确定如何解析和运行配置有`@Sql`注释的 SQL 脚本。下面的示例展示了如何使用它:

爪哇

```
@Test
@Sql(
    scripts = "/test-user-data.sql",
    config = @SqlConfig(commentPrefix = "`", separator = "@@") (1)
)
void userTest() {
    // run code that relies on the test data
}
```

|**1**|在 SQL 脚本中设置注释前缀和分隔符。|
|-----|--------------------------------------------------------|

Kotlin

```
@Test
@Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) (1)
fun userTest() {
    // run code that relies on the test data
}
```

|**1**|在 SQL 脚本中设置注释前缀和分隔符。|
|-----|--------------------------------------------------------|

##### `@SqlMergeMode`

`@SqlMergeMode`用于对测试类或测试方法进行注释,以配置方法级`@Sql`声明是否与类级`@Sql`声明合并。如果`@SqlMergeMode`未在测试类或测试方法上声明,则默认情况下将使用`OVERRIDE`合并模式。在`OVERRIDE`模式下,方法级别`@Sql`声明将有效地覆盖类级别`@Sql`声明。

请注意,方法级别`@SqlMergeMode`声明覆盖了类级别声明。

下面的示例展示了如何在类级别上使用`@SqlMergeMode`

爪哇

```
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    void standardUserProfile() {
        // run code that relies on test data set 001
    }
}
```

|**1**|对于类中的所有测试方法,将`@Sql`合并模式设置为`MERGE`。|
|-----|-----------------------------------------------------------------------|

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    fun standardUserProfile() {
        // run code that relies on test data set 001
    }
}
```

|**1**|对于类中的所有测试方法,将`@Sql`合并模式设置为`MERGE`。|
|-----|-----------------------------------------------------------------------|

下面的示例展示了如何在方法级别上使用`@SqlMergeMode`

Java

```
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    @SqlMergeMode(MERGE) (1)
    void standardUserProfile() {
        // run code that relies on test data set 001
    }
}
```

|**1**|为特定的测试方法将`@Sql`合并模式设置为`MERGE`。|
|-----|----------------------------------------------------------------|

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    @SqlMergeMode(MERGE) (1)
    fun standardUserProfile() {
        // run code that relies on test data set 001
    }
}
```

|**1**|为特定的测试方法将`@Sql`合并模式设置为`MERGE`。|
|-----|----------------------------------------------------------------|

##### `@SqlGroup`

`@SqlGroup`是一个容器注释,它聚合了几个`@Sql`注释。你可以本地使用`@SqlGroup`来声明几个嵌套的`@Sql`注释,或者可以将其与 Java8 对可重复注释的支持结合使用,其中`@Sql`可以在同一个类或方法上声明几次,隐式地生成这个容器注释。下面的示例展示了如何声明 SQL 组:

Java

```
@Test
@SqlGroup({ (1)
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // run code that uses the test schema and test data
}
```

|**1**|声明一组 SQL 脚本。|
|-----|-------------------------------|

Kotlin

```
@Test
@SqlGroup( (1)
    Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
    Sql("/test-user-data.sql"))
fun userTest() {
    // run code that uses the test schema and test data
}
```

|**1**|声明一组 SQL 脚本。|
|-----|-------------------------------|

#### 3.4.2.标准注释支持

Spring TestContext 框架的所有配置都使用标准语义支持以下注释。请注意,这些注释不是特定于测试的,并且可以在 Spring 框架中的任何地方使用。

* `@Autowired`

* `@Qualifier`

* `@Value`

* `@Resource`(javax.annotation)如果存在 JSR-250

* `@ManagedBean`(javax.annotation)如果存在 JSR-250

* `@Inject`如果存在 JSR-330

* `@Named`如果存在 JSR-330

* `@PersistenceContext`(javax.persistence)如果存在 JPA

* `@PersistenceUnit`(javax.persistence)如果存在 JPA

* `@Required`

* `@Transactional`*with[有限的属性支持](#testcontext-tx-attribute-support)*

|   |JSR-250 生命周期注释<br/><br/>在 Spring TestContext 框架中,可以在`@PostConstruct``@PreDestroy`中配置的任何应用程序组件上使用<br/>标准语义。<br/>但是,在实际的测试类中,这些生命周期注释的使用是有限的。<br/><br/>如果测试类中的方法使用`@PostConstruct`进行注释,则该方法在底层测试框架的任何方法之前运行<br/>(例如,方法<br/>使用 JUnit Jupiter 的`@BeforeEach`进行注释),这适用于<br/>测试类中的每个测试方法。另一方面,如果测试类中的方法被注释为`@PreDestroy`,则该方法永远不会运行。因此,在测试类中,我们建议<br/>使用来自底层测试框架的测试生命周期回调,而不是`@PostConstruct``@PreDestroy`。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 3.4.3. Spring JUnit4 测试注释

以下注释仅在与[Springrunner](#testcontext-junit4-runner)[Spring’s JUnit 4 rules](#testcontext-junit4-rules)[Spring’s JUnit 4 support classes](#testcontext-support-classes-junit4)结合使用时才受支持:

* [`@IfProfileValue`](#Integration-Testing-Annotations-JUnit4-IFProfileValue)

* [`@ProfileValueSourceConfiguration`](#Integration-Testing-Annotations-JUnit4-ProfilEvaluesourceConfiguration)

* [`@Timed`](#Integration-Testing-Annotations-JUnit4-Timed)

* [`@Repeat`](#Integration-Testing-Annotations-JUnit4-Repeat)

##### `@IfProfileValue`

`@IfProfileValue`表示针对特定的测试环境启用了带注释的测试。如果配置的`ProfileValueSource`返回所提供的`value`的匹配`value`,则启用测试。否则,该测试将被禁用,并实际上被忽略。

你可以在类级别、方法级别或两者都应用`@IfProfileValue`。对于该类或其子类中的任何方法,`@IfProfileValue`的类级用法优先于方法级用法。具体地说,如果在类级别和方法级别都启用了测试,则将启用该测试。缺少`@IfProfileValue`意味着隐式启用了测试。这类似于 JUnit4 的`@Ignore`注释的语义,只是`@Ignore`的存在总是禁用测试。

下面的示例显示了一个带有`@IfProfileValue`注释的测试:

Java

```
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}
```

|**1**|仅当 Java 供应商“甲骨文股份有限公司”时才运行此测试。|
|-----|----------------------------------------------------------------|

Kotlin

```
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
fun testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}
```

|**1**|仅当 Java 供应商“甲骨文股份有限公司”时才运行此测试。|
|-----|----------------------------------------------------------------|

或者,你可以使用`@IfProfileValue`的列表配置`values`(带有`OR`语义),以在 JUnit4 环境中实现对测试组的类似 TestNG 的支持。考虑以下示例:

Java

```
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) (1)
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}
```

|**1**|为单元测试和集成测试运行此测试。|
|-----|---------------------------------------------------|

Kotlin

```
@IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) (1)
@Test
fun testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}
```

|**1**|为单元测试和集成测试运行此测试。|
|-----|---------------------------------------------------|

##### `@ProfileValueSourceConfiguration`

`@ProfileValueSourceConfiguration`是一个类级注释,它指定在检索通过`@IfProfileValue`注释配置的配置文件值时要使用的`ProfileValueSource`类型。如果`@ProfileValueSourceConfiguration`未声明用于测试,则默认情况下使用`SystemProfileValueSource`。下面的示例展示了如何使用`@ProfileValueSourceConfiguration`:

Java

```
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) (1)
public class CustomProfileValueSourceTests {
    // class body...
}
```

|**1**|使用自定义配置文件的值源。|
|-----|----------------------------------|

Kotlin

```
@ProfileValueSourceConfiguration(CustomProfileValueSource::class) (1)
class CustomProfileValueSourceTests {
    // class body...
}
```

|**1**|使用自定义配置文件的值源。|
|-----|----------------------------------|

##### `@Timed`

`@Timed`表示带注释的测试方法必须在指定的时间内(以毫秒为单位)完成执行。如果文本执行时间超过了指定的时间段,则测试失败。

该时间段包括测试方法本身的运行、测试的任何重复(参见`@Repeat`),以及测试夹具的任何设置或拆除。下面的示例展示了如何使用它:

Java

```
@Timed(millis = 1000) (1)
public void testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to run
}
```

|**1**|将测试的时间段设置为一秒。|
|-----|-----------------------------------------------|

Kotlin

```
@Timed(millis = 1000) (1)
fun testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to run
}
```

|**1**|将测试的时间段设置为一秒。|
|-----|-----------------------------------------------|

Spring 的`@Timed`注释与 JUnit4 的`@Test(timeout=…​)`支持具有不同的语义。具体地说,由于 JUnit4 处理测试执行超时的方式(即通过在单独的`Thread`中执行测试方法),如果测试时间过长,`@Test(timeout=…​)`会抢先失败测试。 Spring 的`@Timed`,在另一方面,不会先发制人地使测试失败,而是等待测试完成后再失败。

##### `@Repeat`

`@Repeat`表示带注释的测试方法必须重复运行。在注释中指定了要运行测试方法的次数。

要重复的执行范围包括测试方法本身的执行以及测试夹具的任何设置或拆除。当与[`SpringMethodRule`](#TestContext-JUnit4-Rules)一起使用时,范围还包括通过`TestExecutionListener`实现准备测试实例。下面的示例展示了如何使用`@Repeat`注释:

Java

```
@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
    // ...
}
```

|**1**|把这个测验重复十次。|
|-----|---------------------------|

Kotlin

```
@Repeat(10) (1)
@Test
fun testProcessRepeatedly() {
    // ...
}
```

|**1**|把这个测验重复十次。|
|-----|---------------------------|

#### 3.4.4. Spring Junit Jupiter 测试注释

当与[`SpringExtension`](#TestContext-JUnit-Jupiter-Extension)和 JUnit Jupiter(即 JUnit5 中的编程模型)结合使用时,支持以下注释:

* [`@SpringJUnitConfig`](#Integration-Testing-Annotations-JUnit-Jupiter-SpringJunitConfig)

* [`@SpringJUnitWebConfig`](#Integration-Testing-Annotations-JUnit-Jupiter-SpringJunitWebConfig)

* [`@TestConstructor`](#Integration-Testing-Annotations-TestConstructor)

* [`@NestedTestConfiguration`](#Integration-Testing-Annotations-NestedTestConfiguration)

* [`@EnabledIf`](#Integration-Testing-Annotations-JUnit-Jupiter-EnableDIF)

* [`@DisabledIf`](#Integration-Testing-Annotations-JUnit-Jupiter-DisableDIF)

##### `@SpringJUnitConfig`

`@SpringJUnitConfig`是一个组合注释,它结合了来自 JUnit Jupiter 的`@ExtendWith(SpringExtension.class)`和来自 Spring TestContext 框架的`@ContextConfiguration`。它可以在类级别上用作`@ContextConfiguration`的插入替换。关于配置选项,`@ContextConfiguration``@SpringJUnitConfig`之间的唯一区别是,组件类可以在`@SpringJUnitConfig`中使用`value`属性声明。

下面的示例展示了如何使用`@SpringJUnitConfig`注释来指定一个配置类:

Java

```
@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}
```

|**1**|指定配置类。|
|-----|--------------------------------|

Kotlin

```
@SpringJUnitConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}
```

|**1**|指定配置类。|
|-----|--------------------------------|

下面的示例展示了如何使用`@SpringJUnitConfig`注释来指定配置文件的位置:

Java

```
@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
    // class body...
}
```

|**1**|指定配置文件的位置。|
|-----|---------------------------------------------|

Kotlin

```
@SpringJUnitConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringTests {
    // class body...
}
```

|**1**|指定配置文件的位置。|
|-----|---------------------------------------------|

参见[上下文管理](#testcontext-ctx-management)以及[`@SpringJUnitConfig`]的 Javadoc(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/junit/junitconfig.html)和`@ContextConfiguration`以获取更多详细信息。

##### `@SpringJUnitWebConfig`

`@SpringJUnitWebConfig`是一个组合注释,它结合了来自 JUnit Jupiter 的`@ExtendWith(SpringExtension.class)`和来自 Spring TestContext 框架的`@WebAppConfiguration`。你可以在类级别上使用它作为`@ContextConfiguration``@WebAppConfiguration`的插入替换。关于配置选项,`@ContextConfiguration``@SpringJUnitWebConfig`之间的唯一区别是,你可以使用`value`中的`value`属性来声明组件类。此外,只需在`@SpringJUnitWebConfig`中使用`resourcePath`属性,就可以覆盖`value`中的`value`属性。

下面的示例展示了如何使用`@SpringJUnitWebConfig`注释来指定一个配置类:

Java

```
@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}
```

|**1**|指定配置类。|
|-----|--------------------------------|

Kotlin

```
@SpringJUnitWebConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}
```

|**1**|指定配置类。|
|-----|--------------------------------|

下面的示例展示了如何使用`@SpringJUnitWebConfig`注释来指定配置文件的位置:

Java

```
@SpringJUnitWebConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringWebTests {
    // class body...
}
```

|**1**|指定配置文件的位置。|
|-----|---------------------------------------------|

Kotlin

```
@SpringJUnitWebConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringWebTests {
    // class body...
}
```

|**1**|指定配置文件的位置。|
|-----|---------------------------------------------|

参见[上下文管理](#testcontext-ctx-management)以及[`@SpringJUnitWebConfig`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/SpringFramework/test/context/junit/junitwebconfig.html)的 Javadoc,[<`@ContextConfiguration`](https:///DOCS. Spring.io/ Spring-framework/ Spring-framework/javadoc/javadoc/juntextframework/org/juntextframework/cr/

##### `@TestConstructor`

`@TestConstructor`是一种类型级别的注释,用于配置如何从测试的`ApplicationContext`中的组件自动连接测试类构造函数的参数。

如果`@TestConstructor`在测试类上不存在或元存在,则将使用默认的*测试构造函数 AutoWire 模式*。有关如何更改默认模式的详细信息,请参见下面的技巧。但是,请注意,构造函数上的`@Autowired`的本地声明优先于`@TestConstructor`和默认模式。

|   |更改默认的测试构造函数 AutoWire 模式<br/><br/>可以通过将`spring.test.constructor.autowire.mode`JVM 系统属性设置为`all`来更改默认的*测试构造函数 AutoWire 模式*。或者,<br/>默认模式也可以通过[`SpringProperties`](Appendix.html#Appendix- Spring-Properties)机制设置。<br/><br/>自 Spring Framework5.3 起,默认模式也可以配置为[JUnit 平台配置参数](https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params)<br/><br/>如果不设置`spring.test.constructor.autowire.mode`属性,则不会自动连接构造函数。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |在 Spring Framework5.2 中,`@TestConstructor`只支持与<br/>结合使用的`SpringExtension`。请注意,`SpringExtension`<br/>通常会自动为你注册–例如,当使用诸如`@SpringJUnitConfig``@SpringJUnitWebConfig`之类的注释或来自<br/> Spring 引导测试的各种与测试相关的注释时。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### `@NestedTestConfiguration`

`@NestedTestConfiguration`是一种类型级别的注释,用于配置 Spring 测试配置注释在为内部测试类封装的类层次结构中的处理方式。

如果`@NestedTestConfiguration`在测试类上不存在或不存在元存在,则在其超级类型层次结构中或在其封闭的类层次结构中,将使用默认的*封闭配置继承模式*。有关如何更改默认模式的详细信息,请参见下面的技巧。

|   |更改默认的封闭配置继承模式<br/><br/>默认的*封闭配置继承模式*`INHERIT`,但是可以通过将`@ContextConfiguration`JVM 系统属性设置为`OVERRIDE`来更改。或者,可以通过[`SpringProperties`](Appendix.html#Appendix- Spring-Properties)机制设置默认模式。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

[Spring TestContext Framework](#testcontext-framework)为下面的注释提供了`@NestedTestConfiguration`语义。

* [`@BootstrapWith`](# Spring-testing-annotation-bootstrapwith)

* [`@ContextConfiguration`](# Spring-testing-annotation-contextconfiguration)

* [`@WebAppConfiguration`](# Spring-testing-annotation-webappconfiguration)

* [`@ContextHierarchy`](# Spring-testing-annotation-contexthierarchy)

* [`@ActiveProfiles`](# Spring-测试-注释-ActiveProfiles)

* [`@TestPropertySource`](# Spring-testing-annotation-testPropertySource)

* [`@DynamicPropertySource`](# Spring-testing-annotation-dynamicpropertysource)

* [`@DirtiesContext`](# Spring-testing-annotation-dirtiescontext)

* [`@TestExecutionListeners`](# Spring-testing-annotation-testexecutionlisteners)

* [`@RecordApplicationEvents`](# Spring-testing-annotation-recordapplicationEvents)

* [`@Transactional`]

* [`@Commit`](# Spring-testing-annotation-commit)

* [`@Rollback`](# Spring-testing-annotation-rollback)

* [`@Sql`](# Spring-testing-annotation-sql)

* [`@SqlConfig`](# Spring-testing-annotation-sqlconfig)

* [`@SqlMergeMode`](# Spring-testing-annotation-sqlmergemode)

* [`@TestConstructor`](#Integration-Testing-Annotations-TestConstructor)

|   |在 JUnit Jupiter 中,使用`@NestedTestConfiguration`通常仅在<br/>`@Nested`测试类结合时才有意义;但是,可能存在支持 Spring 的其他测试<br/>框架和使用这种<br/>注释的嵌套测试类。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

参见[`@Nested`测试类配置](#testcontext-junit-jupiter-nested-test-configuration)以获得示例和更多详细信息。

##### `@EnabledIf`

`@EnabledIf`用于表示启用了注释的 JUnit Jupiter 测试类或测试方法,并且如果提供的`expression`计算为`true`,则应该运行该方法。具体地说,如果表达式的求值为`Boolean.TRUE``String`等于`true`(忽略情况),则启用测试。当应用于类级别时,该类中的所有测试方法在默认情况下也会自动启用。

表达式可以是以下任何一种:

* [Spring Expression Language](core.html#expressions)表达式。例如:`@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")`

* 在 Spring[`Environment`](core.html#beans-environment)中可用的属性的占位符。例如:`@EnabledIf("${smoke.tests.enabled}")`

* 文字文字。例如:`@EnabledIf("true")`

然而,请注意,不是动态解析属性占位符的结果的文本文字是零实用价值的,因为`@EnabledIf("false")`等价于`@Disabled``@EnabledIf("true")`在逻辑上是没有意义的。

你可以使用`@EnabledIf`作为元注释来创建自定义组合注释。例如,你可以创建一个自定义`@EnabledOnMac`注释,如下所示:

爪哇

```
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
```

Kotlin

```
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@EnabledIf(
        expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
        reason = "Enabled on Mac OS"
)
annotation class EnabledOnMac {}
```

##### `@DisabledIf`

`@DisabledIf`用于表示注释的 JUnit Jupiter 测试类或测试方法已禁用,并且如果提供的`expression`计算为`true`,则不应运行该测试方法。具体地说,如果表达式的求值为`Boolean.TRUE``String`等于`true`(忽略情况),则禁用测试。当应用于类级别时,该类中的所有测试方法也会自动禁用。

表达式可以是以下任何一种:

* [Spring Expression Language](core.html#expressions)表达式。例如:`@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")`

* 在 Spring[`Environment`](core.html#beans-environment)中可用的属性的占位符。例如:`@DisabledIf("${smoke.tests.disabled}")`

* 文字文字。例如:`@DisabledIf("true")`

然而,请注意,不是动态解析属性占位符的结果的文本文字是零实用价值的,因为`@DisabledIf("true")`等价于`@Disabled``@DisabledIf("false")`在逻辑上是没有意义的。

你可以使用`@DisabledIf`作为元注释来创建自定义组合注释。例如,你可以创建一个自定义`@DisabledOnMac`注释,如下所示:

爪哇

```
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
```

Kotlin

```
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@DisabledIf(
        expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
        reason = "Disabled on Mac OS"
)
annotation class DisabledOnMac {}
```

#### 3.4.5.测试的元注释支持

你可以使用大多数与测试相关的注释[元注释](core.html#beans-meta-annotations)来创建定制的组合注释,并减少跨测试套件的配置重复。

你可以结合[TestContext 框架](#testcontext-framework)使用以下每一项作为元注释。

* `@BootstrapWith`

* `@ContextConfiguration`

* `@ContextHierarchy`

* `@ActiveProfiles`

* `@TestPropertySource`

* `@DirtiesContext`

* `@WebAppConfiguration`

* `@TestExecutionListeners`

* `@Transactional`

* `@BeforeTransaction`

* `@AfterTransaction`

* `@Commit`

* `@Rollback`

* `@Sql`

* `@SqlConfig`

* `@SqlMergeMode`

* `@SqlGroup`

* `@Repeat` *(仅在 JUnit4 上支持)*

* `@Timed` *(仅在 JUnit4 上支持)*

* `@IfProfileValue` *(仅在 JUnit4 上支持)*

* `@ProfileValueSourceConfiguration` *(仅在 JUnit4 上支持)*

* `@SpringJUnitConfig` *(仅在 Junit Jupiter 上支持)*

* `@SpringJUnitWebConfig` *(仅在 Junit Jupiter 上支持)*

* `@TestConstructor` *(仅在 Junit Jupiter 上支持)*

* `@NestedTestConfiguration` *(仅在 Junit Jupiter 上支持)*

* `@EnabledIf` *(仅在 Junit Jupiter 上支持)*

* `@DisabledIf` *(仅在 Junit Jupiter 上支持)*

考虑以下示例:

爪哇

```
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
```

Kotlin

```
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
```

如果我们发现我们在基于 JUnit4 的测试套件中重复了前面的配置,那么我们可以通过引入自定义组合注释来减少重复,该注释集中了 Spring 的公共测试配置,如下所示:

爪哇

```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
```

Kotlin

```
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
```

然后,我们可以使用自定义的`@TransactionalDevTestConfig`注释来简化基于 JUnit4 的测试类的配置,如下所示:

爪哇

```
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
```

Kotlin

```
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class OrderRepositoryTests

@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class UserRepositoryTests
```

如果我们编写使用 JUnit Jupiter 的测试,我们可以进一步减少代码重复,因为 JUnit5 中的注释也可以用作元注释。考虑以下示例:

爪哇

```
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
```

Kotlin

```
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
```

如果我们发现我们在基于 JUnit Jupiter 的测试套件中重复了前面的配置,那么我们可以通过引入一个自定义组合注释来减少重复,该注释集中了 Spring 和 JUnit Jupiter 的公共测试配置,如下所示:

爪哇

```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
```

Kotlin

```
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
```

然后,我们可以使用自定义的`@TransactionalDevTestConfig`注释来简化基于 JUnit Jupiter 的各个测试类的配置,如下所示:

爪哇

```
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }
```

Kotlin

```
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }
```

由于 JUnit Jupiter 支持使用`@Test``@RepeatedTest``ParameterizedTest`等作为元注释,因此你还可以在测试方法级别上创建自定义的组合注释。例如,如果我们希望创建一个组合注释,将来自 Junit Jupiter 的`@Test``@Tag`注释与来自 Spring 的`@Transactional`注释结合在一起,那么我们可以创建一个`@TransactionalIntegrationTest`注释,如下所示:

爪哇

```
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
```

Kotlin

```
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
annotation class TransactionalIntegrationTest { }
```

然后我们可以使用我们的自定义`@TransactionalIntegrationTest`注释来简化基于 JUnit Jupiter 的单个测试方法的配置,如下所示:

爪哇

```
@TransactionalIntegrationTest
void saveOrder() { }

@TransactionalIntegrationTest
void deleteOrder() { }
```

Kotlin

```
@TransactionalIntegrationTest
fun saveOrder() { }

@TransactionalIntegrationTest
fun deleteOrder() { }
```

有关更多详细信息,请参见[Spring Annotation Programming Model](https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model)维基页面。

### 3.5. Spring TestContext 框架

Spring TestContext 框架(位于`org.springframework.test.context`包中)提供了通用的、注释驱动的单元和集成测试支持,该支持与正在使用的测试框架无关。TestContext 框架还非常重视约定而不是配置,使用合理的默认值,你可以通过基于注释的配置来覆盖这些默认值。

除了通用的测试基础设施之外,TestContext 框架还为 JUnit4、JUnit Jupiter(AKA 为 JUnit5)和 TestNG 提供了明确的支持。对于 JUnit4 和 TestNG, Spring 提供`abstract`支持类。此外, Spring 为 JUnit4 提供了自定义的 JUnit`Runner`和自定义的 JUnit`Rules`,为 JUnit Jupiter 提供了自定义的`Extension`,允许你编写所谓的 POJO 测试类。扩展特定的类层次结构不需要 POJO 测试类,例如`abstract`支持类。

下一节将概述 TestContext 框架的内部内容。如果你只对使用框架感兴趣,而对使用自己的自定义侦听器或自定义加载器扩展框架不感兴趣,可以直接访问配置([上下文管理](#testcontext-ctx-management)[依赖注入](#testcontext-fixture-di)[事务管理](#testcontext-tx))、[支持类](#testcontext-support-classes)[注释支持](#integration-testing-annotations)部分。

#### 3.5.1.关键抽象

框架的核心包括[依赖注入](#testcontext-fixture-di)类和`TestContext``TestExecutionListener``SmartContextLoader`接口。为每个测试类创建`TestContextManager`(例如,用于在 JUnit Jupiter 中的单个测试类中执行所有测试方法)。而`TestContextManager`则管理保存当前测试上下文的`TestContext`。随着测试的进行,`TestContextManager`还会更新`TestContext`的状态,并将其委托给`TestExecutionListener`实现,该实现通过提供依赖注入、管理事务等来实现实际的测试执行。a`SmartContextLoader`负责为给定的测试类加载`ApplicationContext`。有关更多信息和各种实现的示例,请参见[javadoc](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/test/context/package-summary.html)和 Spring 测试套件。

##### `TestContext`

`TestContext`封装了运行测试的上下文(与实际使用的测试框架无关),并为它负责的测试实例提供了上下文管理和缓存支持。如果请求,`TestContext`还将委托给`SmartContextLoader`以加载`ApplicationContext`

##### `TestContextManager`

`TestContextManager`是 Spring TestContext 框架的主要入口点,负责管理单个`TestContext`,并在定义良好的测试执行点向每个注册的`TestExecutionListener`发送事件信号:

* 在特定测试框架的任何“类之前”或“所有之前”方法之前。

* 测试实例后处理。

* 在特定测试框架的任何“before”或“before each”方法之前。

* 在测试方法执行之前,但在测试设置之后.

* 在测试方法执行后但在测试前立即拆除。

* 在特定测试框架的任何“之后”或“之后”方法之后。

* 在特定测试框架的任何“课后”或“毕竟”方法之后。

##### `TestExecutionListener`

`TestExecutionListener`定义了用于对注册侦听器的`TestContextManager`发布的测试执行事件做出反应的 API。参见[`TestExecutionListener`配置]。

##### 上下文加载程序

`ContextLoader`是一个策略接口,用于为 Spring TestContext 框架管理的集成测试加载`ApplicationContext`。你应该实现`WebApplicationContext`而不是这个接口,以提供对组件类、活动 Bean 定义配置文件、测试属性源、上下文层次结构和`WebApplicationContext`支持的支持。

`SmartContextLoader``ContextLoader`接口的扩展,它取代了原始的极小值`ContextLoader`SPI。具体地说,`SmartContextLoader`可以选择处理资源位置、组件类或上下文初始化器。此外,`SmartContextLoader`可以在其加载的上下文中设置活动 Bean 定义配置文件和测试属性源。

Spring 提供了以下实现方式:

* `TestContext`:两个默认加载器之一,它在内部委托给一个`AnnotationConfigContextLoader`、一个`GenericXmlContextLoader`或一个`GenericGroovyXmlContextLoader`,这取决于为测试类声明的配置,或者取决于是否存在默认位置或默认配置类。仅当 Groovy 位于 Classpath 上时,才启用 Groovy 支持。

* `WebDelegatingSmartContextLoader`:两个默认加载器之一,它在内部委托给一个`AnnotationConfigWebContextLoader`、一个`GenericXmlWebContextLoader`或一个`GenericGroovyXmlWebContextLoader`,这取决于为测试类声明的配置,或者取决于缺省位置或缺省配置类的存在。只有当测试类上存在`@WebAppConfiguration`时,才使用 Web`ContextLoader`。仅当 Groovy 位于 Classpath 上时,才启用 Groovy 支持。

* `AnnotationConfigContextLoader`:从组件类加载标准的`ApplicationContext`

* `AnnotationConfigWebContextLoader`:从组件类加载`WebApplicationContext`

* `GenericGroovyXmlContextLoader`:从 Groovy 脚本或 XML 配置文件的资源位置加载标准的`ApplicationContext`

* `GenericGroovyXmlWebContextLoader`:从 Groovy 脚本或 XML 配置文件的资源位置加载`WebApplicationContext`

* `GenericXmlContextLoader`:从 XML 资源位置加载标准的`ApplicationContext`

* `GenericXmlWebContextLoader`:从 XML 资源位置加载`WebApplicationContext`

#### 3.5.2.引导 TestContext 框架

Spring TestContext 框架内部的默认配置对于所有常见的用例都足够了。但是,有时开发团队或第三方框架希望更改默认的`ContextLoader`,实现自定义的`GenericXmlWebContextLoader`或,增加`ContextCustomizerFactory``TestExecutionListener`实现的默认集,以此类推。对于这种对 TestContext 框架如何操作的低级控制, Spring 提供了一种引导策略。

`ApplicationEventsTestExecutionListener`定义了引导 TestContext 框架的 SPI。`TestContextBootstrapper``TestContextManager`用于加载用于当前测试的`TestExecutionListener`实现,并构建其管理的`TestContext`。你可以直接使用`@BootstrapWith`或作为元注释,为测试类(或测试类层次结构)配置自定义引导策略。如果没有使用`@BootstrapWith`显式配置引导程序,则使用`DefaultTestContextBootstrapper``WebTestContextBootstrapper`,这取决于`@WebAppConfiguration`的存在。

由于`TestContextBootstrapper`SPI 在未来可能会发生变化(以适应新的需求),因此我们强烈鼓励实现者不要直接实现这个接口,而是扩展`AbstractTestContextBootstrapper`或它的一个具体子类。

#### 3.5.3.`TestExecutionListener`配置

Spring 提供了以下`TestExecutionListener`默认情况下注册的实现,其顺序完全如下:

* `ServletTestExecutionListener`:为`WebApplicationContext`配置 Servlet API 模拟。

* `GenericGroovyXmlWebContextLoader`:处理“before”模式的`@DirtiesContext`注释。

* `ApplicationEventsTestExecutionListener`:提供对[`ApplicationEvents`](#TestContext-Application-Events)的支持。

* `DependencyInjectionTestExecutionListener`:为测试实例提供依赖注入。

* `DirtiesContextTestExecutionListener`:处理“after”模式的`@DirtiesContext`注释。

* `TransactionalTestExecutionListener`:提供带有默认回滚语义的事务测试执行。

* `SqlScriptsTestExecutionListener`:运行使用`TransactionalTestExecutionListener`注释配置的 SQL 脚本。

* `EventPublishingTestExecutionListener`:将测试执行事件发布到测试的`ApplicationContext`(参见[测试执行事件](#testcontext-test-execution-events))。

##### 注册`TestExecutionListener`实现

你可以使用`@TestExecutionListeners`注释来注册测试类及其子类的`TestExecutionListener`实现。有关详细信息和示例,请参见[注释支持](#integration-testing-annotations)和[`@TestExecutionListeners`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/contexectionlisteners.html)的 爪哇doc。

##### 默认`TestExecutionListener`实现的自动发现

通过使用`@TestExecutionListeners`注册`TestExecutionListener`实现适合于在有限的测试场景中使用的自定义侦听器。但是,如果需要在整个测试套件中使用自定义侦听器,那么它可能会变得很麻烦。该问题通过支持通过`SpringFactoriesLoader`机制自动发现默认`TestExecutionListener`实现来解决。

具体地说,`ApplicationEvents`模块在其`META-INF/spring.factories`属性文件中的`org.springframework.test.context.TestExecutionListener`键下声明所有核心默认`TestExecutionListener`实现。第三方框架和开发人员可以通过他们自己的`META-INF/spring.factories`属性文件以同样的方式向默认侦听器列表贡献他们自己的`TestExecutionListener`实现。

##### 排序`TestExecutionListener`实现

当 TestContext 框架通过[前述](#testcontext-tel-config-automatic-discovery)`SpringFactoriesLoader`机制发现默认的`TestExecutionListener`实现时,实例化的侦听器将通过使用 Spring 的`SpringFactoriesLoader`进行排序,该命令使用 Spring 的`Ordered`接口和`@Order`注释进行排序。`AbstractTestExecutionListener`和 Spring 提供的所有默认`TestExecutionListener`实现用适当的值实现`Ordered`。因此,第三方框架和开发人员应该通过实现`TestExecutionListener`或声明`@Order`来确保其默认的`TestExecutionListener`实现是按正确的顺序注册的。关于分配给每个核心侦听器的值的详细信息,请参见 爪哇doc 获取核心默认`getOrder()`实现的`TestExecutionListener`方法。

##### 合并`TestExecutionListener`实现

如果通过`@TestExecutionListeners`注册了自定义`TestExecutionListener`,则不会注册默认侦听器。在大多数常见的测试场景中,这有效地迫使开发人员手动声明除任何自定义侦听器之外的所有默认侦听器。下面的清单演示了这种配置风格:

爪哇

```
@ContextConfiguration
@TestExecutionListeners({
    MyCustomTestExecutionListener.class,
    ServletTestExecutionListener.class,
    DirtiesContextBeforeModesTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    SqlScriptsTestExecutionListener.class
})
class MyTest {
    // class body...
}
```

Kotlin

```
@ContextConfiguration
@TestExecutionListeners(
    MyCustomTestExecutionListener::class,
    ServletTestExecutionListener::class,
    DirtiesContextBeforeModesTestExecutionListener::class,
    DependencyInjectionTestExecutionListener::class,
    DirtiesContextTestExecutionListener::class,
    TransactionalTestExecutionListener::class,
    SqlScriptsTestExecutionListener::class
)
class MyTest {
    // class body...
}
```

这种方法的挑战在于,它要求开发人员准确地知道默认注册了哪些侦听器。此外,缺省侦听器集可以从一个版本更改到另一个版本——例如,`SqlScriptsTestExecutionListener`在 Spring Framework4.1 中引入,`DirtiesContextBeforeModesTestExecutionListener`在 Spring Framework4.2 中引入。此外,像 Spring 引导和 Spring 安全之类的第三方框架通过使用前述的[自动发现机制](#testcontext-tel-config-automatic-discovery)来注册它们自己的默认`TestExecutionListener`实现。

为了避免必须意识到并重新声明所有默认侦听器,你可以将`@TestExecutionListeners``mergeMode`属性设置为`MergeMode.MERGE_WITH_DEFAULTS``MERGE_WITH_DEFAULTS`表示本地声明的侦听器应该与默认侦听器合并。合并算法确保从列表中删除重复项,并确保合并后的侦听器集合根据`AnnotationAwareOrderComparator`的语义进行排序,如[Ordering`TestExecutionListener`实现](#TestContext-TEL-Config-Ordering)中所述。如果侦听器实现了`Ordered`,或者用`@Order`进行了注释,那么它可能会影响与默认值合并的位置。否则,在合并时,本地声明的侦听器将被追加到默认侦听器列表中。

例如,如果上一个示例中的`MyCustomTestExecutionListener`类将其`order`值(例如,`500`)配置为小于`ServletTestExecutionListener`(恰好是`1000`)的顺序,然后可以将`MyCustomTestExecutionListener``ServletTestExecutionListener`前面的默认值列表自动合并,并且可以用以下示例替换前面的示例:

爪哇

```
@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}
```

Kotlin

```
@ContextConfiguration
@TestExecutionListeners(
        listeners = [MyCustomTestExecutionListener::class],
        mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}
```

#### 3.5.4.应用程序事件

Spring Framework5.3.3 以来,TestContext Framework 提供了对记录[应用程序事件](core.html#context-functionality-events)中发布的`ApplicationContext`的支持,以便可以针对测试中的那些事件执行断言。在执行单个测试期间发布的所有事件都可以通过`ApplicationEvents`API 获得,该 API 允许你以`java.util.Stream`的形式处理事件。

要在测试中使用`ApplicationEvents`,请执行以下操作。

* 确保你的测试类是用[`@RecordApplicationEvents`](# Spring-testing-annotation-recordapplicationEvents)进行注释或元注释的。

* 确保`ApplicationEventsTestExecutionListener`已注册。但是,请注意,`ApplicationEventsTestExecutionListener`是默认注册的,并且只有在你通过`@TestExecutionListeners`进行了自定义配置且不包括默认侦听器的情况下,才需要手动注册。

*`@Autowired`注释类型`ApplicationEvents`的字段,并在你的测试和生命周期方法(例如 JUnit Jupiter 中的`@BeforeEach``@AfterEach`方法)中使用`ApplicationEvents`的实例。

  * 当使用[朱尼特木星的 SpringExtension](#testcontext-junit-jupiter-extension)时,可以在测试或生命周期方法中声明类型为`ApplicationEvents`的方法参数,作为测试类中`@Autowired`字段的替代。

下面的测试类使用`SpringExtension`for JUnit Jupiter 和[AssertJ](https://assertj.github.io/doc/)来断言在 Spring 管理的组件中调用方法时发布的应用程序事件的类型:

爪哇

```
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {

    @Autowired
    OrderService orderService;

    @Autowired
    ApplicationEvents events; (2)

    @Test
    void submitOrder() {
        // Invoke method in OrderService that publishes an event
        orderService.submitOrder(new Order(/* ... */));
        // Verify that an OrderSubmitted event was published
        long numEvents = events.stream(OrderSubmitted.class).count(); (3)
        assertThat(numEvents).isEqualTo(1);
    }
}
```

|**1**|用`@RecordApplicationEvents`注释测试类。|
|-----|-----------------------------------------------------------------------------------------|
|**2**|为当前测试注入`ApplicationEvents`实例。|
|**3**|使用`ApplicationEvents`API 来计算发布了多少`OrderSubmitted`事件。|

Kotlin

```
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {

    @Autowired
    lateinit var orderService: OrderService

    @Autowired
    lateinit var events: ApplicationEvents (2)

    @Test
    fun submitOrder() {
        // Invoke method in OrderService that publishes an event
        orderService.submitOrder(Order(/* ... */))
        // Verify that an OrderSubmitted event was published
        val numEvents = events.stream(OrderSubmitted::class).count() (3)
        assertThat(numEvents).isEqualTo(1)
    }
}
```

|**1**|用`@RecordApplicationEvents`注释测试类。|
|-----|-----------------------------------------------------------------------------------------|
|**2**|为当前测试注入`ApplicationEvents`实例。|
|**3**|使用`ApplicationEvents`API 来计算发布了多少`OrderSubmitted`事件。|

有关`ApplicationEvents`爪哇doc](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/event/applicationevents.html)的更多详细信息,请参见[`ApplicationEvents`api。

#### 3.5.5.测试执行事件

Spring Framework5.2 中引入的`EventPublishingTestExecutionListener`提供了一种实现自定义`TestExecutionListener`的替代方法。测试的`ApplicationContext`中的组件可以侦听`EventPublishingTestExecutionListener`发布的以下事件,每个事件对应于`TestExecutionListener`API 中的一个方法。

* `BeforeTestClassEvent`

* `PrepareTestInstanceEvent`

* `BeforeTestMethodEvent`

* `BeforeTestExecutionEvent`

* `AfterTestExecutionEvent`

* `AfterTestMethodEvent`

* `AfterTestClassEvent`

|   |只有在`ApplicationContext`已经加载的情况下,这些事件才会发布。|
|---|------------------------------------------------------------------------------------|

这些事件可能由于各种原因而被使用,例如重置模拟 bean 或跟踪测试执行。使用测试执行事件而不是实现自定义`TestExecutionListener`的一个优点是,测试执行事件可以被注册在测试`ApplicationContext`中的任何 Spring  Bean 消耗,并且这样的 bean 可以直接受益于依赖注入和`ApplicationContext`的其他特性。相反,在`ApplicationContext`中,a`AfterTestClassEvent`不是 Bean。

为了侦听测试执行事件, Spring  Bean 可以选择实现`org.springframework.context.ApplicationListener`接口。或者,侦听器方法可以使用`AfterTestClassEvent`进行注释,并配置为侦听上面列出的特定事件类型之一(参见[基于注释的事件监听器](core.html#context-functionality-events-annotation))。由于这种方法的流行, Spring 提供了以下专用的`@EventListener`注释,以简化测试执行事件侦听器的注册。这些注释驻留在`org.springframework.test.context.event.annotation`包中。

* `@BeforeTestClass`

* `@PrepareTestInstance`

* `@BeforeTestMethod`

* `@BeforeTestExecution`

* `@AfterTestExecution`

* `@AfterTestMethod`

* `@AfterTestClass`

##### 异常处理

默认情况下,如果测试执行事件侦听器在使用事件时抛出异常,则该异常将传播到使用中的底层测试框架(例如 JUnit 或 TestNG)。例如,如果消耗`BeforeTestMethodEvent`导致异常,则相应的测试方法将作为异常的结果而失败。相反,如果异步测试执行事件侦听器抛出异常,则异常将不会传播到底层测试框架。有关异步异常处理的更多详细信息,请参阅类级 爪哇doc for`@EventListener`

##### 异步侦听器

如果希望特定的测试执行事件侦听器异步处理事件,可以使用 Spring 的[regular`@Async`support](integration.html#schooling-annotation-support-async)。有关更多详细信息,请参见类级 爪哇doc`@EventListener`

#### 3.5.6.上下文管理

每个`TestContext`都为其负责的测试实例提供了上下文管理和缓存支持。测试实例不会自动接收对配置的`ApplicationContext`的访问。但是,如果测试类实现了`ApplicationContextAware`接口,则将向测试实例提供对`ApplicationContext`的引用。注意`AbstractJUnit4SpringContextTests``AbstractTestNGSpringContextTests`实现`ApplicationContextAware`,因此,自动提供对`ApplicationContext`的访问。

|     |@AutoWired ApplicationContext<br/><br/>作为实现`ApplicationContextAware`接口的一种替代方案,你可以通过<br/>在`@Autowired`一个字段或 setter 方法上的<br/><br/>java=“1231”/>`@Autowired`<1223">>><gt="gt=">gt=”1228"|**1**|Injecting the `ApplicationContext`.|<br/>|-----|-----------------------------------|<br/><br/>Kotlin<br/><br/>```<br/>@SpringJUnitConfig<br/>class MyTest {<br/><br/>    @Autowired (1)<br/>    lateinit var applicationContext: ApplicationContext<br/><br/>    // class body...<br/>}<br/>```<br/><br/>|**1**|Injecting the `ApplicationContext`.|<br/>|-----|-----------------------------------|<br/><br/>Similarly, if your test is configured to load a `WebApplicationContext`, you can inject<br/>the web application context into your test, as follows:<br/><br/>爪哇<br/><br/>```<br/>@SpringJUnitWebConfig (1)<br/>class MyWebAppTest {<br/><br/>    @Autowired (2)<br/>    WebApplicationContext wac;<br/><br/>    // class body...<br/>}<br/>```<br/><br/>|**1**|Configuring the `WebApplicationContext`.|<br/>|-----|----------------------------------------|<br/>|**2**| Injecting the `WebApplicationContext`. |<br/><br/>Kotlin<br/><br/>```<br/>@SpringJUnitWebConfig (1)<br/>class MyWebAppTest {<br/><br/>    @Autowired (2)<br/>    lateinit var wac: WebApplicationContext<br/>    // class body...<br/>}<br/>```<br/><br/>|**1**|Configuring the `WebApplicationContext`.|<br/>|-----|----------------------------------------|<br/>|**2**| Injecting the `WebApplicationContext`. |<br/><br/>Dependency injection by using `@Autowired` is provided by the`DependencyInjectionTestExecutionListener`, which is configured by default<br/>(see [Dependency Injection of Test Fixtures](#testcontext-fixture-di)).|
|-----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**1**|注入`ApplicationContext`。|
|**1**|注入`ApplicationContext`。|
|**1**|配置`WebApplicationContext`。|
|**2**|注入`WebApplicationContext`。|
|**1**|配置`WebApplicationContext`。|
|**2**|注入`WebApplicationContext`。|

使用 TestContext 框架的测试类不需要扩展任何特定的类或实现特定的接口来配置它们的应用程序上下文。相反,配置是通过在类级别声明`@ContextConfiguration`注释来实现的。如果你的测试类没有显式声明应用程序上下文资源位置或组件类,那么配置的`ContextLoader`将决定如何从默认位置或默认配置类加载上下文。除了上下文资源位置和组件类之外,还可以通过应用程序上下文初始化器来配置应用程序上下文。

下面的部分解释了如何使用 Spring 的`@ContextConfiguration`注释来通过使用 XML 配置文件、Groovy 脚本、组件类(通常是`@Configuration`类)或上下文初始化器来配置测试`ApplicationContext`。或者,你可以为高级用例实现和配置你自己的自定义`SmartContextLoader`

* [使用 XML 资源的上下文配置](#testcontext-ctx-management-xml)

* [使用 Groovy 脚本的上下文配置](#testcontext-ctx-management-groovy)

* [具有组件类的上下文配置](#testcontext-ctx-management-javaconfig)

* [混合 XML、Groovy 脚本和组件类](#testcontext-ctx-management-mixed-config)

* [带有上下文初始化器的上下文配置](#testcontext-ctx-management-initializers)

* [上下文配置继承](#testcontext-ctx-management-inheritance)

* [具有环境配置文件的上下文配置](#testcontext-ctx-management-env-profiles)

* [具有测试属性源的上下文配置](#testcontext-ctx-management-property-sources)

* [具有动态属性源的上下文配置](#testcontext-ctx-management-dynamic-property-sources)

* [加载`WebApplicationContext`](#testcontext-ctx-management-web)

* [上下文缓存](#testcontext-ctx-management-caching)

* [上下文层次结构](#testcontext-ctx-management-ctx-hierarchies)

##### 使用 XML 资源的上下文配置

要通过使用 XML 配置文件为测试加载`ApplicationContext`,请用`@ContextConfiguration`注释测试类,并使用包含 XML 配置元数据资源位置的数组配置`locations`属性。普通或相对路径(例如,`context.xml`)被视为相对于定义测试类的包的 Classpath 资源。以斜杠开头的路径被视为绝对 Classpath 位置(例如,`/org/example/config.xml`)。表示资源 URL 的路径(即,带有`classpath:``file:``http:`等前缀的路径)被使用*就像现在一样*

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
    // class body...
}
```

|**1**|将 locations 属性设置为 XML 文件列表。|
|-----|-------------------------------------------------------|

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
    // class body...
}
```

|**1**|将 locations 属性设置为 XML 文件列表。|
|-----|-------------------------------------------------------|

`@ContextConfiguration`通过标准的 爪哇`value`属性支持`locations`属性的别名。因此,如果不需要在`@ContextConfiguration`中声明其他属性,则可以省略`locations`属性名的声明,并使用以下示例中演示的速记格式声明资源位置:

爪哇

```
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
    // class body...
}
```

|**1**|指定 XML 文件而不使用`location`属性。|
|-----|------------------------------------------------------------|

Kotlin

```
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
    // class body...
}
```

|**1**|指定 XML 文件而不使用`location`属性。|
|-----|------------------------------------------------------------|

如果从`@ContextConfiguration`注释中省略`locations``value`属性,TestContext 框架将尝试检测默认的 XML 资源位置。具体地说,`GenericXmlContextLoader``GenericXmlWebContextLoader`根据测试类的名称检测缺省位置。如果你的类名为`com.example.MyTest`,则`GenericXmlContextLoader``"classpath:com/example/MyTest-context.xml"`加载应用程序上下文。下面的示例展示了如何做到这一点:

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
```

|**1**|从默认位置加载配置。|
|-----|------------------------------------------------|

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
```

|**1**|从默认位置加载配置。|
|-----|------------------------------------------------|

##### 使用 Groovy 脚本的上下文配置

要使用使用使用[Groovy Bean Definition DSL](core.html#groovy-bean-definition-dsl)的 Groovy 脚本为你的测试加载`ApplicationContext`,你可以使用`@ContextConfiguration`注释测试类,并配置`locations``value`属性,并使用一个包含 Groovy 脚本资源位置的数组。Groovy 脚本的资源查找语义与[XML 配置文件](#testcontext-ctx-management-xml)的资源查找语义相同。

|   |启用 Groovy 脚本支持`value`<br/>在 Spring <br/>testContext 框架中使用 Groovy 脚本加载`ApplicationContext`的支持是自动启用的,如果 Groovy 在 Classpath 上。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

下面的示例展示了如何指定 Groovy 配置文件:

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) (1)
class MyTest {
    // class body...
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") (1)
class MyTest {
    // class body...
}
```

|**1**|指定 Groovy 配置文件的位置。|
|-----|------------------------------------------------------|

如果从`@ContextConfiguration`注释中省略`locations``value`属性,TestContext 框架将尝试检测默认的 Groovy 脚本。具体地说,`GenericGroovyXmlContextLoader``GenericGroovyXmlWebContextLoader`基于测试类的名称检测缺省位置。如果你的类名为`com.example.MyTest`,那么 Groovy 上下文加载程序将从`"classpath:com/example/MyTestContext.groovy"`加载应用程序上下文。下面的示例展示了如何使用默认值:

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
```

|**1**|从默认位置加载配置。|
|-----|------------------------------------------------|

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
    // class body...
}
```

|**1**|从默认位置加载配置。|
|-----|------------------------------------------------|

|   |同时声明 XML 配置和 Groovy 脚本<br/><br/>通过使用<br/>`locations``value`属性,可以同时声明 XML 配置文件和 Groovy 脚本。如果到<br/>配置资源位置的路径以`.xml`结束,则使用`XmlBeanDefinitionReader`加载。否则,它将通过使用`GroovyBeanDefinitionReader`来加载。<br/><br/>下面的列表显示了如何在集成测试中合并这两个测试:<br/><br/><br/><br/><br/><br/><br/><<<>r=“1319”>r=“>”><gt=">r=”1319“/>r=”r=“>”>“><gt=”r="|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### 具有组件类的上下文配置

要通过使用组件类为你的测试加载`ApplicationContext`(参见[基于 爪哇 的容器配置](core.html#beans-java)),你可以用`@ContextConfiguration`注释你的测试类,并用一个包含对组件类的引用的数组配置`classes`属性。下面的示例展示了如何做到这一点:

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) (1)
class MyTest {
    // class body...
}
```

|**1**|指定组件类。|
|-----|-----------------------------|

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) (1)
class MyTest {
    // class body...
}
```

|**1**|指定组件类。|
|-----|-----------------------------|

|   |组件类<br/><br/>术语“组件类”可以指以下任何一种:<br/><br/>* 用<br/>注释的类<br/>* 一个组件(即用`@Component`、`@Service`、`WebApplicationContext`注释的类,或其他原型注释)。<br/><br/>* 一个用`javax.inject`注释的 JSR-330 兼容的类。<br/><br/>* 任何包含`@Bean`-methods 的类。<br/><br/>* 任何其他打算在<80]组件中注册为 Spring 组件的类(即,一个 Spring gt=“1347”/>r=“1331”," 可能利用不使用 Spring 注释的单个构造函数的自动连接<br/>。<br/><br/>参见[`@Configuration`](https://DOCS. Spring.io/ Spring-framework/5.3.16/javadog/javadoc-api-api/org/5.3.16/javadoc-api-api-api/org/org/javadoc-api-api/org/org/spramework/Springframework/contem/context/context/context/context/cont 特别注意<br/>`@Bean`精简模式的讨论。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果省略`@ContextConfiguration`注释中的`classes`属性,TestContext 框架将尝试检测缺省配置类的存在。具体地说,`AnnotationConfigContextLoader``AnnotationConfigWebContextLoader`检测满足配置类实现要求的测试类的所有`static`嵌套类,如在[`@Configuration`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/context/annation/configuration.html)中指定的。请注意,配置类的名称是任意的。此外,如果需要,一个测试类可以包含多个`static`嵌套配置类。在下面的示例中,`OrderServiceTest`类声明了一个名为`static`的嵌套配置类,该配置类自动用于为测试类加载`ApplicationContext`:

爪哇

```
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {

    @Configuration
    static class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        OrderService orderService() {
            OrderService orderService = new OrderServiceImpl();
            // set properties, etc.
            return orderService;
        }
    }

    @Autowired
    OrderService orderService;

    @Test
    void testOrderService() {
        // test the orderService
    }

}
```

|**1**|从嵌套的`Config`类加载配置信息。|
|-----|-----------------------------------------------------------------|

Kotlin

```
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the nested Config class
class OrderServiceTest {

    @Autowired
    lateinit var orderService: OrderService

    @Configuration
    class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        fun orderService(): OrderService {
            // set properties, etc.
            return OrderServiceImpl()
        }
    }

    @Test
    fun testOrderService() {
        // test the orderService
    }
}
```

|**1**|从嵌套的`Config`类加载配置信息。|
|-----|-----------------------------------------------------------------|

##### 混合 XML、Groovy 脚本和组件类

有时可能需要混合 XML 配置文件、Groovy 脚本和组件类(通常是`@Configuration`类)来为测试配置`ApplicationContext`。例如,如果你在生产中使用 XML 配置,那么你可能会决定使用`@Configuration`类来为你的测试配置特定的由 Spring 管理的组件,反之亦然。

此外,一些第三方框架(例如 Spring Boot)提供了用于同时从不同类型的资源(例如,XML 配置文件、Groovy 脚本和类)加载的一流支持。 Spring 框架在历史上并不支持这一标准部署。因此, Spring 框架在`spring-test`模块中交付的大多数`SmartContextLoader`实现只为每个测试上下文支持一种资源类型。然而,这并不意味着你不能同时使用这两种方法。一般规则的一个例外是`GenericGroovyXmlContextLoader``GenericGroovyXmlWebContextLoader`同时支持 XML 配置文件和 Groovy 脚本。此外,第三方框架可以选择通过`@ContextConfiguration`来支持`locations``classes`的声明,并且,在 TestContext 框架中的标准测试支持下,你有以下选项。

如果希望使用资源位置(例如,XML 或 Groovy)和`@Configuration`类来配置测试,则必须选择一个作为入口点,其中一个必须包含或导入另一个。例如,在 XML 或 Groovy 脚本中,可以通过使用组件扫描或将它们定义为正常的 Spring bean 来包含`@Configuration`类,而在`@Configuration`类中,可以使用`@ImportResource`来导入 XML 配置文件或 Groovy 脚本。请注意,这种行为在语义上等同于在生产中配置应用程序的方式:在生产配置中,你可以定义一组 XML 或 Groovy 资源位置,也可以定义一组`@Configuration`类,从这些类加载你的生产`ApplicationContext`,但是,你仍然可以自由地包含或导入其他类型的配置。

##### 带有上下文初始化器的上下文配置

要使用上下文初始化器为测试配置`ApplicationContext`,请用`@ContextConfiguration`注释测试类,并使用一个数组配置`initializers`属性,该数组包含对实现`ApplicationContextInitializer`的类的引用。然后使用声明的上下文初始化器初始化为测试加载的`ConfigurableApplicationContext`。请注意,每个声明的初始化器支持的具体`ConfigurableApplicationContext`类型必须与使用中的`SmartContextLoader`创建的`ApplicationContext`类型兼容(通常是`GenericApplicationContext`)。此外,初始化器被调用的顺序取决于它们是实现 Spring 的`Ordered`接口,还是使用 Spring 的`@Order`注释或标准的`@Priority`注释。下面的示例展示了如何使用初始化器:

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class) (1)
class MyTest {
    // class body...
}
```

|**1**|通过使用配置类和初始化器指定配置。|
|-----|---------------------------------------------------------------------------|

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
        classes = [TestConfig::class],
        initializers = [TestAppCtxInitializer::class]) (1)
class MyTest {
    // class body...
}
```

|**1**|通过使用配置类和初始化器指定配置。|
|-----|---------------------------------------------------------------------------|

你还可以完全省略`@ContextConfiguration`中的 XML 配置文件、Groovy 脚本或组件类的声明,而只声明`ApplicationContextInitializer`类,这些类随后负责在上下文中注册 bean——例如,通过编程方式从 XML 文件或配置类中加载 Bean 定义。下面的示例展示了如何做到这一点:

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) (1)
class MyTest {
    // class body...
}
```

|**1**|仅使用初始化器指定配置。|
|-----|------------------------------------------------------|

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = [EntireAppInitializer::class]) (1)
class MyTest {
    // class body...
}
```

|**1**|仅使用初始化器指定配置。|
|-----|------------------------------------------------------|

##### 上下文配置继承

`@ContextConfiguration`支持布尔`inheritLocations``inheritInitializers`属性,这些属性表示是否应该继承由超类声明的资源位置或组件类和上下文初始化器。这两个标志的默认值都是`true`。这意味着测试类继承了资源位置或组件类,以及由任何超类声明的上下文初始化器。具体地说,测试类的资源位置或组件类被追加到由超类声明的资源位置或注释类的列表中。类似地,给定测试类的初始化器被添加到由测试超类定义的初始化器集合中。因此,子类可以选择扩展资源位置、组件类或上下文初始化器。

如果`inheritLocations``inheritInitializers`中的`inheritInitializers`属性被设置为`false`,则资源位置或组件类和上下文初始化器分别用于测试类的影子和有效替换由超类定义的配置。

|   |在 Spring Framework5.3 中,测试配置也可以从包含<br/>类中继承。有关详细信息,请参见[`@Nested`测试类配置]。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在下一个使用 XML 资源位置的示例中,`ApplicationContext``ExtendedTest``ApplicationContext`按照这个顺序从`base-config.xml``extended-config.xml`加载。因此,在`extended-config.xml`中定义的 bean 可以覆盖(即替换)在`base-config.xml`中定义的 bean。下面的示例展示了一个类如何扩展另一个类,并同时使用自己的配置文件和超类的配置文件:

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest extends BaseTest {
    // class body...
}
```

|**1**|在超类中定义的配置文件。|
|-----|---------------------------------------------|
|**2**|在子类中定义的配置文件。|

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest : BaseTest() {
    // class body...
}
```

|**1**|在超类中定义的配置文件。|
|-----|---------------------------------------------|
|**2**|在子类中定义的配置文件。|

类似地,在下一个使用组件类的示例中,`ApplicationContext``ExtendedTest`是从`BaseConfig``ExtendedConfig`类按此顺序加载的。因此,在`ExtendedConfig`中定义的 bean 可以覆盖(即替换)在`BaseConfig`中定义的 bean。下面的示例展示了一个类如何扩展另一个类,并同时使用自己的配置类和超类的配置类:

爪哇

```
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) (2)
class ExtendedTest extends BaseTest {
    // class body...
}
```

|**1**|在超类中定义的配置类。|
|-----|----------------------------------------------|
|**2**|在子类中定义的配置类。|

Kotlin

```
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig::class) (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig::class) (2)
class ExtendedTest : BaseTest() {
    // class body...
}
```

|**1**|在超类中定义的配置类。|
|-----|----------------------------------------------|
|**2**|在子类中定义的配置类。|

在下一个使用上下文初始化器的示例中,`ApplicationContext``ExtendedTest`通过使用`BaseInitializer``ExtendedInitializer`进行初始化。然而,请注意,初始化器被调用的顺序取决于它们是实现 Spring 的`Ordered`接口,还是使用 Spring 的`@Order`注释或标准的`@Priority`注释。下面的示例展示了一个类如何扩展另一个类,并同时使用自己的初始化器和超类的初始化器:

爪哇

```
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class) (1)
class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) (2)
class ExtendedTest extends BaseTest {
    // class body...
}
```

|**1**|在超类中定义的初始化器。|
|-----|--------------------------------------|
|**2**|在子类中定义的初始化器。|

Kotlin

```
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = [BaseInitializer::class]) (1)
open class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = [ExtendedInitializer::class]) (2)
class ExtendedTest : BaseTest() {
    // class body...
}
```

|**1**|在超类中定义的初始化器。|
|-----|--------------------------------------|
|**2**|在子类中定义的初始化器。|

##### 具有环境配置文件的上下文配置

Spring 框架具有对环境和配置文件(AKA“ Bean 定义配置文件”)的概念的一流支持,并且集成测试可以被配置为针对各种测试场景激活特定的 Bean 定义配置文件。这是通过用`@ActiveProfiles`注释一个测试类并提供一个配置文件列表来实现的,这些配置文件在为测试加载`ApplicationContext`时应该被激活。

|   |你可以将`@ActiveProfiles`用于`SmartContextLoader`SPI 的任何实现,但是`@ActiveProfiles`不支持较早的`ContextLoader`SPI 的实现。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

考虑使用 XML 配置和`@Configuration`类的两个示例:

```
<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <bean id="transferService"
            class="com.bank.service.internal.DefaultTransferService">
        <constructor-arg ref="accountRepository"/>
        <constructor-arg ref="feePolicy"/>
    </bean>

    <bean id="accountRepository"
            class="com.bank.repository.internal.JdbcAccountRepository">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="feePolicy"
        class="com.bank.service.internal.ZeroFeePolicy"/>

    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script
                location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>

    <beans profile="default">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
        </jdbc:embedded-database>
    </beans>

</beans>
```

爪哇

```
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}
```

当运行`TransferServiceTest`时,其`ApplicationContext`将从 Classpath 的根目录中的`app-config.xml`配置文件加载。如果检查`app-config.xml`,可以看到`accountRepository` Bean 对`dataSource` Bean 具有依赖关系。然而,`dataSource`并未被定义为顶层 Bean。相反,`dataSource`被定义了三次:在`production`配置文件中,在`dev`配置文件中,以及在`default`配置文件中。

通过用`TransferServiceTest`注释`@ActiveProfiles("dev")`,我们指示 Spring TestContext 框架加载`ApplicationContext`,并将活动配置文件设置为`{"dev"}`。结果,嵌入式数据库被创建并填充了测试数据,并且`accountRepository` Bean 与开发`DataSource`的引用连接。这很可能就是我们在集成测试中想要的。

有时,将 bean 分配到`default`配置文件中是有用的。只有当没有其他配置文件被特别激活时,默认配置文件中的 bean 才会被包含。你可以使用它来定义要在应用程序的默认状态下使用的“fallback”bean。例如,你可以显式地为`dev``production`配置文件提供数据源,但在这两个配置文件都不是活动的情况下,将内存中数据源定义为默认值。

下面的代码清单演示了如何使用`@Configuration`类而不是 XML 来实现相同的配置和集成测试:

爪哇

```
@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
```

Kotlin

```
@Configuration
@Profile("dev")
class StandaloneDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }
}
```

爪哇

```
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
```

Kotlin

```
@Configuration
@Profile("production")
class JndiDataConfig {

    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}
```

爪哇

```
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
```

Kotlin

```
@Configuration
@Profile("default")
class DefaultDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}
```

爪哇

```
@Configuration
public class TransferServiceConfig {

    @Autowired DataSource dataSource;

    @Bean
    public TransferService transferService() {
        return new DefaultTransferService(accountRepository(), feePolicy());
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public FeePolicy feePolicy() {
        return new ZeroFeePolicy();
    }
}
```

Kotlin

```
@Configuration
class TransferServiceConfig {

    @Autowired
    lateinit var dataSource: DataSource

    @Bean
    fun transferService(): TransferService {
        return DefaultTransferService(accountRepository(), feePolicy())
    }

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }

    @Bean
    fun feePolicy(): FeePolicy {
        return ZeroFeePolicy()
    }
}
```

爪哇

```
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
```

Kotlin

```
@SpringJUnitConfig(
        TransferServiceConfig::class,
        StandaloneDataConfig::class,
        JndiDataConfig::class,
        DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}
```

在这个变体中,我们将 XML 配置拆分为四个独立的`@Configuration`类:

* `TransferServiceConfig`:通过使用`@Autowired`通过依赖注入获得`dataSource`

* `StandaloneDataConfig`:为适合于开发人员测试的嵌入式数据库定义`dataSource`

* `JndiDataConfig`:定义在生产环境中从 JNDI 检索的`dataSource`

* `DefaultDataConfig`:为默认的嵌入式数据库定义一个`dataSource`,以防没有配置文件处于活动状态。

与基于 XML 的配置示例一样,我们仍然使用`TransferServiceTest`注释`@ActiveProfiles("dev")`,但这次我们使用`@ContextConfiguration`注释来指定所有四个配置类。测试类本身的主体完全保持不变。

通常的情况是,在一个给定的项目中,跨多个测试类使用一组配置文件。因此,为了避免重复`@ActiveProfiles`注释的声明,可以在基类上声明`@ActiveProfiles`一次,子类自动从基类继承`@ActiveProfiles`配置。在下面的示例中,`@ActiveProfiles`的声明(以及其他注释)已移动到一个抽象超类`AbstractIntegrationTest`:

|   |在 Spring Framework5.3 中,测试配置也可以从包含<br/>类中继承。有关详细信息,请参见[`@Nested`测试类配置]。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

爪哇

```
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
```

Kotlin

```
@SpringJUnitConfig(
        TransferServiceConfig::class,
        StandaloneDataConfig::class,
        JndiDataConfig::class,
        DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
```

爪哇

```
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}
```

Kotlin

```
// "dev" profile inherited from superclass
class TransferServiceTest : AbstractIntegrationTest() {

    @Autowired
    lateinit var transferService: TransferService

    @Test
    fun testTransferService() {
        // test the transferService
    }
}
```

`@ActiveProfiles`还支持一个`inheritProfiles`属性,该属性可用于禁用活动配置文件的继承,如下例所示:

爪哇

```
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
    // test body
}
```

Kotlin

```
// "dev" profile overridden with "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
    // test body
}
```

此外,有时需要以编程方式而不是声明式地解决测试的活动配置文件——例如,基于:

* 当前的操作系统。

* 测试是否在持续集成构建服务器上运行。

* 某些环境变量的存在。

* 自定义类级注释的存在。

* 其他问题。

要以编程方式解析活动 Bean 定义配置文件,你可以实现一个自定义的`ActiveProfilesResolver`,并使用`resolver``resolver`属性对其进行注册。有关更多信息,请参见相应的[javadoc](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/test/context/ActiveProfilesResolver.html)。下面的示例演示如何实现和注册自定义`OperatingSystemActiveProfilesResolver`:

爪哇

```
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver.class,
        inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
```

Kotlin

```
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver::class,
        inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
    // test body
}
```

爪哇

```
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

    @Override
    public String[] resolve(Class<?> testClass) {
        String profile = ...;
        // determine the value of profile based on the operating system
        return new String[] {profile};
    }
}
```

Kotlin

```
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {

    override fun resolve(testClass: Class<*>): Array<String> {
        val profile: String = ...
        // determine the value of profile based on the operating system
        return arrayOf(profile)
    }
}
```

##### 具有测试属性源的上下文配置

Spring 框架对具有属性源层次结构的环境的概念具有一流的支持,并且你可以使用特定于测试的属性源来配置集成测试。与`@Configuration`类上使用的`@PropertySource`注释相反,你可以在测试类上声明`@TestPropertySource`注释,以声明测试属性文件或内联属性的资源位置。这些测试属性源被添加到`PropertySources`中的`Environment`中的`ApplicationContext`集合中,用于为带注释的集成测试加载`ApplicationContext`

|   |你可以使用`@TestPropertySource``SmartContextLoader`SPI 的任何实现一起使用`@TestPropertySource`,但是`@TestPropertySource`不支持与较早的`ContextLoader`SPI 的实现一起使用<br/>`SmartContextLoader`通过`getPropertySourceLocations()`中的<gt r=“1495”和<gt=“1494”/>方法访问合并的测试属性源值<br/>。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### 声明测试属性源

可以使用`locations``value`的属性配置测试属性文件。

支持传统的和基于 XML 的属性文件格式——例如,`"classpath:/com/example/test.properties"``"file:///path/to/file.xml"`

每个路径被解释为 Spring `Resource`。普通路径(例如,`"test.properties"`)被视为与定义测试类的包相关的 Classpath 资源。以斜杠开头的路径被视为绝对 Classpath 资源(例如:`"/org/example/test.xml"`)。通过使用指定的资源协议加载引用 URL 的路径(例如,带有`classpath:``file:``http:`前缀的路径)。不允许使用资源位置通配符(例如`***/**.properties`):每个位置必须精确地计算一个`.properties``.xml`资源。

下面的示例使用了一个测试属性文件:

爪哇

```
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|指定具有绝对路径的属性文件。|
|-----|---------------------------------------------------|

Kotlin

```
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|指定具有绝对路径的属性文件。|
|-----|---------------------------------------------------|

你可以使用`@TestPropertySource``properties`属性,以键-值对的形式配置内联属性,如下一个示例所示。将所有键值对添加到附件`Environment`中,作为具有最高优先级的单个测试`PropertySource`

所支持的键-值对语法与为 爪哇 属性文件中的条目定义的语法相同:

* `key=value`

* `key:value`

* `key value`

下面的示例设置了两个内联属性:

爪哇

```
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|使用键值语法的两种变体设置两个属性。|
|-----|-----------------------------------------------------------------------|

Kotlin

```
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
    // class body...
}
```

|**1**|使用键值语法的两种变体设置两个属性。|
|-----|-----------------------------------------------------------------------|

|   |在 Spring Framework5.2 中,`@TestPropertySource`可以用作*可重复注释*<br/>这意味着可以在单个<br/>测试类上有多个`@TestPropertySource`的声明,随着`locations``properties`后面的`@TestPropertySource`注释覆盖了前面的`@TestPropertySource`注释,<br/>此外,你可以在一个测试类上声明多个组合注释,每个组合注释<br/>meta 注释为`@TestPropertySource`,并且所有那些`@TestPropertySource`声明都将贡献给你的测试属性源。<br/><br/>直接呈现`@TestPropertySource`注释总是优先于<br/>meta-present`@TestPropertySource`注释。换句话说,`locations``properties`来自直接存在的`@TestPropertySource`注释将覆盖`locations``properties`来自`@TestPropertySource`用作元注释的注释。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### 默认属性文件检测

如果`@TestPropertySource`被声明为空注释(即,对于`locations``properties`属性没有显式的值),则尝试检测相对于声明该注释的类的默认属性文件。例如,如果带注释的测试类是`com.example.MyTest`,那么对应的默认属性文件是`classpath:com/example/MyTest.properties`。如果无法检测到默认值,则抛出一个`IllegalStateException`

###### 优先权

测试属性的优先级高于在操作系统环境、Java 系统属性或应用程序通过使用`@PropertySource`或编程方式声明性地添加的属性源中定义的属性。因此,可以使用测试属性来选择性地覆盖从系统和应用程序属性源加载的属性。此外,与从资源位置加载的属性相比,内联属性具有更高的优先级。但是,请注意,通过[`@DynamicPropertySource`]注册的属性(#testcontext-ctx-management-dynamic-property-sources)比通过`@TestPropertySource`加载的属性具有更高的优先级。

在下一个示例中,`timezone``port`属性以及`"/test.properties"`中定义的任何属性覆盖了在系统和应用程序属性源中定义的同名属性。此外,如果`"/test.properties"`文件为`timezone``port`属性定义条目,则这些条目将被使用`properties`属性声明的内联属性覆盖。下面的示例展示了如何在文件和内联中指定属性:

Java

```
@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
    // class body...
}
```

Kotlin

```
@ContextConfiguration
@TestPropertySource("/test.properties",
        properties = ["timezone = GMT", "port: 4242"]
)
class MyIntegrationTests {
    // class body...
}
```

###### 继承和重写测试属性源

`@TestPropertySource`支持布尔`inheritLocations``inheritProperties`属性,这些属性表示超类声明的属性文件和内联属性的资源位置是否应该被继承。这两个标志的默认值都是`true`。这意味着测试类继承了由任何超类声明的位置和内联属性。具体地说,测试类的位置和内联属性被追加到超类声明的位置和内联属性之后。因此,子类可以选择扩展位置和内联属性。请注意,后面出现的属性是与前面出现的相同名称的 shadow(即覆盖)属性。此外,前面提到的优先规则也适用于继承的测试属性源。

如果`inheritLocations``inheritProperties`中的`inheritProperties`属性设置为`false`,则位置或内联属性分别用于测试类的影子和有效地替换由超类定义的配置。

|   |在 Spring Framework5.3 中,测试配置也可以从包含<br/>类中继承。有关详细信息,请参见[`@Nested`测试类配置]。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在下一个示例中,只使用`base.properties`文件作为测试属性源加载`ApplicationContext`for`BaseTest`。相比之下,`ApplicationContext``ExtendedTest`是通过使用`base.properties``extended.properties`文件作为测试属性源位置加载的。下面的示例展示了如何通过使用`properties`文件在子类和其超类中定义属性:

Java

```
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
```

Kotlin

```
@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest : BaseTest() {
    // ...
}
```

在下一个示例中,只使用内联的`key1`属性加载`ApplicationContext`for`BaseTest`。与此相反,`ApplicationContext``ExtendedTest`是通过使用内联的`key1``key2`属性加载的。下面的示例展示了如何通过使用内联属性在子类和其超类中定义属性:

Java

```
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
```

Kotlin

```
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
    // ...
}

@TestPropertySource(properties = ["key2 = value2"])
@ContextConfiguration
class ExtendedTest : BaseTest() {
    // ...
}
```

##### 具有动态属性源的上下文配置

从 Spring Framework5.2.5 开始,TestContext 框架通过`@DynamicPropertySource`注释为*动态*属性提供支持。此注释可用于集成测试,这些测试需要在`Environment`中的`PropertySources`集合中添加具有动态值的属性,用于为集成测试加载`ApplicationContext`

|   |`@DynamicPropertySource`注释及其支持的基础结构是<br/>,最初的设计目的是允许基于[测试容器](https://www.testcontainers.org/)的测试中的属性很容易地暴露在<br/> Spring 集成测试中。然而,这个特性也可以用于任何形式的<br/>外部资源,其生命周期被维护在测试的`ApplicationContext`之外。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

与在类级别应用的[`@TestPropertySource`](#testcontext-ctx-management-property-sources)注释不同,`@DynamicPropertySource`必须应用到一个`static`方法,该方法接受一个`DynamicPropertyRegistry`参数,该参数用于将*名称-值*对添加到`Environment`。值是动态的,并通过`Supplier`提供,只有在解析属性时才调用该属性。通常,方法引用用于提供值,如下面的示例所示,该示例使用 TestContainers 项目来管理 Spring `ApplicationContext`之外的 Redis 容器。托管 Redis 容器的 IP 地址和端口可通过`redis.host``redis.port`属性提供给测试`ApplicationContext`中的组件。这些属性可以通过 Spring 的`Environment`抽象进行访问,也可以直接注入到 Spring 管理的组件中——例如,分别通过`@Value("${redis.host}")``@Value("${redis.port}")`

|   |如果在基类中使用`@DynamicPropertySource`,并发现子类<br/>中的测试失败,因为子类之间的动态属性发生了变化,那么你可能需要用[<br/>](# Spring-testing-annotation-dirtiescontext)对基类进行注释,以<br/>确保每个子类都具有正确的动态`ApplicationContext`属性。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Java

```
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

    @Container
    static RedisContainer redis = new RedisContainer();

    @DynamicPropertySource
    static void redisProperties(DynamicPropertyRegistry registry) {
        registry.add("redis.host", redis::getContainerIpAddress);
        registry.add("redis.port", redis::getMappedPort);
    }

    // tests ...

}
```

Kotlin

```
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

    companion object {

        @Container
        @JvmStatic
        val redis: RedisContainer = RedisContainer()

        @DynamicPropertySource
        @JvmStatic
        fun redisProperties(registry: DynamicPropertyRegistry) {
            registry.add("redis.host", redis::getContainerIpAddress)
            registry.add("redis.port", redis::getMappedPort)
        }
    }

    // tests ...

}
```

###### 优先权

动态属性的优先级高于从`@TestPropertySource`、操作系统的环境、Java 系统属性或应用程序通过使用`@PropertySource`或编程方式声明性地添加的属性源加载的属性。因此,动态属性可以用于选择性地覆盖通过`@TestPropertySource`、系统属性源和应用程序属性源加载的属性。

##### 装入`WebApplicationContext`

要指示 TestContext 框架加载`WebApplicationContext`而不是标准的`ApplicationContext`,你可以用`@WebAppConfiguration`注释相应的测试类。

测试类上的`@WebAppConfiguration`指示 TestContext 框架应该为集成测试加载`WebApplicationContext`。在后台,TCF 确保创建了`MockServletContext`并将其提供给测试的 WAC。默认情况下,`MockServletContext`的基本资源路径设置为`src/main/webapp`。这被解释为与你的 JVM 的根相关的路径(通常是你的项目的路径)。如果你熟悉 Maven 项目中 Web 应用程序的目录结构,那么你就知道`src/main/webapp`是你的 WAR 的根目录的默认位置。如果需要重写此默认值,则可以提供`@WebAppConfiguration`注释的替代路径(例如,`@WebAppConfiguration("src/test/webapp")`)。如果希望从 Classpath 而不是文件系统引用基本资源路径,则可以使用 Spring 的`classpath:`前缀。

请注意, Spring 对`WebApplicationContext`实现的测试支持与其对标准`ApplicationContext`实现的支持是相同的。当使用`WebApplicationContext`进行测试时,你可以使用`@ContextConfiguration`声明 XML 配置文件、Groovy 脚本或`@Configuration`类。你还可以自由地使用任何其他测试注释,例如`@ActiveProfiles``@TestExecutionListeners``@Sql``@Rollback`和其他注释。

本节中的其余示例显示了用于加载`WebApplicationContext`的各种配置选项中的一些。下面的示例展示了 TestContext 框架对配置之上的约定的支持:

Java

```
@ExtendWith(SpringExtension.class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
    //...
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
    //...
}
```

如果使用`@WebAppConfiguration`注释测试类而不指定资源基路径,则资源路径实际上默认为`file:src/main/webapp`。类似地,如果在不指定资源`locations`的情况下声明`@ContextConfiguration`,组件`classes`,或上下文`initializers`, Spring 试图通过使用约定(即`WacTests-context.xml`在与`WacTests`类相同的包中或静态嵌套`@Configuration`类)来检测配置的存在。

下面的示例显示了如何显式地声明具有`@WebAppConfiguration`的资源库路径和具有`@ContextConfiguration`的 XML 资源位置:

Java

```
@ExtendWith(SpringExtension.class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}
```

这里需要注意的重要一点是,这两种注释的路径具有不同的语义。默认情况下,`@WebAppConfiguration`资源路径是基于文件系统的,而`@ContextConfiguration`资源位置是基于 Classpath 的。

下面的示例表明,我们可以通过指定 Spring 资源前缀来覆盖这两种注释的默认资源语义:

Java

```
@ExtendWith(SpringExtension.class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //...
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //...
}
```

将本例中的注释与前面的示例进行对比。

[]()使用 Web 模拟

为了提供全面的 Web 测试支持,TestContext 框架有一个`ServletTestExecutionListener`,默认情况下启用。当针对`WebApplicationContext`进行测试时,此[`TestExecutionListener`](#testcontext-key-abstractions)通过在每个测试方法之前使用 Spring Web 的`RequestContextHolder`设置默认的线程-本地状态,并基于配置有`@WebAppConfiguration`的基本资源路径创建`MockHttpServletResponse``ServletWebRequest``ServletTestExecutionListener`还确保`MockHttpServletResponse``ServletWebRequest`可以被注入到测试实例中,并且,一旦测试完成,它将清理线程本地状态。

一旦为测试加载了`WebApplicationContext`,你可能会发现需要与 Web 模拟交互——例如,在调用 Web 组件后设置测试 fixture 或执行断言。下面的示例展示了哪些模拟可以自动连接到你的测试实例中。请注意,`WebApplicationContext``MockServletContext`都是跨测试套件缓存的,而其他模拟是由`ServletTestExecutionListener`按每个测试方法管理的。

Java

```
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}
```

Kotlin

```
@SpringJUnitWebConfig
class WacTests {

    @Autowired
    lateinit var wac: WebApplicationContext // cached

    @Autowired
    lateinit var servletContext: MockServletContext // cached

    @Autowired
    lateinit var session: MockHttpSession

    @Autowired
    lateinit var request: MockHttpServletRequest

    @Autowired
    lateinit var response: MockHttpServletResponse

    @Autowired
    lateinit var webRequest: ServletWebRequest

    //...
}
```

##### 上下文缓存

一旦 TestContext Framework 为测试加载`ApplicationContext`(或`WebApplicationContext`),该上下文将被缓存,并在所有随后的测试中重用,这些测试在相同的测试套件中声明相同的唯一上下文配置。要理解缓存的工作原理,重要的是要理解“唯一”和“测试套件”的含义。

`ApplicationContext`可以通过用于加载它的配置参数的组合来唯一标识。因此,配置参数的唯一组合被用来生成一个关键字,在该关键字下缓存上下文。TestContext 框架使用以下配置参数来构建上下文缓存键:

* `locations`(来自`@ContextConfiguration`

* `classes`(来自`@ContextConfiguration`

* `contextInitializerClasses`(来自`@ContextConfiguration`

* `contextCustomizers`(来自`ContextCustomizerFactory`)–这包括`@DynamicPropertySource`方法以及来自 Spring Boot 的测试支持的各种功能,例如`@MockBean``@SpyBean`

* `contextLoader`(来自`@ContextConfiguration`

* `parent`(来自`@ContextHierarchy`

* `activeProfiles`(来自`@ActiveProfiles`

* `propertySourceLocations`(来自`@TestPropertySource`

* `propertySourceProperties`(来自`@TestPropertySource`

* `resourceBasePath`(来自`@WebAppConfiguration`

例如,如果`TestClassA``locations`(或`value`)属性的`{"app-config.xml", "test-config.xml"}`指定`{"app-config.xml", "test-config.xml"}`,则 TestContext 框架加载相应的`ApplicationContext`,并将其存储在`static`上下文缓存中,该密钥仅基于这些位置。因此,如果`TestClassB`也为其位置定义了`{"app-config.xml", "test-config.xml"}`(通过继承显式或隐式地),但没有定义`@WebAppConfiguration`,则不同的`ContextLoader`、不同的活动配置文件、不同的上下文初始化器、不同的测试属性源或不同的父上下文,那么相同的`ApplicationContext`由两个测试类共享。这意味着加载应用程序上下文的设置成本仅发生一次(每个测试套件),并且随后的测试执行速度要快得多。

|   |测试套件和分叉进程<br/><br/> Spring TestContext 框架将应用程序上下文存储在静态缓存中。这<br/>意味着上下文实际上存储在`static`变量中。换句话说,如果<br/>测试在单独的进程中运行,则在每个测试<br/>执行之间清除静态缓存,这有效地禁用了缓存机制。<br/><br/>要受益于缓存机制,所有测试都必须在相同的进程或测试<br/>套件中运行。这可以通过在 IDE 中作为一个组执行所有测试来实现。类似地,<br/>在使用 Ant、 Maven 或 Gradle 之类的构建框架执行测试时,重要的是要确保构建框架不会在测试之间分叉。例如,<br/>如果将 Maven surefire 插件的[`forkMode`](https:// Maven.apache.org/plugins/ Maven-surefire-plugin/test-mojo.html#forkmode)设置为`always``pertest`,则 TestContext 框架<br/>不能在测试类之间运行<gtcache 上下文,结果是构建过程的速度明显慢得多。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

上下文缓存的大小是有界的,默认的最大大小为 32。每当达到最大大小时,就会使用最近使用的驱逐策略来驱逐和关闭陈旧的上下文。通过设置名为`spring.test.context.cache.maxSize`的 JVM 系统属性,可以从命令行或构建脚本配置最大大小。作为一种选择,你可以通过[`SpringProperties`](acception.html#acception- Spring-properties)机制设置相同的属性。

由于在给定的测试套件中加载大量的应用程序上下文可能会导致该套件花费不必要的长时间运行,因此准确地知道加载和缓存了多少上下文通常是有益的。要查看底层上下文缓存的统计信息,可以将`org.springframework.test.context.cache`日志类别的日志级别设置为`DEBUG`

在不太可能的情况下,测试会破坏应用程序上下文并需要重新加载(例如,通过修改 Bean 定义或应用程序对象的状态),你可以使用`@DirtiesContext`对测试类或测试方法进行注释(请参见[Spring Testing Annotations](#spring-testing-annotation-dirtiescontext)中对`@DirtiesContext`的讨论)。这指示 Spring 在运行需要相同应用程序上下文的下一个测试之前,从缓存中删除上下文并重建应用程序上下文。请注意,对`@DirtiesContext`注释的支持是由`DirtiesContextBeforeModesTestExecutionListener``DirtiesContextTestExecutionListener`提供的,这是默认启用的。

|   |ApplicationContext 生命周期和控制台日志<br/><br/>当需要调试使用 Spring TestContext 框架执行的测试时,可以对<br/>控制台输出进行有用的分析(即输出到`SYSOUT``SYSERR`流)。一些构建工具和 IDE 能够将控制台输出与给定的<br/>测试相关联;但是,某些控制台输出不能很容易地与给定的测试相关联。<br/><br/>关于由 Spring 框架本身触发的控制台日志记录或由<br/>中注册的组件`ApplicationContext`触发的控制台日志记录,理解由 Spring TestContext 框架在<br/>测试套件中加载的`ApplicationContext`测试的生命周期非常重要,<br/><br/>测试的`ApplicationContext`通常是在准备测试<br/>类的实例时加载的,例如,要在测试实例的`@Autowired`字段中执行依赖项注入。这意味着在<br/>初始化`ApplicationContext`期间触发的任何控制台日志记录通常不能与<br/>单独的测试方法关联。然而,如果在执行根据[`@DirtiesContext`](# Spring-testing-annotation-dirtiescontext)语义的测试方法之前立即关闭了上下文,则在执行<br/>测试方法之前将加载一个新的上下文实例。在后一种情况下,IDE 或构建工具可能会将<br/>控制台日志记录与单独的测试方法关联起来。<br/><br/>`ApplicationContext`对于测试可以通过以下场景中的一种来关闭。<br/><br/>* 上下文是根据`@DirtiesContext`语义关闭的。<br/><br/>* 上下文是关闭的因为它已经根据 LRU 驱逐策略从缓存<br/>中自动驱逐了。<br/><br/>* 当 JVM 关闭钩子时,上下文将通过 JVM 关闭钩子关闭。对于测试套件<br/>终止。<br/><br/>如果上下文是根据`@DirtiesContext`语义关闭的,在特定的测试<br/>方法之后,IDE 或构建工具可能会将控制台日志记录与<br/>单独的测试方法关联起来。如果上下文根据`@DirtiesContext`语义<br/>关闭了一个测试类,则在关闭`ApplicationContext`期间触发的任何控制台日志记录都不能与单独的测试方法关联。类似地,在关机阶段期间通过 JVM 关机钩子触发的任何<br/>控制台日志记录都不能与单独的测试方法相关联。<br/><br/>当 Spring `ApplicationContext`通过 JVM 关机钩子关闭时,在关机阶段执行的回调<br/>在名为`SpringContextShutdownHook`的线程上执行。因此,<br/>如果你希望禁用在`ApplicationContext`关闭<br/>时触发的控制台日志记录,则可以通过 JVM 关闭钩子,在你的日志<br/>框架中注册一个自定义过滤器,该框架允许你忽略由该线程发起的任何日志记录。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### 上下文层次结构

在编写依赖加载的 Spring `ApplicationContext`的集成测试时,通常只需针对单个上下文进行测试。然而,有时对`ApplicationContext`实例的层次结构进行测试是有益的,甚至是必要的。例如,如果你正在开发 Spring MVC Web 应用程序,则通常有一个根`WebApplicationContext`由 Spring 的`ContextLoaderListener`加载,而一个子`WebApplicationContext`由 Spring 的`DispatcherServlet`加载。这将导致一个父子上下文层次结构,其中共享组件和基础设施配置在根上下文中声明,并由特定于 Web 的组件在子上下文中使用。另一个用例可以在 Spring 批处理应用程序中找到,在该应用程序中,通常有一个父上下文为共享批处理基础设施提供配置,以及一个子上下文为特定批处理作业的配置提供配置。

可以在单个测试类上或在测试类层次结构中,通过使用`@ContextHierarchy`注释声明上下文配置来编写使用上下文层次结构的集成测试。如果在测试类层次结构中的多个类上声明了上下文层次结构,则还可以合并或覆盖上下文层次结构中特定的命名级别的上下文配置。当合并层次结构中给定级别的配置时,配置资源类型(即 XML 配置文件或组件类)必须是一致的。否则,在上下文层次结构中使用不同的资源类型配置不同的级别是完全可以接受的。

本节中剩余的基于 JUnit Jupiter 的示例展示了需要使用上下文层次结构的集成测试的常见配置场景。

具有上下文层次结构的单个测试类

`ControllerIntegrationTests`通过声明由两个级别组成的上下文层次结构,表示 Spring MVC Web 应用程序的典型集成测试场景,一个用于根`WebApplicationContext`(通过使用`TestAppConfig``@Configuration`类加载),一个用于调度器 Servlet `WebApplicationContext`(通过使用`WebConfig`类加载)。自动连接到测试实例的`WebApplicationContext`是子上下文(即层次结构中最低的上下文)。下面的清单展示了这个配置场景:

Java

```
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {

    @Autowired
    WebApplicationContext wac;

    // ...
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextHierarchy(
    ContextConfiguration(classes = [TestAppConfig::class]),
    ContextConfiguration(classes = [WebConfig::class]))
class ControllerIntegrationTests {

    @Autowired
    lateinit var wac: WebApplicationContext

    // ...
}
```

具有隐式父上下文的类层次结构

本例中的测试类在测试类层次结构中定义了上下文层次结构。`AbstractWebTests`在 Spring 驱动的 Web 应用程序中声明根`WebApplicationContext`的配置。但请注意,`AbstractWebTests`并不声明`@ContextHierarchy`。因此,`AbstractWebTests`的子类可以选择性地参与上下文层次结构或遵循`@ContextConfiguration`的标准语义。`SoapWebServiceTests``RestWebServiceTests`都扩展了`AbstractWebTests`,并通过使用`@ContextHierarchy`定义了上下文层次结构。结果是加载了三个应用程序上下文(每个`@ContextConfiguration`的声明有一个),并且基于`AbstractWebTests`中的配置加载的应用程序上下文被设置为为为具体子类加载的每个上下文的父上下文。下面的清单展示了这个配置场景:

爪哇

```
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
abstract class AbstractWebTests

@ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml"))
class SoapWebServiceTests : AbstractWebTests()

@ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml"))
class RestWebServiceTests : AbstractWebTests()
```

具有合并上下文层次结构配置的类层次结构

本例中的类展示了命名层次结构级别的使用,以便合并上下文层次结构中特定级别的配置。`BaseTests`定义了层次结构中的两个级别,`parent``child``ExtendedTests`扩展了`BaseTests`,并指示 Spring TestContext 框架合并`child`层次结构级别的上下文配置,方法是确保`@ContextConfiguration`中的`name`属性中声明的名称都是`child`。结果是加载了三个应用程序上下文:一个用于`/app-config.xml`,一个用于`/user-config.xml`,一个用于`{"/user-config.xml", "/order-config.xml"}`。与前面的示例一样,从`/app-config.xml`加载的应用程序上下文被设置为从`/user-config.xml``{"/user-config.xml", "/order-config.xml"}`加载的上下文的父上下文。下面的清单展示了这个配置场景:

爪哇

```
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
    ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
    ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}

@ContextHierarchy(
    ContextConfiguration(name = "child", locations = ["/order-config.xml"])
)
class ExtendedTests : BaseTests() {}
```

具有重写的上下文层次结构配置的类层次结构

与前面的示例相反,这个示例演示了如何通过将`@ContextConfiguration`中的`inheritLocations`标志设置为`false`来覆盖上下文层次结构中给定命名级别的配置。因此,`ExtendedTests`的应用程序上下文仅从`/test-user-config.xml`加载,并将其父集设置为从`/app-config.xml`加载的上下文。下面的清单展示了这个配置场景:

爪哇

```
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(
        name = "child",
        locations = "/test-user-config.xml",
        inheritLocations = false
))
class ExtendedTests extends BaseTests {}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
    ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
    ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}

@ContextHierarchy(
        ContextConfiguration(
                name = "child",
                locations = ["/test-user-config.xml"],
                inheritLocations = false
        ))
class ExtendedTests : BaseTests() {}
```

|   |在上下文层次结构<br/><br/>中玷污上下文如果你在一个测试中使用`@DirtiesContext`,该测试的上下文被配置为<br/>上下文层次结构的一部分,那么你可以使用`hierarchyMode`标志来控制如何清除上下文缓存<br/>。有关更多详细信息,请参见`@DirtiesContext`中对[Spring Testing Annotations](#spring-testing-annotation-dirtiescontext)的讨论和[`@DirtiesContext`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/annotation/dirtiescontext.html)的讨论。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 3.5.7.测试夹具的依赖注入

当你使用`DependencyInjectionTestExecutionListener`(它是默认配置的)时,你的测试实例的依赖项是从你配置了`@ContextConfiguration`或相关注释的应用程序上下文中的 bean 注入的。你可以使用 setter 注入、字段注入,或者两者都使用,这取决于你选择了哪些注释,以及是否将它们放置在 setter 方法或字段上。如果你正在使用 JUnit Jupiter,你也可以选择使用构造函数注入(参见[Dependency Injection with`SpringExtension`](#TestContext-JUnit-Jupiter-DI))。为了与 Spring 的基于注释的注入支持保持一致,还可以使用 Spring 的`@Autowired`注释或来自 JSR-330 的`@Inject`注释进行字段和 setter 注入。

|   |对于 JUnit Jupiter 以外的测试框架,TestContext 框架不<br/>参与测试类的实例化。因此,对于构造函数使用`@Autowired``@Inject`对测试类没有影响。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |尽管在生产代码中不鼓励现场注入,但在测试代码中,现场注入实际上是<br/>非常自然的。这种差异的基本原理是,你将永远不会直接实例化你的测试类<br/>。因此,不需要能够在测试类上调用<br/>构造函数或 setter 方法。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

因为`@Autowired`用于执行[按类型自动布线](core.html#beans-factory-autowire),所以如果你有多个相同类型的 Bean 定义,那么对于那些特定的 bean,你不能依赖这种方法。在这种情况下,可以将`@Autowired``@Qualifier`结合使用。你还可以选择将`@Inject``@Named`结合使用。或者,如果你的测试类具有对其`ApplicationContext`的访问权限,则可以使用(例如)对`applicationContext.getBean("titleRepository", TitleRepository.class)`的调用来执行显式查找。

如果不希望将依赖注入应用于测试实例,请不要使用`@Autowired``@Inject`注释域或 setter 方法。或者,可以通过显式地使用`@TestExecutionListeners`配置类并从侦听器列表中省略`DependencyInjectionTestExecutionListener.class`来完全禁用依赖注入。

考虑测试`HibernateTitleRepository`类的场景,如[Goals](#integration-testing-goals)部分所概述的那样。接下来的两个代码清单演示了`@Autowired`在字段和 setter 方法上的使用。在所有示例代码列表之后,将介绍应用程序上下文配置。

|   |以下代码列表中的依赖注入行为不是 JUnit<br/>Jupiter 特有的。相同的 DI 技术可以与任何支持的测试<br/>框架结合使用。<br/><br/>以下示例调用静态断言方法,例如`assertNotNull()`<br/>,但不预先处理与`Assertions`的调用。在这种情况下,假设<br/>方法是通过`import static`声明正确导入的,该声明在<br/>示例中未显示。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

第一个代码清单显示了一个基于 JUnit Jupiter 的测试类实现,它使用`@Autowired`进行字段注入:

爪哇

```
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    HibernateTitleRepository titleRepository;

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    lateinit var titleRepository: HibernateTitleRepository

    @Test
    fun findById() {
        val title = titleRepository.findById(10)
        assertNotNull(title)
    }
}
```

或者,你可以将类配置为使用`@Autowired`进行 setter 注入,如下所示:

爪哇

```
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    HibernateTitleRepository titleRepository;

    @Autowired
    void setTitleRepository(HibernateTitleRepository titleRepository) {
        this.titleRepository = titleRepository;
    }

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}
```

Kotlin

```
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    lateinit var titleRepository: HibernateTitleRepository

    @Autowired
    fun setTitleRepository(titleRepository: HibernateTitleRepository) {
        this.titleRepository = titleRepository
    }

    @Test
    fun findById() {
        val title = titleRepository.findById(10)
        assertNotNull(title)
    }
}
```

前面的代码列表使用由`@ContextConfiguration`注释(即`repository-config.xml`)引用的相同 XML 上下文文件。以下显示了此配置:

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
    <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <!-- configuration elided for brevity -->
    </bean>

</beans>
```

|   |如果你是从 Spring 提供的测试基类进行扩展的,而测试基类恰好在它的一个 setter 方法上使用`@Autowired`,那么你可能在应用程序上下文中定义了多个受影响的<br/>类型的 bean(例如,多个`DataSource`bean)。在<br/>这样的情况下,可以重写 setter 方法,并使用`@Qualifier`注释来表示特定的目标 Bean,如下所示(但也要确保将其委托给超类中重写的<br/>方法):<br/><br/>java<br/><br/><br/><br/><br/><br/><><1893"r="gt="1914"/><gt15"/>指定的限定词表示指定的限定符[lt=”lt=“lt=”lt=“lt=”lt=“lt17”/><lt=“lt=”lt="lt= 它的值与相应的`<bean>`定义中的`<qualifier>`声明相匹配。 Bean name<br/>被用作回退限定符值,因此你也可以有效地通过这里的名称指向特定的<br/> Bean(如前面所示,假设`myDataSource`是 Bean `id`)。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 3.5.8.测试请求和会话范围的 bean

Spring 从早期开始就支持[请求和会话范围的 bean](core.html#beans-factory-scopes-other),你可以通过以下步骤来测试你的请求范围和会话范围的 bean:

* 通过使用`@WebAppConfiguration`注释测试类,确保为测试加载了`WebApplicationContext`

* 将模拟请求或会话注入到你的测试实例中,并根据需要准备测试装置。

* 调用从配置的`WebApplicationContext`中检索到的 Web 组件(带有依赖项注入)。

* 对模拟执行断言。

下一个代码片段显示了登录用例的 XML 配置。请注意,`userService` Bean 对请求作用域`loginAction` Bean 具有依赖关系。而且,`LoginAction`是通过使用[Spel 表达式](core.html#expressions)实例化的,后者从当前的 HTTP 请求中检索用户名和密码。在测试中,我们希望通过 TestContext 框架管理的模拟来配置这些请求参数。下面的清单显示了这个用例的配置:

请求范围 Bean 配置

```
<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:loginAction-ref="loginAction"/>

    <bean id="loginAction" class="com.example.LoginAction"
            c:username="#{request.getParameter('user')}"
            c:password="#{request.getParameter('pswd')}"
            scope="request">
        <aop:scoped-proxy/>
    </bean>

</beans>
```

`RequestScopedBeanTests`中,我们将`UserService`(即被测试的对象)和`MockHttpServletRequest`注入到我们的测试实例中。在我们的`requestScope()`测试方法中,我们通过在提供的`MockHttpServletRequest`中设置请求参数来设置测试夹具。当在我们的`userService`上调用`loginUser()`方法时,我们确信用户服务可以访问当前`MockHttpServletRequest`的请求范围`loginAction`(即我们只设置参数的那个)。然后,我们可以根据用户名和密码的已知输入,针对结果执行断言。下面的清单展示了如何做到这一点:

爪哇

```
@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

    @Test
    void requestScope() {
        request.setParameter("user", "enigma");
        request.setParameter("pswd", "$pr!ng");

        LoginResults results = userService.loginUser();
        // assert results
    }
}
```

Kotlin

```
@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired lateinit var userService: UserService
    @Autowired lateinit var request: MockHttpServletRequest

    @Test
    fun requestScope() {
        request.setParameter("user", "enigma")
        request.setParameter("pswd", "\$pr!ng")

        val results = userService.loginUser()
        // assert results
    }
}
```

下面的代码片段与我们前面看到的请求范围的代码片段类似 Bean。然而,这一次,`userService` Bean 对会话范围`userPreferences` Bean 具有依赖关系。请注意,`UserPreferences` Bean 是通过使用 SPEL 表达式实例化的,该表达式从当前 HTTP 会话中检索主题。在测试中,我们需要在由 TestContext 框架管理的模拟会话中配置一个主题。下面的示例展示了如何做到这一点:

会话范围 Bean 配置

```
<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:userPreferences-ref="userPreferences" />

    <bean id="userPreferences" class="com.example.UserPreferences"
            c:theme="#{session.getAttribute('theme')}"
            scope="session">
        <aop:scoped-proxy/>
    </bean>

</beans>
```

`SessionScopedBeanTests`中,我们将`UserService``MockHttpSession`注入到我们的测试实例中。在我们的`sessionScope()`测试方法中,我们通过在提供的`MockHttpSession`中设置预期的`theme`属性来设置测试夹具。当在我们的`userService`上调用`processUserPreferences()`方法时,我们确信用户服务可以访问当前`MockHttpSession`的会话范围`userPreferences`,并且我们可以根据配置的主题对结果执行断言。下面的示例展示了如何做到这一点:

爪哇

```
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

    @Test
    void sessionScope() throws Exception {
        session.setAttribute("theme", "blue");

        Results results = userService.processUserPreferences();
        // assert results
    }
}
```

Kotlin

```
@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired lateinit var userService: UserService
    @Autowired lateinit var session: MockHttpSession

    @Test
    fun sessionScope() {
        session.setAttribute("theme", "blue")

        val results = userService.processUserPreferences()
        // assert results
    }
}
```

#### 3.5.9.事务管理

在 TestContext 框架中,事务由`TransactionalTestExecutionListener`管理,这是默认配置的,即使你没有在测试类上显式声明`@TestExecutionListeners`。但是,要启用对事务的支持,你必须在`ApplicationContext`中配置一个`PlatformTransactionManager` Bean,它加载了`@ContextConfiguration`语义(更多详细信息将在后面提供)。此外,你必须在测试的类级别或方法级别声明 Spring 的`@Transactional`注释。

##### 测试管理事务

测试管理事务是通过使用`TransactionalTestExecutionListener`或通过使用`TestTransaction`(在后面描述)以声明式方式管理的事务。你不应该将这样的事务与 Spring 管理的事务(那些直接由 Spring 在`ApplicationContext`中为测试加载的事务中管理的事务)或应用程序管理的事务(那些在由测试调用的应用程序代码中以编程方式管理的事务)混淆。 Spring-管理事务和应用程序管理事务通常参与测试管理事务。但是,如果 Spring-managed 或 application-managed 事务配置为除`REQUIRED``SUPPORTS`以外的任何传播类型,则应谨慎使用(有关详细信息,请参见[事务传播](data-access.html#tx-propagation)的讨论)。

|   |在结合 Spring 的测试管理事务使用来自测试框架<br/>的任何形式的抢占超时时时时时<br/>时,必须谨慎。<br/><br/>具体来说, Spring 的测试支持将事务状态绑定到当前线程(通过<br/>a`java.lang.ThreadLocal`变量)*在此之前*调用当前的测试方法。如果<br/>测试框架在新线程中调用当前测试方法以支持<br/>抢占超时,则在当前测试方法内执行的任何操作都将*不是*在测试管理事务中调用<br/>。因此,任何此类操作<br/>的结果都不会被测试管理事务回滚。相反,这样的动作<br/>将被承诺给持久性存储——例如,关系数据库——甚至<br/>,尽管测试管理事务被适当地回滚了 Spring。<br/><br/>可能发生这种情况的情况包括但不限于以下情况。<br/><br/>*Junit4 的`@Test(timeout = …​)`支持而`TimeOut`rule<br/><br/>*Junit Jupiter 的`assertTimeoutPreemptively(…​)`方法在`org.junit.jupiter.api.Assertions`class<br/><br/>*testng 的`@Test(timeOut = …​)`支持|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### 启用和禁用事务

`@Transactional`注释测试方法会导致测试在事务中运行,默认情况下,该事务在测试完成后会自动回滚。如果一个测试类使用`@Transactional`进行注释,那么这个类层次结构中的每个测试方法都在一个事务中运行。未使用`@Transactional`(在类或方法级别)进行注释的测试方法不在事务中运行。注意,`@Transactional`在测试生命周期方法上不受支持——例如,用 JUnit Jupiter 的`@BeforeAll``@BeforeEach`注释的方法,等等。此外,用`@Transactional`注释但将`propagation`属性设置为`NOT_SUPPORTED``NEVER`的测试不会在事务中运行。

|                 Attribute                  |支持测试管理的事务|
|--------------------------------------------|----------------------------------------------------------------------|
|      `value` and `transactionManager`      |是的|
|               `propagation`                |只支持`Propagation.NOT_SUPPORTED``Propagation.NEVER`|
|                `isolation`                 |无|
|                 `timeout`                  |无|
|                 `readOnly`                 |无|
|  `rollbackFor` and `rollbackForClassName`  |否:用`TestTransaction.flagForRollback()`代替|
|`noRollbackFor` and `noRollbackForClassName`|否:用`TestTransaction.flagForCommit()`代替|

|   |方法级别的生命周期方法——例如,用 JUnit Jupiter 的`@BeforeEach``@AfterEach`注释的方法——在测试管理事务中运行。在另一种<br/>方法上,使用组件级和类级生命周期方法——例如,使用<br/>Junit Jupiter 的`@BeforeAll``@AfterAll`注释的方法,以及使用 Testng 的`@BeforeSuite``@AfterSuite`注释的方法,或者`@AfterClass`—是*不是*<br/>测试管理事务中运行。<br/><br/>如果你需要在<br/>事务中以组件级或类级生命周期方法运行代码,你可能希望将相应的`PlatformTransactionManager`注入到<br/>你的测试类中,然后将其与`TransactionTemplate`用于程序化的<br/>事务管理。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

请注意,[`AbstractTransactionalJUnit4SpringContextTests`](#TestContext-Support-Classes-JUnit4)和[<gtr="2023"/>](#TestContext-Support-Classes-TestNG)是为类级别的事务支持而预先配置的。

下面的示例演示了为基于 Hibernate 的`UserRepository`编写集成测试的常见场景:

爪哇

```
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

    @Autowired
    void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }

    private int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    private void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
```

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {

    @Autowired
    lateinit var repository: HibernateUserRepository

    @Autowired
    lateinit var sessionFactory: SessionFactory

    lateinit var jdbcTemplate: JdbcTemplate

    @Autowired
    fun setDataSource(dataSource: DataSource) {
        this.jdbcTemplate = JdbcTemplate(dataSource)
    }

    @Test
    fun createUser() {
        // track initial state in test database:
        val count = countRowsInTable("user")

        val user = User()
        repository.save(user)

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush()
        assertNumUsers(count + 1)
    }

    private fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }

    private fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}
```

正如在[事务回滚和提交行为](#testcontext-tx-rollback-and-commit-behavior)中所解释的那样,在`createUser()`方法运行后不需要清理数据库,因为对数据库所做的任何更改都会由`TransactionalTestExecutionListener`自动回滚。

##### 事务回滚和提交行为

默认情况下,测试事务将在测试完成后自动回滚;但是,事务提交和回滚行为可以通过`@Commit``@Rollback`注释进行声明性配置。有关更多详细信息,请参见[注释支持](#integration-testing-annotations)部分中的相应条目。

##### 程序化事务管理

可以通过使用`TestTransaction`中的静态方法以编程方式与测试管理的事务交互。例如,可以在测试方法中、方法之前和方法之后使用`TestTransaction`来启动或结束当前的测试管理事务,或者为回滚或提交配置当前的测试管理事务。只要启用`TransactionalTestExecutionListener`,对`TestTransaction`的支持就会自动可用。

下面的示例演示了`TestTransaction`的一些特性。关于[`TestTransaction`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/transaction/testtransaction.html)的更多详细信息,请参见 爪哇doc。

爪哇

```
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2);

        deleteFromTables("user");

        // changes to the database will be committed!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);

        TestTransaction.start();
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}
```

Kotlin

```
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {

    @Test
    fun transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2)

        deleteFromTables("user")

        // changes to the database will be committed!
        TestTransaction.flagForCommit()
        TestTransaction.end()
        assertFalse(TestTransaction.isActive())
        assertNumUsers(0)

        TestTransaction.start()
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected fun assertNumUsers(expected: Int) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
    }
}
```

##### 在事务之外运行代码

有时,你可能需要在事务性测试方法之前或之后运行特定的代码,但要在事务性上下文之外运行,例如,要在运行测试之前验证初始数据库状态,或者在测试运行之后验证预期的事务提交行为(如果测试被配置为提交事务)。`TransactionalTestExecutionListener`支持针对此类场景的`@BeforeTransaction``@AfterTransaction`注释。你可以使用这些注释之一对测试类中的任何`void`方法或测试接口中的任何`void`默认方法进行注释,并且`TransactionalTestExecutionListener`确保你的 before transaction 方法或 after transaction 方法在适当的时间运行。

|   |任何之前的方法(例如用 JUnit Jupiter 的`@BeforeEach`注释的方法)<br/>和任何之后的方法(例如用 JUnit Jupiter 的`@AfterEach`注释的方法)都是<br/>在事务中运行的。此外,对于未配置为在<br/>事务中运行的测试方法,不运行带有`@BeforeTransaction``@AfterTransaction`注释的方法。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### 配置事务管理器

`TransactionalTestExecutionListener`期望在 Spring `ApplicationContext`中定义一个`PlatformTransactionManager`用于测试。如果在测试的`ApplicationContext`中有多个`PlatformTransactionManager`实例,则可以使用`@Transactional("myTxMgr")``@Transactional(transactionManager = "myTxMgr")`声明限定符,或者`TransactionManagementConfigurer`可以通过`@Configuration`类实现。查询[爪哇doc for`TestContextTransactionUtils.retrieveTransactionManager()`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/contexttransactionutils.html#retrivetransactionmanager-org.spramework.test.context.tcontext.tcontext-tcontext-justcontext-java.lang.string-)以获取有关在测试中查找事务管理器的算法的详细信息,该算法用于在测试的<<

##### 演示所有与事务相关的注释

下面的基于 JUnit Jupiter 的示例显示了一个虚拟的集成测试场景,该场景突出显示了所有与事务相关的注释。该示例的目的不是演示最佳实践,而是演示如何使用这些注释。有关更多信息和配置示例,请参见[注释支持](#integration-testing-annotations)小节。[用于`@Sql`的事务管理](#TestContext-Executing-SQL-Declarationly-TX)包含一个附加示例,该示例使用`@Sql`执行带有默认事务回滚语义的声明性 SQL 脚本。下面的示例显示了相关的注释:

爪哇

```
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {

    @BeforeTransaction
    void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @BeforeEach
    void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @AfterEach
    void tearDownWithinTransaction() {
        // run "tear down" logic within the transaction
    }

    @AfterTransaction
    void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}
```

Kotlin

```
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {

    @BeforeTransaction
    fun verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @BeforeEach
    fun setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    fun modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @AfterEach
    fun tearDownWithinTransaction() {
        // run "tear down" logic within the transaction
    }

    @AfterTransaction
    fun verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}
```

|   |在测试 ORM 代码<br/><br/>时避免误报当你测试处理 Hibernate 会话状态或 JPA <br/>持久性上下文的应用程序代码时,请确保刷新运行该代码的测试方法<br/>中的底层工作单元。未能刷新底层工作单元可能会产生错误的<br/>正:你的测试通过了,但是相同的代码在动态的生产<br/>环境中抛出了异常。请注意,这适用于任何维护<br/>工作的内存单元的 ORM 框架。在以下基于 Hibernate 的示例测试用例中,一种方法演示了<br/>假阳性,而另一种方法则正确地公开了刷新<br/>会话的结果:<br/><br/>爪哇<br/>```<br/>// ...<br/><br/>@Autowired<br/>SessionFactory sessionFactory;<br/><br/>@Transactional<br/>@Test // no expected exception!<br/>public void falsePositive() {<br/>    updateEntityInHibernateSession();<br/>    // False positive: an exception will be thrown once the Hibernate<br/>    // Session is finally flushed (i.e., in production code)<br/>}<br/><br/>@Transactional<br/>@Test(expected = ...)<br/>public void updateWithSessionFlush() {<br/>    updateEntityInHibernateSession();<br/>    // Manual flush is required to avoid false positive in test<br/>    sessionFactory.getCurrentSession().flush();<br/>}<br/><br/>// ...<br/>```<br/><br/><br/>```<br/>// ...<br/><br/>@Autowired<br/>lateinit var sessionFactory: SessionFactory<br/><br/>@Transactional<br/>@Test // no expected exception!<br/>fun falsePositive() {<br/>    updateEntityInHibernateSession()<br/>    // False positive: an exception will be thrown once the Hibernate<br/>    // Session is finally flushed (i.e., in production code)<br/>}<br/><br/>@Transactional<br/>@Test(expected = ...)<br/>fun updateWithSessionFlush() {<br/>    updateEntityInHibernateSession()<br/>    // Manual flush is required to avoid false positive in test<br/>    sessionFactory.getCurrentSession().flush()<br/>}<br/><br/>// ...<br/>```<gt="2085"/><><br/><"><"><"><"gt="gt="2085">>><">>><<<"gt=2085">>>>>>>><<<<<"gt=|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 3.5.10.执行 SQL 脚本

在针对关系数据库编写集成测试时,运行 SQL 脚本来修改数据库模式或将测试数据插入到表中通常是有益的。`spring-jdbc`模块通过在 Spring `ApplicationContext`加载时执行 SQL 脚本,为*初始化*嵌入式或现有数据库提供支持。详见[嵌入式数据库支持](data-access.html#jdbc-embedded-database-support)[用嵌入式数据库测试数据访问逻辑](data-access.html#jdbc-embedded-database-dao-testing)

虽然在`ApplicationContext`加载时初始化一个用于测试*曾经*的数据库是非常有用的,但有时能够修改数据库*期间*集成测试是必不可少的。下面的部分解释了如何在集成测试期间以编程方式和声明式方式运行 SQL 脚本。

##### 以编程方式执行 SQL 脚本

Spring 提供了以下用于在集成测试方法中以编程方式执行 SQL 脚本的选项。

* `org.springframework.jdbc.datasource.init.ScriptUtils`

* `org.springframework.jdbc.datasource.init.ResourceDatabasePopulator`

* `org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests`

* `org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests`

`ScriptUtils`提供了一组用于处理 SQL 脚本的静态实用程序方法,主要用于框架内的内部使用。但是,如果你需要完全控制 SQL 脚本的解析和运行方式,`ScriptUtils`可能比后面介绍的其他一些替代方案更适合你的需要。有关更多详细信息,请参见[javadoc](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/jdbc/datasource/init/ScriptUtils.html)中的单个方法。

`ResourceDatabasePopulator`提供了一个基于对象的 API,用于通过使用在外部资源中定义的 SQL 脚本以编程方式填充、初始化或清理数据库。`ResourceDatabasePopulator`提供了用于配置字符编码、语句分隔符、注释分隔符和解析和运行脚本时使用的错误处理标志的选项。每个配置选项都有一个合理的默认值。有关默认值的详细信息,请参见[javadoc](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.html)。要运行在`ResourceDatabasePopulator`中配置的脚本,你可以调用`populate(Connection)`方法来针对`java.sql.Connection`运行填充器,也可以调用`execute(DataSource)`方法来针对`javax.sql.DataSource`运行填充器。下面的示例为测试模式和测试数据指定 SQL 脚本,将语句分隔符设置为`@@`,并针对`DataSource`运行脚本:

爪哇

```
@Test
void databaseTest() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
            new ClassPathResource("test-schema.sql"),
            new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // run code that uses the test schema and data
}
```

Kotlin

```
@Test
fun databaseTest() {
    val populator = ResourceDatabasePopulator()
    populator.addScripts(
            ClassPathResource("test-schema.sql"),
            ClassPathResource("test-data.sql"))
    populator.setSeparator("@@")
    populator.execute(dataSource)
    // run code that uses the test schema and data
}
```

请注意,`ResourceDatabasePopulator`内部委托给`ScriptUtils`用于解析和运行 SQL 脚本。类似地,[`executeSqlScript(..)`](#testcontext-support-classes-junit4)和[`AbstractTransactionalTestNGSpringContextTests`](#testcontext-support-classes-testng)中的`executeSqlScript(..)`方法在内部使用`ResourceDatabasePopulator`来运行 SQL 脚本。有关更多详细信息,请参见 爪哇doc 获取各种`executeSqlScript(..)`方法。

##### 使用 @sql 声明式执行 SQL 脚本

除了上述以编程方式运行 SQL 脚本的机制外,还可以在 Spring TestContext 框架中声明性地配置 SQL 脚本。具体地说,你可以在测试类或测试方法上声明`@Sql`注释,以配置应在集成测试方法之前或之后针对给定数据库运行的 SQL 脚本的各个 SQL 语句或资源路径。对`@Sql`的支持由`SqlScriptsTestExecutionListener`提供,默认情况下启用。

|   |方法级别`@Sql`声明默认重写类级别声明。然而,作为 Spring Framework5.2 的<br/>,该行为可以通过`@SqlMergeMode`配置为每个测试类或每个<br/>测试方法。有关更多详细信息,请参见[使用`@SqlMergeMode`合并和覆盖配置]。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###### 路径资源语义

每个路径被解释为 Spring `Resource`。普通路径(例如,`"schema.sql"`)被视为与定义测试类的包相关的 Classpath 资源。以斜杠开头的路径被视为绝对 Classpath 资源(例如,`"/org/example/schema.sql"`)。通过使用指定的资源协议加载引用 URL 的路径(例如,带有`classpath:``file:``http:`前缀的路径)。

下面的示例展示了如何在类级别和基于 JUnit Jupiter 的集成测试类中的方法级别上使用`@Sql`:

爪哇

```
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    void emptySchemaTest() {
        // run code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    void userTest() {
        // run code that uses the test schema and test data
    }
}
```

Kotlin

```
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    fun emptySchemaTest() {
        // run code that uses the test schema without any test data
    }

    @Test
    @Sql("/test-schema.sql", "/test-user-data.sql")
    fun userTest() {
        // run code that uses the test schema and test data
    }
}
```

###### 默认脚本检测

如果没有指定 SQL 脚本或语句,则尝试检测`default`脚本,这取决于声明`@Sql`的位置。如果无法检测到默认值,则抛出`IllegalStateException`

* 类级声明:如果带注释的测试类是`com.example.MyTest`,则对应的默认脚本是`classpath:com/example/MyTest.sql`

* 方法级别声明:如果带注释的测试方法名为`testMethod()`,并且在类`com.example.MyTest`中定义,那么对应的默认脚本是`classpath:com/example/MyTest.testMethod.sql`

###### 声明多个`@Sql`集

如果需要为给定的测试类或测试方法配置多组 SQL 脚本,但语法配置不同,错误处理规则不同,或者每组执行阶段不同,则可以声明`@Sql`的多个实例。对于 爪哇8,你可以使用`@Sql`作为可重复的注释。否则,你可以使用`@SqlGroup`注释作为显式容器来声明`@Sql`的多个实例。

下面的示例展示了如何使用`@Sql`作为 爪哇8 的可重复注释:

爪哇

```
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
    // run code that uses the test schema and test data
}
```

Kotlin

```
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin
```

在前面示例中介绍的场景中,`test-schema.sql`脚本对单行注释使用了不同的语法。

下面的示例与前面的示例相同,只是`@Sql`声明被组合在`@SqlGroup`中。对于 爪哇8 及以上版本,`@SqlGroup`的使用是可选的,但是你可能需要使用`@SqlGroup`来与其他 JVM 语言(如 Kotlin)兼容。

爪哇

```
@Test
@SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // run code that uses the test schema and test data
}
```

Kotlin

```
@Test
@SqlGroup(
    Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
    Sql("/test-user-data.sql"))
fun userTest() {
    // Run code that uses the test schema and test data
}
```

###### 脚本执行阶段

默认情况下,SQL 脚本在相应的测试方法之前运行。但是,如果需要在测试方法之后运行一组特定的脚本(例如,清理数据库状态),则可以在`executionPhase`中使用`@Sql`属性,如下例所示:

爪哇

```
@Test
@Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
)
void userTest() {
    // run code that needs the test data to be committed
    // to the database outside of the test's transaction
}
```

Kotlin

```
@Test
@SqlGroup(
    Sql("create-test-data.sql",
        config = SqlConfig(transactionMode = ISOLATED)),
    Sql("delete-test-data.sql",
        config = SqlConfig(transactionMode = ISOLATED),
        executionPhase = AFTER_TEST_METHOD))
fun userTest() {
    // run code that needs the test data to be committed
    // to the database outside of the test's transaction
}
```

注意,`ISOLATED``AFTER_TEST_METHOD`分别从`Sql.TransactionMode``Sql.ExecutionPhase`静态导入。

###### 带有`@SqlConfig`的脚本配置

你可以使用`@SqlConfig`注释来配置脚本解析和错误处理。当声明为集成测试类上的类级注释时,`@SqlConfig`充当测试类层次结构中所有 SQL 脚本的全局配置。当通过使用`@Sql`注释的`config`属性直接声明时,`@SqlConfig`充当在附件`@Sql`注释中声明的 SQL 脚本的本地配置。`@SqlConfig`中的每个属性都有一个隐含的默认值,该默认值在相应属性的 爪哇doc 中有文档说明。由于 爪哇 语言规范中为注释属性定义的规则,遗憾的是,不可能为注释属性赋值`null`。因此,为了支持对继承的全局配置的重写,`@SqlConfig`属性具有显式默认值`""`(用于字符串),`{}`(用于数组),或`DEFAULT`(用于枚举)。这种方法允许`@SqlConfig`的本地声明通过提供`""``{}``DEFAULT`以外的值,选择性地覆盖`@SqlConfig`的全局声明中的单个属性。当局部`@SqlConfig`属性不提供除`""``{}``DEFAULT`以外的显式值时,全局`@SqlConfig`属性将被继承。因此,显式的局部配置重写全局配置。

`@Sql``@SqlConfig`提供的配置选项与`ScriptUtils``ResourceDatabasePopulator`支持的配置选项等价,但它们是由`<jdbc:initialize-database/>`XML 名称空间元素提供的那些选项的超集。参见[`@Sql`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/context/jdbc/sql.html)和[`@SqlConfig`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api-api/org/context/jdbc/jdbc/sqlconframework.html)中的各个属性的 爪哇doc,以获取详细信息。

**`@Sql`的事务管理 **

默认情况下,`SqlScriptsTestExecutionListener`为使用`@Sql`配置的脚本推断所需的事务语义。具体地说,SQL 脚本在没有事务的情况下运行,在现有 Spring 管理的事务(例如,由`TransactionalTestExecutionListener`管理的事务,用于用`@Transactional`注释的测试)中运行,或者在独立的事务中运行,取决于`@SqlConfig``transactionMode`属性的配置值,以及在测试的`PlatformTransactionManager`中是否存在`ApplicationContext`。然而,作为最低要求,测试的`javax.sql.DataSource`中必须存在`ApplicationContext`

如果`SqlScriptsTestExecutionListener`用于检测`DataSource``PlatformTransactionManager`并推断事务语义的算法不适合你的需要,则可以通过设置`dataSource``transactionManager``@SqlConfig`属性来指定显式名称。此外,你可以通过设置`@SqlConfig``transactionMode`属性来控制事务传播行为(例如,是否应该在独立的事务中运行脚本)。尽管对`@Sql`的事务管理所支持的所有选项的彻底讨论超出了本参考手册的范围,但对于[`@SqlConfig`](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/javadoc-api/org/SpringFramework/tFramework/tFramework/tform/springframework/test/test/contextbc/jdbc/sqlconfig.html)和[`SqlScriptsTestExecutionListener`](DOCS.[ps:/ Spring.io/[[framework. Spring-fram 下面的示例展示了一个典型的测试场景,该场景使用 JUnit Jupiter 和事务测试`@Sql`:

爪哇

```
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {

    final JdbcTemplate jdbcTemplate;

    @Autowired
    TransactionalSqlScriptsTests(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    @Sql("/test-data.sql")
    void usersTest() {
        // verify state in test database:
        assertNumUsers(2);
        // run code that uses the test data...
    }

    int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    void assertNumUsers(int expected) {
        assertEquals(expected, countRowsInTable("user"),
            "Number of rows in the [user] table.");
    }
}
```

Kotlin

```
@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {

    val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)

    @Test
    @Sql("/test-data.sql")
    fun usersTest() {
        // verify state in test database:
        assertNumUsers(2)
        // run code that uses the test data...
    }

    fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }

    fun assertNumUsers(expected: Int) {
        assertEquals(expected, countRowsInTable("user"),
                "Number of rows in the [user] table.")
    }
}
```

请注意,在`usersTest()`方法运行后,不需要清理数据库,因为对数据库所做的任何更改(在测试方法内或`/test-data.sql`脚本内)都会由`TransactionalTestExecutionListener`自动回滚(有关详细信息,请参见[事务管理](#testcontext-tx))。

###### 用`@SqlMergeMode`合并和覆盖配置

在 Spring Framework5.2 中,可以将方法级`@Sql`声明与类级声明合并。例如,这允许你为每个测试类提供一次数据库模式或一些公共测试数据的配置,然后为每个测试方法提供额外的、特定于用例的测试数据。要启用`@Sql`合并,请用`@SqlMergeMode(MERGE)`注释你的测试类或测试方法。要禁用特定测试方法(或特定测试子类)的合并,可以通过`@SqlMergeMode(OVERRIDE)`切换回默认模式。有关示例和更多详细信息,请参阅[`@SqlMergeMode`注释文档部分](# Spring-testing-annotation-sqlmergemode)。

#### 3.5.11.并行测试执行

Spring Framework5.0 引入了在使用 Spring TestContext 框架时在单个 JVM 内并行执行测试的基本支持。通常,这意味着大多数测试类或测试方法可以并行运行,而不需要对测试代码或配置进行任何更改。

|   |有关如何设置并行测试执行的详细信息,请参阅<br/>测试框架、构建工具或 IDE 的文档。|
|---|-------------------------------------------------------------------------------------------------------------------------------|

请记住,在测试套件中引入并发可能会导致意外的副作用、奇怪的运行时行为,以及间歇性或似乎随机地失败的测试。 Spring 因此,团队为何时不并行运行测试提供了以下一般准则。

如果测试:

* 使用 Spring Framework 的`@DirtiesContext`支持。

* 使用 Spring Boot 的`@MockBean``@SpyBean`支持。

* 使用 JUnit4 的`@FixMethodOrder`支持或任何旨在确保测试方法以特定顺序运行的测试框架功能。但是,请注意,如果整个测试类并行运行,则不适用于此。

* 更改共享服务或系统(如数据库、消息代理、文件系统等)的状态。这既适用于嵌入式系统,也适用于外部系统。

|   |如果并行测试执行失败,并且异常地声明当前测试的`ApplicationContext`不再活动,这通常意味着`ApplicationContext`在不同的线程中从`ContextCache`中被删除。<br/><br/>这可能是由于使用`@DirtiesContext`或由于从`ContextCache`中自动驱逐所致。如果`@DirtiesContext`是罪魁祸首,则需要找到一种方法来<br/>避免使用`@DirtiesContext`,或者将此类测试排除在并行执行之外。如果<br/>已超过`ContextCache`的最大大小,则可以增加缓存的最大大小<br/>。详见[上下文缓存](#testcontext-ctx-management-caching)讨论。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |Spring TestContext 框架中的并行测试执行只有在以下情况下才是可能的:<br/>底层`TestContext`实现提供了一个复制构造函数,如<br/>中所解释的[`TestContext`]的 爪哇doc(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/text/context.html)。 Spring 中使用的`DefaultTestContext`提供了这样的构造函数。但是,如果使用提供自定义<br/>实现的`TestContext`第三方库,则需要<br/>验证它是否适合并行测试执行。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 3.5.12.TestContext 框架支持类

本节描述了支持 Spring TestContext 框架的各种类。

##### Spring JUnit4Runner

Spring TestContext 框架通过自定义运行器(在 JUnit4.12 或更高版本上支持)提供与 JUnit4 的完全集成。通过使用`@RunWith(SpringJUnit4ClassRunner.class)`或更短的`@RunWith(SpringRunner.class)`变体注释测试类,开发人员可以实现基于标准 JUnit4 的单元和集成测试,并同时获得 TestContext 框架的好处,例如对加载应用程序上下文、测试实例的依赖注入、事务性测试方法执行的支持,等等。如果希望将 Spring TestContext 框架与可选的运行器(例如 JUnit4 的Runner)或第三方运行器(例如)一起使用,则可以选择使用。

下面的代码清单显示了配置使用自定义 Spring `Runner`运行的测试类的最低要求:

爪哇

```
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

    @Test
    public void testMethod() {
        // test logic...
    }
}
```

Kotlin

```
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {

    @Test
    fun testMethod() {
        // test logic...
    }
}
```

在前面的示例中,`@TestExecutionListeners`被配置为空列表,以禁用默认侦听器,否则将需要通过`ApplicationContext`配置`@ContextConfiguration`

##### Spring JUnit4 规则

`org.springframework.test.context.junit4.rules`包提供了以下 JUnit4 规则(JUnit4.12 或更高版本支持):

* `SpringClassRule`

* `SpringMethodRule`

`SpringClassRule`是支持 Spring TestContext 框架的类级特性的 JUnit`TestRule`,而`SpringMethodRule`是支持 Spring TestContext 框架的实例级和方法级特性的 JUnit`MethodRule`

`SpringRunner`相反, Spring 的基于规则的 JUnit 支持具有独立于任何`org.junit.runner.Runner`实现的优点,因此可以与现有的替代运行器(例如 JUnit4 的`Parameterized`)或第三方运行器(例如`MockitoJUnitRunner`)组合。

要支持 TestContext 框架的全部功能,你必须将`SpringClassRule``SpringMethodRule`合并。下面的示例展示了在集成测试中声明这些规则的正确方法:

爪哇

```
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {

    @ClassRule
    public static final SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Test
    public void testMethod() {
        // test logic...
    }
}
```

Kotlin

```
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {

    @Rule
    val springMethodRule = SpringMethodRule()

    @Test
    fun testMethod() {
        // test logic...
    }

    companion object {
        @ClassRule
        val springClassRule = SpringClassRule()
    }
}
```

##### JUnit4 支持类

`org.springframework.test.context.junit4`包为基于 JUnit4 的测试用例(JUnit4.12 或更高版本支持)提供了以下支持类:

* `AbstractJUnit4SpringContextTests`

* `AbstractTransactionalJUnit4SpringContextTests`

`AbstractJUnit4SpringContextTests`是一个抽象基测试类,它将 Spring TestContext 框架与 JUnit4 环境中显式的`ApplicationContext`测试支持集成在一起。扩展`AbstractJUnit4SpringContextTests`时,可以访问`protected``applicationContext`实例变量,可以使用该变量执行显式 Bean 查找或测试整个上下文的状态。

`AbstractTransactionalJUnit4SpringContextTests``AbstractJUnit4SpringContextTests`的抽象事务扩展,它为 JDBC 访问添加了一些方便的功能。该类期望在`javax.sql.DataSource` Bean 和`PlatformTransactionManager` Bean 中定义`ApplicationContext`。扩展`AbstractTransactionalJUnit4SpringContextTests`时,可以访问`protected``jdbcTemplate`实例变量,你可以使用该变量运行 SQL 语句来查询数据库。可以使用这样的查询来确认在运行数据库相关的应用程序代码之前和之后的数据库状态,并且 Spring 确保这样的查询在与应用程序代码相同的事务的范围内运行。当与 ORM 工具一起使用时,请务必避免[误报](#testcontext-tx-false-positives)。正如在[JDBC 测试支持](#integration-testing-support-jdbc)中提到的,`AbstractTransactionalJUnit4SpringContextTests`还提供了方便的方法,通过使用前面提到的`JdbcTestUtils`将方法委托给`jdbcTemplate`中的方法。此外,`AbstractTransactionalJUnit4SpringContextTests`提供了一个`executeSqlScript(..)`方法,用于针对配置的`DataSource`运行 SQL 脚本。

|   |这些类为扩展提供了方便。如果不希望你的测试类<br/>绑定到 Spring 特定的类层次结构,则可以使用<br/>[Spring’s<br/>JUnit rules](#testcontext-junit4-rules)配置你自己的自定义测试类。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### 朱尼特木星的 SpringExtension

Spring TestContext 框架提供了与 JUnit Jupiter 测试框架的完全集成,JUnit5 中介绍了该测试框架。通过使用`@ExtendWith(SpringExtension.class)`对测试类进行注释,你可以实现标准的基于 JUnit Jupiter 的单元和集成测试,并同时获得 TestContext 框架的好处,例如对加载应用程序上下文的支持、测试实例的依赖注入、事务测试方法的执行,等等。

此外,由于 JUnit Jupiter 中丰富的扩展 API, Spring 在 Spring 支持 JUnit4 和 TestNG 的功能集之上和之上提供了以下功能:

* 用于测试构造函数、测试方法和测试生命周期回调方法的依赖注入。有关更多详细信息,请参见[dependency injection with`SpringExtension`]。

* 基于 SPEL 表达式、环境变量、系统属性等对[条件测试执行](https://junit.org/junit5/docs/current/user-guide/#extensions-conditions)的强大支持。有关更多详细信息和示例,请参见`@EnabledIf``@DisabledIf`中的文档。

* 自定义合成的注释结合了来自 Spring 和 JUnit Jupiter 的注释。有关更多详细信息,请参见`@TransactionalDevTestConfig``@TransactionalIntegrationTest`中的示例。

下面的代码清单显示了如何配置一个测试类,以便将`SpringExtension``@ContextConfiguration`结合使用:

爪哇

```
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
```

Kotlin

```
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension::class)
// Instructs Spring to load an ApplicationContext from TestConfig::class
@ContextConfiguration(classes = [TestConfig::class])
class SimpleTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}
```

由于还可以使用 JUnit5 中的注释作为元注释, Spring 提供了`@SpringJUnitConfig``@SpringJUnitWebConfig`组合注释,以简化测试`ApplicationContext`和 JUnit Jupiter 的配置。

下面的示例使用`@SpringJUnitConfig`来减少前面示例中使用的配置量:

爪哇

```
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
```

Kotlin

```
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig::class)
class SimpleTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}
```

类似地,下面的示例使用`@SpringJUnitWebConfig`创建一个`WebApplicationContext`,用于 JUnit Jupiter:

爪哇

```
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {

    @Test
    void testMethod() {
        // test logic...
    }
}
```

Kotlin

```
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig::class
@SpringJUnitWebConfig(TestWebConfig::class)
class SimpleWebTests {

    @Test
    fun testMethod() {
        // test logic...
    }
}
```

有关更多详细信息,请参见`@SpringJUnitConfig``@SpringJUnitWebConfig`中的文档。

##### 依赖注入与`SpringExtension`

`SpringExtension`实现了来自 JUnit Jupiter 的[`ParameterResolver`](https://junit.org/junit5/DOCS/current/user-guide/#extensions-parameter-resolution)扩展 API,它允许 Spring 为测试构造函数、测试方法和测试生命周期回调方法提供依赖注入。

具体地说,`SpringExtension`可以将来自测试的`ApplicationContext`的依赖关系注入到使用`@BeforeAll``@AfterAll``@BeforeEach``@AfterEach``@RepeatedTest``@RepeatedTest``@ParameterizedTest`等注释的测试构造函数和方法中。

###### 构造函数注入

如果 JUnit Jupiter 测试类的构造函数中的特定参数类型为`ApplicationContext`(或其子类型),或者被注释或 meta 注释为`@Autowired``@Qualifier`,或`@Value`, Spring 将该特定参数的值注入相应的 Bean 或来自测试的`ApplicationContext`的值。

Spring 还可以被配置为在一个测试类构造函数被认为是*可自动连接*的情况下自动连接用于该构造函数的所有参数。如果满足以下条件之一(按优先顺序),则构造函数被认为是可自动连接的。

* 构造函数用`@Autowired`注释。

* `@TestConstructor`在测试类上存在或元存在,并且`autowireMode`属性设置为`ALL`

* 默认的*测试构造函数 AutoWire 模式*已更改为`ALL`

有关`@TestConstructor`的使用以及如何更改全局*测试构造函数 AutoWire 模式*的详细信息,请参见[`@TestConstructor`](#Integration-Testing-Annotations-TestConstructor)。

|   |如果一个测试类的构造函数被认为是*可自动连接*,则 Spring <br/>承担解决构造函数中所有参数的参数的责任。<br/>因此,在 JUnit Jupiter 注册的其他`ParameterResolver`都不能为这样的构造函数解析<br/>参数。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |测试类的构造函数注入不能与 JUnit<br/>Jupiter 的`@TestInstance(PER_CLASS)`支持一起使用如果`@DirtiesContext`是用来关闭<br/>测试的`ApplicationContext`测试方法之前或之后的<br/>的,原因是`@TestInstance(PER_CLASS)`指示 JUnit Jupiter 在测试方法调用之间缓存测试<br/>实例。因此,测试实例将保留<br/>对 bean 的引用,这些 bean 最初是从`ApplicationContext`注入的,而<br/>随后被关闭。由于在这种情况下测试类的构造函数只会被调用<br/>一次,因此依赖项注入将不会再次发生,而随后的测试<br/>将与来自闭合的`ApplicationContext`的 bean 进行交互,这可能会导致错误。<br/><br/>`@DirtiesContext`与“测试方法之前”或“测试方法之后”的模式在<br/>中与`@TestInstance(PER_CLASS)`结合,必须将 Spring <br/>中的依赖项配置为通过字段或 setter 注入提供,以便它们可以在测试<br/>方法调用之间重新注入。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在下面的示例中, Spring 将从`OrderService` Bean 加载的`ApplicationContext`中的`TestConfig.class`注入到`OrderServiceIntegrationTests`构造函数中。

爪哇

```
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    @Autowired
    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}
```

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
    // tests that use the injected OrderService
}
```

请注意,这个特性允许测试依赖项`final`,因此是不可变的。

如果`spring.test.constructor.autowire.mode`属性是`all`(参见[`@TestConstructor`](#Integration-Testing-Annotations-TestConstructor)),我们可以在前面的示例中省略构造函数上`@Autowired`的声明,从而产生以下结果。

爪哇

```
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}
```

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests(val orderService:OrderService) {
    // tests that use the injected OrderService
}
```

###### 方法注入

如果 JUnit Jupiter 测试方法或测试生命周期回调方法中的参数类型为`ApplicationContext`(或其子类型),或被注释或 meta 注释为`@Autowired``@Qualifier`,或`@Value`, Spring 从测试的`ApplicationContext`中将该特定参数的值注入相应的 Bean。

在下面的示例中, Spring 将从`OrderService`加载的`ApplicationContext`中的`TestConfig.class`注入到`deleteOrder()`测试方法中:

爪哇

```
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @Test
    void deleteOrder(@Autowired OrderService orderService) {
        // use orderService from the test's ApplicationContext
    }
}
```

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {

    @Test
    fun deleteOrder(@Autowired orderService: OrderService) {
        // use orderService from the test's ApplicationContext
    }
}
```

由于 JUnit Jupiter 中`ParameterResolver`支持的健壮性,你还可以将多个依赖注入到单个方法中,不仅来自 Spring,还来自 JUnit Jupiter 本身或其他第三方扩展。

下面的示例展示了如何让 Spring 和 JUnit Jupiter 同时向`placeOrderRepeatedly()`测试方法中注入依赖项。

爪哇

```
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
            @Autowired OrderService orderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}
```

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}
```

注意,使用来自 Junit Jupiter 的`@RepeatedTest`可以让测试方法访问`RepetitionInfo`

##### `@Nested`测试类配置

自 Spring Framework5.0 以来,*Spring TestContext Framework*一直支持在 JUnit Jupiter 中的`@Nested`测试类上使用与测试相关的注释;然而,在 Spring Framework5.3 之前,类级测试配置注释并不像来自超类那样来自封闭类的*继承*

Spring Framework5.3 引入了用于从封闭类继承测试类配置的第一类支持,并且这样的配置将在默认情况下被继承。要从默认的`INHERIT`模式更改为`OVERRIDE`模式,你可以用`@Nested`注释单个`@Nested`测试类。显式的`@NestedTestConfiguration`声明将应用于带注释的测试类及其任何子类和嵌套的类。因此,你可以用`@NestedTestConfiguration`注释顶级测试类,这将递归地应用于它的所有嵌套测试类。

为了允许开发团队将缺省模式更改为`OVERRIDE`——例如,为了与 Spring Framework5.0 到 5.2 兼容——缺省模式可以通过 JVM 系统属性或 Classpath 根中的`spring.properties`文件进行全局更改。有关详细信息,请参见[“更改默认的封闭配置继承模式”](#integration-testing-annotations-nestedtestconfiguration)注释。

尽管下面的“Hello World”示例非常简单,但它展示了如何在顶级类上声明公共配置,该类由其`@Nested`测试类继承。在这个特定的示例中,只继承了`TestConfig`配置类。每个嵌套测试类提供自己的一组活动配置文件,从而为每个嵌套测试类提供一个不同的`ApplicationContext`(有关详细信息,请参见[上下文缓存](#testcontext-ctx-management-caching))。请参阅[支持的注释](#integration-testing-annotations-nestedtestconfiguration)列表,以查看哪些注释可以在`@Nested`测试类中继承。

爪哇

```
@SpringJUnitConfig(TestConfig.class)
class GreetingServiceTests {

    @Nested
    @ActiveProfiles("lang_en")
    class EnglishGreetings {

        @Test
        void hello(@Autowired GreetingService service) {
            assertThat(service.greetWorld()).isEqualTo("Hello World");
        }
    }

    @Nested
    @ActiveProfiles("lang_de")
    class GermanGreetings {

        @Test
        void hello(@Autowired GreetingService service) {
            assertThat(service.greetWorld()).isEqualTo("Hallo Welt");
        }
    }
}
```

Kotlin

```
@SpringJUnitConfig(TestConfig::class)
class GreetingServiceTests {

    @Nested
    @ActiveProfiles("lang_en")
    inner class EnglishGreetings {

        @Test
        fun hello(@Autowired service:GreetingService) {
            assertThat(service.greetWorld()).isEqualTo("Hello World")
        }
    }

    @Nested
    @ActiveProfiles("lang_de")
    inner class GermanGreetings {

        @Test
        fun hello(@Autowired service:GreetingService) {
            assertThat(service.greetWorld()).isEqualTo("Hallo Welt")
        }
    }
}
```

##### TestNG 支持类

`org.springframework.test.context.testng`包为基于 TestNG 的测试用例提供了以下支持类:

* `AbstractTestNGSpringContextTests`

* `AbstractTransactionalTestNGSpringContextTests`

`AbstractTestNGSpringContextTests`是一个抽象基测试类,它将 Spring TestContext 框架与 TestNG 环境中显式的`ApplicationContext`测试支持集成在一起。扩展`AbstractTestNGSpringContextTests`时,可以访问`protected``applicationContext`实例变量,你可以使用该变量执行显式 Bean 查找或测试整个上下文的状态。

`AbstractTransactionalTestNGSpringContextTests``AbstractTestNGSpringContextTests`的抽象事务扩展,它为 JDBC 访问添加了一些方便的功能。该类期望在`javax.sql.DataSource` Bean 和`PlatformTransactionManager` Bean 中定义`ApplicationContext`。扩展`AbstractTransactionalTestNGSpringContextTests`时,可以访问`protected``jdbcTemplate`实例变量,你可以使用该变量运行 SQL 语句来查询数据库。可以使用这样的查询来确认在运行数据库相关的应用程序代码之前和之后的数据库状态,并且 Spring 确保这样的查询在与应用程序代码相同的事务范围内运行。当与 ORM 工具一起使用时,请务必避免[误报](#testcontext-tx-false-positives)。正如在[JDBC 测试支持](#integration-testing-support-jdbc)中提到的,`AbstractTransactionalTestNGSpringContextTests`还提供了方便的方法,通过使用前面提到的`jdbcTemplate`将方法委托给`JdbcTestUtils`中的方法。此外,`AbstractTransactionalTestNGSpringContextTests`提供了一个`executeSqlScript(..)`方法,用于针对配置的`DataSource`运行 SQL 脚本。

|   |这些类为扩展提供了方便。如果你不希望你的测试类<br/>被绑定到 Spring 特定的类层次结构,那么你可以通过使用`@ContextConfiguration``@TestExecutionListeners`以此类推,并通过<br/>手动使用`TestContextManager`来配置你自己的自定义测试类。请参阅源代码<br/>`AbstractTestNGSpringContextTests`,以获得如何测试类的示例。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 3.6.WebTestClient

`WebTestClient`是一个用于测试服务器应用程序的 HTTP 客户机。它封装了 Spring 的[WebClient](web-reactive.html#webflux-client),并使用它来执行请求,但公开了一个用于验证响应的测试 facade。`WebTestClient`可用于执行端到端的 HTTP 测试。 Spring MVC 和 Spring WebFlux 应用程序也可用于在没有运行服务器的情况下通过模拟服务器来测试请求和响应对象。

|   |Kotlin 用户:参见[本节](languages.html#kotlin-webtestclient-issue)相关的`WebTestClient`的使用。|
|---|-----------------------------------------------------------------------------------------------------------------|

#### 3.6.1.设置

要设置`WebTestClient`,你需要选择要绑定到的服务器设置。这可以是几个模拟服务器设置选项中的一个,也可以是与实时服务器的连接。

##### 绑定到控制器

此设置允许你通过模拟请求和响应对象测试特定的控制器,而无需运行服务器。

对于 WebFlux 应用程序,使用以下方法加载与[WebFlux 爪哇 配置](web-reactive.html#webflux-config)等价的基础设施,注册给定的控制器,并创建[Webhandler 链](web-reactive.html#webflux-web-handler-api)来处理请求:

爪哇

```
WebTestClient client =
        WebTestClient.bindToController(new TestController()).build();
```

Kotlin

```
val client = WebTestClient.bindToController(TestController()).build()
```

对于 Spring MVC,使用以下方法委托给[StandalOneMockmvcBuilder](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.html)以加载与[WebMVC 爪哇 配置](web.html#mvc-config)等价的基础设施,注册给定的控制器,并创建[MockMvc](#spring-mvc-test-framework)的实例来处理请求:

爪哇

```
WebTestClient client =
        MockMvcWebTestClient.bindToController(new TestController()).build();
```

Kotlin

```
val client = MockMvcWebTestClient.bindToController(TestController()).build()
```

##### 绑定到`ApplicationContext`

这种设置允许你使用 Spring MVC 或 Spring WebFlux 基础设施和控制器声明加载 Spring 配置,并使用它通过模拟请求和响应对象来处理请求,而无需运行服务器。

对于 WebFlux,使用以下方法将 Spring `ApplicationContext`传递到[WebHttphandlerBuilder](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-),以创建[Webhandler 链](web-reactive.html#webflux-web-handler-api)来处理请求:

爪哇

```
@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {

    WebTestClient client;

    @BeforeEach
    void setUp(ApplicationContext context) {  (2)
        client = WebTestClient.bindToApplicationContext(context).build(); (3)
    }
}
```

|**1**|指定要加载的配置|
|-----|---------------------------------|
|**2**|注入配置|
|**3**|创建`WebTestClient`|

Kotlin

```
@SpringJUnitConfig(WebConfig::class) (1)
class MyTests {

    lateinit var client: WebTestClient

    @BeforeEach
    fun setUp(context: ApplicationContext) { (2)
        client = WebTestClient.bindToApplicationContext(context).build() (3)
    }
}
```

|**1**|指定要加载的配置|
|-----|---------------------------------|
|**2**|注入配置|
|**3**|创建`WebTestClient`|

对于 Spring MVC,使用以下方法,其中将 Spring `ApplicationContext`传递到[mockmvcbuilders.webappcontextsetup](https://docs.spring.io/spring-framework/docs/5.3.16/javadoc-api/org/springframework/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-),以创建[MockMvc](#spring-mvc-test-framework)实例来处理请求:

爪哇

```
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
    @ContextConfiguration(classes = RootConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class MyTests {

    @Autowired
    WebApplicationContext wac; (2)

    WebTestClient client;

    @BeforeEach
    void setUp() {
        client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); (3)
    }
}
```

|**1**|指定要加载的配置|
|-----|---------------------------------|
|**2**|注入配置|
|**3**|创建`WebTestClient`|

Kotlin

```
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
    @ContextConfiguration(classes = RootConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class MyTests {

    @Autowired
    lateinit var wac: WebApplicationContext; (2)

    lateinit var client: WebTestClient

    @BeforeEach
    fun setUp() { (2)
        client = MockMvcWebTestClient.bindToApplicationContext(wac).build() (3)
    }
}
```

|**1**|指定要加载的配置|
|-----|---------------------------------|
|**2**|注入配置|
|**3**|创建`WebTestClient`|

##### 绑定到路由器功能

这种设置允许你通过模拟请求和响应对象来测试[功能端点](web-reactive.html#webflux-fn),而不需要运行服务器。

对于 WebFlux,使用以下方法委托`RouterFunctions.toWebHandler`来创建服务器设置以处理请求:

爪哇

```
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
```

Kotlin

```
val route: RouterFunction<*> = ...
val client = WebTestClient.bindToRouterFunction(route).build()
```

对于 Spring MVC,目前没有测试[WebMVC 功能端点](web.html#webmvc-fn)的选项。

##### 绑定到服务器

此设置连接到正在运行的服务器,以执行完整的端到端 HTTP 测试:

爪哇

```
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
```

Kotlin

```
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
```

##### 客户端配置

除了前面描述的服务器设置选项外,还可以配置客户端选项,包括基本 URL、默认标头、客户端过滤器和其他选项。这些选项在`bindToServer()`之后很容易获得。对于所有其他配置选项,你需要使用`configureClient()`来从服务器配置转换到客户机配置,如下所示:

爪哇

```
client = WebTestClient.bindToController(new TestController())
        .configureClient()
        .baseUrl("/test")
        .build();
```

Kotlin

```
client = WebTestClient.bindToController(TestController())
        .configureClient()
        .baseUrl("/test")
        .build()
```

#### 3.6.2.写作测试

`WebTestClient`提供与[WebClient](web-reactive.html#webflux-client)相同的 API,直到使用`exchange()`执行请求为止。请参阅[WebClient](web-reactive.html#webflux-client-body)文档中的示例,以了解如何准备包含表单数据、多部分数据等任何内容的请求。

在调用`exchange()`之后,`WebTestClient`偏离了`WebClient`,而是继续使用工作流来验证响应。

要断言响应状态和头,请使用以下方法:

爪哇

```
client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectStatus().isOk()
    .expectHeader().contentType(MediaType.APPLICATION_JSON);
```

Kotlin

```
client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectStatus().isOk()
    .expectHeader().contentType(MediaType.APPLICATION_JSON)
```

如果你希望所有的期望都被断言,即使其中一个失败了,你可以使用`expectAll(..)`,而不是使用多个链接的`expect*(..)`调用。此功能类似于 AssertJ 中的*软断言*支持和 JUnit Jupiter 中的`assertAll()`支持。

爪哇

```
client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectAll(
        spec -> spec.expectStatus().isOk(),
        spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
    );
```

然后,你可以选择通过以下方式之一对响应体进行解码:

* `expectBody(Class<T>)`:解码为单个对象。

* `expectBodyList(Class<T>)`:将对象解码并收集到`List<T>`

* `expectBody()`:将`byte[]`或空体解码为[JSON 内容](#webtestclient-json)

并在生成的较高级别对象上执行断言:

爪哇

```
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList(Person.class).hasSize(3).contains(person);
```

Kotlin

```
import org.springframework.test.web.reactive.server.expectBodyList

client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList<Person>().hasSize(3).contains(person)
```

如果内置断言不足,则可以使用该对象并执行任何其他断言:

爪哇

```
import org.springframework.test.web.reactive.server.expectBody

client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .consumeWith(result -> {
            // custom assertions (e.g. AssertJ)...
        });
```

Kotlin

```
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody<Person>()
        .consumeWith {
            // custom assertions (e.g. AssertJ)...
        }
```

或者,你可以退出工作流并获得`EntityExchangeResult`:

爪哇

```
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();
```

Kotlin

```
import org.springframework.test.web.reactive.server.expectBody

val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk
        .expectBody<Person>()
        .returnResult()
```

|   |当你需要用泛型解码到目标类型时,请查找接受[<br/>](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/parameterizedtyreference.html)而不是<gtr="2502"/>)的重载方法<gtr=“2503”。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### 无内容

如果预期响应不包含内容,则可以断言如下:

爪哇

```
client.post().uri("/persons")
        .body(personMono, Person.class)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty();
```

Kotlin

```
client.post().uri("/persons")
        .bodyValue(person)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty()
```

如果你想忽略响应内容,那么下面的内容将在没有任何断言的情况下发布:

爪哇

```
client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound()
        .expectBody(Void.class);
```

Kotlin

```
client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound
        .expectBody<Unit>()
```

##### JSON 内容

你可以在没有目标类型的情况下使用`expectBody()`对原始内容执行断言,而不是通过更高级别的对象。

要用[JSONAssert](https://jsonassert.skyscreamer.org)验证完整的 JSON 内容:

爪哇

```
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")
```

Kotlin

```
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")
```

要用[JSONPath](https://github.com/jayway/JsonPath)验证 JSON 内容:

爪哇

```
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason");
```

Kotlin

```
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason")
```

##### 流式响应

要测试可能无限的流,例如`"text/event-stream"``"application/x-ndjson"`,首先要验证响应状态和头,然后获得`FluxExchangeResult`:

爪哇

```
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult(MyEvent.class);
```

Kotlin

```
import org.springframework.test.web.reactive.server.returnResult

val result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult<MyEvent>()
```

现在,你可以使用`StepVerifier`中的`reactor-test`来使用响应流了:

爪哇

```
Flux<Event> eventFlux = result.getResponseBody();

StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith(p -> ...)
        .thenCancel()
        .verify();
```

Kotlin

```
val eventFlux = result.getResponseBody()

StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith { p -> ... }
        .thenCancel()
        .verify()
```

##### MockMVC 断言

`WebTestClient`是一个 HTTP 客户机,因此它只能验证客户机响应中的内容,包括状态、头和主体。

在使用 mockMVC 服务器设置测试 Spring MVC 应用程序时,你有额外的选择来对服务器响应执行进一步的断言。要做到这一点,首先要在断言主体之后获得`ExchangeResult`:

爪哇

```
// For a response with a body
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();

// For a response without a body
EntityExchangeResult<Void> result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();
```

Kotlin

```
// For a response with a body
val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();

// For a response without a body
val result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();
```

然后切换到 MockMVC 服务器响应断言:

爪哇

```
MockMvcWebTestClient.resultActionsFor(result)
        .andExpect(model().attribute("integer", 3))
        .andExpect(model().attribute("string", "a string value"));
```

Kotlin

```
MockMvcWebTestClient.resultActionsFor(result)
        .andExpect(model().attribute("integer", 3))
        .andExpect(model().attribute("string", "a string value"));
```

### 3.7.MockMVC

Spring MVC 测试框架,也称为 MockMVC,为测试 Spring MVC 应用程序提供了支持。 Spring 它执行完整的 MVC 请求处理,但通过模拟请求和响应对象,而不是运行中的服务器。

MockMVC 可以单独用于执行请求和验证响应。它也可以通过[WebTestClient](#webtestclient)使用,其中 MockMVC 作为服务器插入以处理请求。`WebTestClient`的优点是可以选择使用更高级别的对象而不是原始数据,并且可以针对实时服务器切换到完整的端到端 HTTP 测试,并使用相同的测试 API。

#### 3.7.1.概述

你可以通过实例化控制器、向其注入依赖项并调用其方法来编写 Spring MVC 的普通单元测试。然而,这样的测试不会验证请求映射、数据绑定、消息转换、类型转换、验证,也不涉及任何支持`@InitBinder``@ModelAttribute``@ExceptionHandler`的方法。

Spring MVC 测试框架,也称为`MockMvc`,旨在为 Spring MVC 控制器提供更完整的测试,而无需运行服务器。它通过调用`DispacherServlet`并从`spring-test`模块传递[“mock” implementations of the Servlet API](#mock-objects-servlet)来实现这一点,该模块在不运行服务器的情况下复制完整的 Spring MVC 请求处理。

MockMVC 是一个服务器端测试框架,它允许你使用轻量级和有针对性的测试来验证 Spring MVC 应用程序的大部分功能。你可以单独使用它来执行请求和验证响应,也可以通过[WebTestClient](#webtestclient)API 使用它,并将 MockMVC 插入为服务器来处理请求。

##### 静态导入

当直接使用 MockMVC 来执行请求时,你将需要用于以下方面的静态导入:

* `MockMvcBuilders.*`

* `MockMvcRequestBuilders.*`

* `MockMvcResultMatchers.*`

* `MockMvcResultHandlers.*`

记住这一点的一个简单方法是搜索`MockMvc*`。如果使用 Eclipse,请确保在 Eclipse 首选项中也添加上面的“最喜欢的静态成员”。

当通过[WebTestClient](#webtestclient)使用 mockmvc 时,不需要静态导入。`WebTestClient`提供了一个没有静态导入的 Fluent API。

##### 设置选项

MockMVC 可以通过以下两种方式之一设置。一种方法是直接指向你想要测试的控制器,并以编程方式配置 Spring MVC 基础设施。第二个是指向具有 Spring MVC 和控制器基础设施的 Spring 配置。

要设置用于测试特定控制器的 mockmvc,请使用以下方法:

爪哇

```
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }

    // ...

}
```

Kotlin

```
class MyWebTests {

    lateinit var mockMvc : MockMvc

    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
    }

    // ...

}
```

或者,你也可以在通过[WebTestClient](#webtestclient-controller-config)进行测试时使用此设置,该设置将委托给与上面所示相同的构建器。

要通过 Spring 配置设置 mockmvc,请使用以下方法:

爪哇

```
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    // ...

}
```

Kotlin

```
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {

    lateinit var mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}
```

或者,你也可以在通过[WebTestClient](#webtestclient-context-config)进行测试时使用此设置,该设置将委托给与上面所示相同的构建器。

你应该使用哪个设置选项?

`webAppContextSetup`加载你实际的 Spring MVC 配置,从而产生一个更完整的集成测试。由于 TestContext 框架缓存了加载的 Spring 配置,因此它有助于保持测试快速运行,即使你在测试套件中引入了更多的测试。此外,还可以通过 Spring 配置将模拟服务注入控制器,以保持对 Web 层的重点测试。下面的示例使用 Mockito 声明了一个模拟服务:

```
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="org.example.AccountService"/>
</bean>
```

然后,你可以将模拟服务注入到测试中,以设置和验证你的期望,如下例所示:

爪哇

```
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {

    @Autowired
    AccountService accountService;

    MockMvc mockMvc;

    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    // ...

}
```

Kotlin

```
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {

    @Autowired
    lateinit var accountService: AccountService

    lateinit mockMvc: MockMvc

    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }

    // ...

}
```

另一方面,`standaloneSetup`更接近于单位检验。它一次测试一个控制器。你可以手动为控制器注入模拟依赖项,并且它不涉及加载 Spring 配置。这样的测试更多地关注于样式,并使其更容易地看到正在测试的控制器,是否需要任何特定的 MVC 配置来工作,等等。`standaloneSetup`也是编写临时测试以验证特定行为或调试问题的一种非常方便的方法。

与大多数“集成与单元测试”的辩论一样,没有正确或错误的答案。然而,使用`standaloneSetup`确实意味着需要额外的`webAppContextSetup`测试,以便验证你的 Spring MVC 配置。或者,你可以使用`webAppContextSetup`编写所有测试,以便始终根据实际的 Spring MVC 配置进行测试。

##### 设置功能

无论你使用哪个 MockMVC Builder,所有`MockMvcBuilder`实现都提供了一些常见且非常有用的特性。例如,你可以为所有请求声明一个`Accept`头,并期望在所有响应中的状态为 200,以及`Content-Type`头,如下所示:

爪哇

```
// static import of MockMvcBuilders.standaloneSetup

MockMvc mockMvc = standaloneSetup(new MusicController())
    .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build();
```

Kotlin

```
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
```

此外,第三方框架(和应用程序)可以预先打包设置指令,例如`MockMvcConfigurer`中的设置指令。 Spring 框架有一个这样的内置实现,该实现有助于跨请求保存和重用 HTTP 会话。你可以按以下方式使用它:

爪哇

```
// static import of SharedHttpSessionConfigurer.sharedHttpSession

MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
        .apply(sharedHttpSession())
        .build();

// Use mockMvc to perform requests...
```

Kotlin

```
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
```

参见 爪哇doc 的[`ConfigurableMockMvcBuilder`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/test/web/ Servlet/setup/confirablemcmvcbuilder.html),以获得所有 MockMVC Builder 特性的列表,或使用 IDE 探索可用的选项。

##### 执行请求

本节展示了如何单独使用 MockMVC 来执行请求和验证响应。如果通过`WebTestClient`使用 mockmvc,请查看[写作测试](#webtestclient-tests)上的相应部分。

要执行使用任何 HTTP 方法的请求,如以下示例所示:

爪哇

```
// static import of MockMvcRequestBuilders.*

mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
```

Kotlin

```
import org.springframework.test.web.servlet.post

mockMvc.post("/hotels/{id}", 42) {
    accept = MediaType.APPLICATION_JSON
}
```

你还可以执行内部使用`MockMultipartHttpServletRequest`的文件上传请求,这样就不会实际解析多个部分的请求。相反,你必须将其设置为类似于以下示例:

Java

```
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
```

Kotlin

```
import org.springframework.test.web.servlet.multipart

mockMvc.multipart("/doc") {
    file("a1", "ABC".toByteArray(charset("UTF8")))
}
```

你可以用 URI 模板样式指定查询参数,如下例所示:

Java

```
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
```

Kotlin

```
mockMvc.get("/hotels?thing={thing}", "somewhere")
```

还可以添加表示查询或表单参数的 Servlet 请求参数,如下例所示:

Java

```
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
```

Kotlin

```
import org.springframework.test.web.servlet.get

mockMvc.get("/hotels") {
    param("thing", "somewhere")
}
```

如果应用程序代码依赖于 Servlet 请求参数,并且没有显式地检查查询字符串(通常是这种情况),那么使用哪个选项并不重要。但是,请记住,与 URI 模板一起提供的查询参数已经被解码,而通过`param(…​)`方法提供的请求参数预计已经被解码。

在大多数情况下,最好是将上下文路径和 Servlet 路径留在请求 URI 之外。如果必须使用完整的请求 URI 进行测试,请确保相应地设置`contextPath``servletPath`,以使请求映射工作,如下例所示:

Java

```
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
```

Kotlin

```
import org.springframework.test.web.servlet.get

mockMvc.get("/app/main/hotels/{id}") {
    contextPath = "/app"
    servletPath = "/main"
}
```

在前面的示例中,每次执行请求时都要设置`contextPath``servletPath`,这会很麻烦。相反,你可以设置默认的请求属性,如下例所示:

Java

```
class MyWebTests {

    MockMvc mockMvc;

    @BeforeEach
    void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON)).build();
    }
}
```

Kotlin

```
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
```

前面的属性影响通过`MockMvc`实例执行的每个请求。如果在给定的请求中也指定了相同的属性,那么它将重写默认值。这就是为什么默认请求中的 HTTP 方法和 URI 无关紧要的原因,因为它们必须在每个请求中指定。

##### 定义期望

可以通过在执行请求后追加一个或多个`andExpect(..)`调用来定义期望,如下例所示。一旦一种预期落空,就不会再有其他预期了。

Java

```
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
```

Kotlin

```
import org.springframework.test.web.servlet.get

mockMvc.get("/accounts/1").andExpect {
    status().isOk()
}
```

可以通过在执行请求后追加`andExpectAll(..)`来定义多个期望,如下例所示。与`andExpect(..)`相反,`andExpectAll(..)`保证将断言所有提供的期望,并将跟踪和报告所有故障。

Java

```
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

mockMvc.perform(get("/accounts/1")).andExpectAll(
    status().isOk(),
    content().contentType("application/json;charset=UTF-8"));
```

`MockMvcResultMatchers.*`提供了一些期望,其中一些期望进一步嵌套了更详细的期望。

预期分为两大类。第一类断言验证响应的属性(例如,响应状态、标题和内容)。这些是可以断言的最重要的结果。

第二类断言超出了响应范围。这些断言允许你检查 Spring MVC 的特定方面,例如哪个控制器方法处理了请求,是否引发并处理了异常,模型的内容是什么,选择了什么视图,添加了什么 flash 属性,等等。它们还允许你检查 Servlet 特定的方面,例如请求和会话属性。

以下测试断言绑定或验证失败:

Java

```
mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
```

Kotlin

```
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andExpect {
    status().isOk()
    model {
        attributeHasErrors("person")
    }
}
```

很多时候,在编写测试时,转储执行的请求的结果是有用的。你可以这样做,其中`print()`是来自`MockMvcResultHandlers`的静态导入:

Java

```
mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
```

Kotlin

```
import org.springframework.test.web.servlet.post

mockMvc.post("/persons").andDo {
        print()
    }.andExpect {
        status().isOk()
        model {
            attributeHasErrors("person")
        }
    }
```

只要请求处理不会导致未处理的异常,`print()`方法就会将所有可用的结果数据打印到`System.out`。还有一个`log()`方法和`print()`方法的两个附加变体,一个接受`OutputStream`,另一个接受`Writer`。例如,调用`print(System.err)`将结果数据打印到`System.err`,而调用`print(myWriter)`将结果数据打印到自定义写入器。如果希望记录结果数据而不是打印结果,则可以调用`log()`方法,该方法将结果数据记录为`DEBUG`日志类别下的单个`DEBUG`消息。

在某些情况下,你可能希望获得对结果的直接访问,并验证某些在其他情况下无法验证的内容。这可以通过在所有其他期望之后附加`.andReturn()`来实现,如下例所示:

Java

```
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
```

Kotlin

```
var mvcResult = mockMvc.post("/persons").andExpect { status().isOk() }.andReturn()
// ...
```

如果所有测试都重复相同的期望,那么在构建`MockMvc`实例时,可以设置一次公共期望,如下例所示:

Java

```
standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()
```

Kotlin

```
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
```

注意,通常的期望总是被应用的,并且在不创建单独的`MockMvc`实例的情况下不能被重写。

当 JSON 响应内容包含用[Spring HATEOAS](https://github.com/spring-projects/spring-hateoas)创建的超媒体链接时,可以使用 JSONPath 表达式来验证结果链接,如下例所示:

Java

```
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
```

Kotlin

```
mockMvc.get("/people") {
    accept(MediaType.APPLICATION_JSON)
}.andExpect {
    jsonPath("$.links[?(@.rel == 'self')].href") {
        value("http://localhost:8080/people")
    }
}
```

当 XML 响应内容包含用[Spring HATEOAS](https://github.com/spring-projects/spring-hateoas)创建的超媒体链接时,可以使用 XPath 表达式来验证生成的链接:

Java

```
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
```

Kotlin

```
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
    accept(MediaType.APPLICATION_XML)
}.andExpect {
    xpath("/person/ns:link[@rel='self']/@href", ns) {
        string("http://localhost:8080/people")
    }
}
```

##### 异步请求

本节展示了如何单独使用 MockMVC 来测试异步请求处理。如果通过[WebTestClient](#webtestclient)使用 MockMVC,则没有什么特别的事情可以使异步请求工作,因为`WebTestClient`会自动执行本节中所描述的操作。

Servlet 3.0 异步请求[supported in Spring MVC](web.html#mvc-ann-async)通过退出 Servlet 容器线程并允许应用程序异步计算响应来工作,在此之后进行异步分派以在 Servlet 容器线程上完成处理。

Spring 在 MVC 测试中,异步请求可以通过首先断言产生的异步值,然后手动执行异步调度,最后验证响应来进行测试。下面是返回`DeferredResult``Callable`或反应类型(例如反应器`Mono`)的控制器方法的示例测试:

Java

```
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*

@Test
void test() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(get("/path"))
            .andExpect(status().isOk()) (1)
            .andExpect(request().asyncStarted()) (2)
            .andExpect(request().asyncResult("body")) (3)
            .andReturn();

    this.mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect(status().isOk()) (5)
            .andExpect(content().string("body"));
}
```

|**1**|检查响应状态仍未更改|
|-----|---------------------------------------------------------------------|
|**2**|异步处理肯定已经启动了。|
|**3**|等待并断言异步结果|
|**4**|手动执行异步分派(因为没有正在运行的容器)|
|**5**|验证最终响应|

Kotlin

```
@Test
fun test() {
    var mvcResult = mockMvc.get("/path").andExpect {
        status().isOk() (1)
        request { asyncStarted() } (2)
        // TODO Remove unused generic parameter
        request { asyncResult<Nothing>("body") } (3)
    }.andReturn()

    mockMvc.perform(asyncDispatch(mvcResult)) (4)
            .andExpect {
                status().isOk() (5)
                content().string("body")
            }
}
```

|**1**|检查响应状态仍未更改|
|-----|---------------------------------------------------------------------|
|**2**|异步处理肯定已经启动了。|
|**3**|等待并断言异步结果|
|**4**|手动执行异步分派(因为没有正在运行的容器)|
|**5**|验证最终响应|

##### 流式响应

测试流响应(例如服务器发送的事件)的最佳方法是通过[WebTestClient](#webtestclient),它可以用作测试客户端,以连接到`MockMvc`实例,从而在 Spring MVC 控制器上执行测试,而无需运行服务器。例如:

爪哇

```
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();

FluxExchangeResult<Person> exchangeResult = client.get()
        .uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType("text/event-stream")
        .returnResult(Person.class);

// Use StepVerifier from Project Reactor to test the streaming response

StepVerifier.create(exchangeResult.getResponseBody())
        .expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
        .expectNextCount(4)
        .consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
        .thenCancel()
        .verify();
```

`WebTestClient`还可以连接到活动服务器并执行完整的端到端集成测试。这在 Spring boot 中也是支持的,在这里你可以[测试正在运行的服务器](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server)

##### 过滤注册

在设置`MockMvc`实例时,可以注册一个或多个 Servlet `Filter`实例,如下例所示:

爪哇

```
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
```

Kotlin

```
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
```

通过`spring-test`中的`MockFilterChain`调用注册过滤器,最后一个过滤器将委托给`DispatcherServlet`

##### MockMVC 与端到端测试

MockMVC 是基于来自`spring-test`模块的 Servlet API 模拟实现构建的,并且不依赖于正在运行的容器。因此,与实际运行客户机和实时服务器的完整端到端集成测试相比,存在一些差异。

思考这个问题最简单的方法是从空白`MockHttpServletRequest`开始。无论你向它添加什么,请求都会变成什么。可能会让你感到惊讶的是,默认情况下没有上下文路径;没有`jsessionid`cookie;没有转发、错误或异步分派;因此,没有实际的 JSP 呈现。相反,“转发”和“重定向”URL 保存在`MockHttpServletResponse`中,并且可以在期望的情况下断言。

这意味着,如果使用 JSP,你可以验证请求被转发到的 JSP 页面,但不呈现 HTML。换句话说,不会调用 JSP。但是,请注意,所有不依赖于转发的其他呈现技术,例如 ThymeLeaf 和 FreeMarker,都会按照预期的那样将 HTML 呈现给响应主体。对于通过`@ResponseBody`方法呈现 JSON、XML 和其他格式,也是如此。

或者,你可以考虑使用`@SpringBootTest`从 Spring 启动的完整的端到端集成测试支持。见[Spring Boot Reference Guide](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing)

每种方法都有优点和缺点。 Spring MVC 测试中提供的选项是从经典单元测试到完全集成测试的不同规模的停止。可以肯定的是, Spring MVC 测试中的所有选项都不属于经典单元测试的范畴,但它们更接近于经典单元测试。例如,你可以通过将模拟的服务注入控制器来隔离 Web 层,在这种情况下,你只能通过`DispatcherServlet`来测试 Web 层,但要使用实际的 Spring 配置,因为你可能会在与上面的层隔离的情况下测试数据访问层。此外,你还可以使用独立设置,一次只关注一个控制器,并手动提供使其工作所需的配置。

使用 Spring MVC 测试时的另一个重要区别是,从概念上讲,这样的测试是服务器端的,因此你可以检查使用了什么处理程序,如果异常是用 HandleRexCeptionResolver 处理的,模型的内容是什么,有哪些绑定错误,以及其他细节。这意味着更容易编写预期值,因为服务器不是一个不透明的框,就像通过实际的 HTTP 客户机进行测试时一样。这通常是经典单元测试的一个优势:它更容易编写、推理和调试,但不会取代对完全集成测试的需求。同时,重要的是不要忽视这样一个事实,即反应是最重要的检查事项。简而言之,即使在同一个项目中,这里也有多种测试风格和策略的空间。

##### 进一步的例子

该框架自己的测试包括[许多样本测试](https://github.com/spring-projects/spring-framework/tree/main/spring-test/src/test/java/org/springframework/test/web/servlet/samples),旨在展示如何单独或通过[WebTestClient](https://github.com/spring-projects/spring-framework/tree/main/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client)使用 MockMVC。浏览这些示例以获得更多的想法。

#### 3.7.2.htmlunit 集成

Spring 提供[MockMvc](#spring-mvc-test-server)[HtmlUnit](http://htmlunit.sourceforge.net/)之间的集成。这简化了在使用基于 HTML 的视图时执行端到端测试的过程。这种集成使你能够:

* 通过使用[HtmlUnit](http://htmlunit.sourceforge.net/)[WebDriver](https://www.seleniumhq.org)[Geb](http://www.gebish.org/manual/current/#spock-junit-testng)等工具轻松地测试 HTML 页面,而无需部署到 Servlet 容器。

* 在页面中测试 爪哇Script。

* 还可以选择使用模拟服务进行测试,以加快测试速度。

* 在容器内端到端测试和容器外集成测试之间共享逻辑。

|   |MockMVC 使用不依赖于 Servlet 容器<br/>的模板化技术(例如,ThymeLeaf、FreeMarker 和其他),但不适用于 JSP,因为<br/>它们依赖于 Servlet 容器。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### 为什么要进行 HTMLUnit 集成?

我脑海中浮现的最明显的问题是“我为什么需要这个?”最好通过探索一个非常基本的示例应用程序来找到答案。假设你有一个 Spring MVC Web 应用程序,该应用程序在`Message`对象上支持增删改查操作。该应用程序还支持对所有消息进行分页。你会如何去测试它呢?

通过 Spring MVC 测试,我们可以很容易地测试是否能够创建`Message`,如下所示:

爪哇

```
MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param("summary", "Spring Rocks")
        .param("text", "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
```

Kotlin

```
@Test
fun test() {
    mockMvc.post("/messages/") {
        param("summary", "Spring Rocks")
        param("text", "In case you didn't know, Spring Rocks!")
    }.andExpect {
        status().is3xxRedirection()
        redirectedUrl("/messages/123")
    }
}
```

如果我们想测试允许我们创建消息的窗体视图,该怎么办?例如,假设我们的表单看起来像以下片段:

```
<form id="messageForm" action="/messages/" method="post">
    <div class="pull-right"><a href="/messages/">Messages</a></div>

    <label for="summary">Summary</label>
    <input type="text" class="required" id="summary" name="summary" value="" />

    <label for="text">Message</label>
    <textarea id="text" name="text"></textarea>

    <div class="form-actions">
        <input type="submit" value="Create" />
    </div>
</form>
```

我们如何确保我们的表单产生正确的请求来创建新消息?一次幼稚的尝试可能类似于以下几点:

爪哇

```
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());
```

Kotlin

```
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='summary']") { exists() }
    xpath("//textarea[@name='text']") { exists() }
}
```

这种测试有一些明显的缺点。如果我们更新控制器以使用参数`message`而不是`text`,那么我们的表单测试将继续通过,即使 HTML 表单与控制器不同步。为了解决这个问题,我们可以将两个测试结合起来,如下所示:

爪哇

```
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
        .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());

MockHttpServletRequestBuilder createMessage = post("/messages/")
        .param(summaryParamName, "Spring Rocks")
        .param(textParamName, "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
        .andExpect(status().is3xxRedirection())
        .andExpect(redirectedUrl("/messages/123"));
```

Kotlin

```
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='$summaryParamName']") { exists() }
    xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
    param(summaryParamName, "Spring Rocks")
    param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
    status().is3xxRedirection()
    redirectedUrl("/messages/123")
}
```

这将减少我们的测试不正确通过的风险,但仍然存在一些问题:

* 如果我们的页面上有多个表单怎么办?诚然,我们可以更新 XPath 表达式,但是随着我们考虑更多因素,它们变得更加复杂:字段是正确的类型吗?字段启用了吗?以此类推。

* 另一个问题是,我们正在做的工作是预期的两倍。我们必须首先验证视图,然后使用刚刚验证过的相同参数提交视图。理想情况下,这可以一次完成。

* 最后,我们仍然无法解释某些事情。例如,如果表单也有我们希望测试的 爪哇Script 验证呢?

总的问题是,测试一个 Web 页面并不涉及一个单独的交互。相反,它是用户如何与 Web 页面交互以及该 Web 页面如何与其他资源交互的组合。例如,表单视图的结果被用作用户创建消息的输入。此外,我们的表单视图可能会使用影响页面行为的额外资源,例如 爪哇Script 验证。

###### 整合测试的拯救?

为了解决前面提到的问题,我们可以执行端到端集成测试,但这有一些缺点。考虑测试这个视图,它可以让我们在消息中进行页面浏览。我们可能需要进行以下测试:

* 我们的页面是否向用户显示通知,以表明当消息为空时没有可用的结果?

* 我们的页面是否正确地显示了一条消息?

* 我们的页面是否适当地支持分页?

要设置这些测试,我们需要确保我们的数据库包含正确的消息。这导致了一些额外的挑战:

* 确保数据库中有正确的消息是很乏味的。(考虑一下国外的关键限制。

* 测试可能会变得很慢,因为每个测试都需要确保数据库处于正确的状态。

* 由于我们的数据库需要处于特定的状态,因此我们不能并行运行测试。

* 对自动生成的 ID、时间戳等项执行断言可能很困难。

这些挑战并不意味着我们应该完全放弃端到端集成测试。相反,我们可以通过重构详细的测试,使用运行速度更快、更可靠且没有副作用的模拟服务,来减少端到端集成测试的数量。然后,我们可以实现少量真正的端到端集成测试,这些测试验证简单的工作流,以确保所有工作都正确地结合在一起。

###### 进入 HTMLUnit 集成

那么,我们如何在测试页面的交互和在测试套件中保持良好性能之间实现平衡呢?答案是:“通过整合 MockMVC 和 HTMLUnit。”

###### htmlunit 集成选项

当你想要将 mockmvc 与 htmlunit 集成在一起时,你有许多选项:

* [mockmvc 和 htmlunit](#spring-mvc-test-server-htmlunit-mah):如果你想使用原始的 htmlUnit 库,请使用此选项。

* [MockMVC 和 WebDriver](#spring-mvc-test-server-htmlunit-webdriver):使用此选项可以简化开发,并在集成和端到端测试之间重用代码。

* [MOCKMVC 和 GEB](#spring-mvc-test-server-htmlunit-geb):如果你想使用 Groovy 进行测试、简化开发以及在集成和端到端测试之间重用代码,请使用此选项。

##### mockmvc 和 htmlunit

本节介绍如何集成 MockMVC 和 HTMLUnit。如果你想使用原始的 HTMLUnit 库,请使用此选项。

###### mockmvc 和 htmlunit 设置

首先,确保包含了对`net.sourceforge.htmlunit:htmlunit`的测试依赖项。为了在 Apache HttpComponents4.5+ 中使用 HTMLUnit,你需要使用 HTMLUnit2.18 或更高版本。

通过使用`MockMvcWebClientBuilder`,我们可以轻松地创建一个与 MockMVC 集成的 htmlUnit`WebClient`,如下所示:

爪哇

```
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
```

Kotlin

```
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}
```

|   |这是使用`MockMvcWebClientBuilder`的一个简单示例。关于高级用法,<br/>参见[advanced`MockMvcWebClientBuilder`](# Spring-mvc-test-server-htmlunit-mah-advanced-builder)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

这确保了将`localhost`作为服务器引用的任何 URL 都指向我们的`MockMvc`实例,而不需要真正的 HTTP 连接。正常情况下,通过使用网络连接来请求任何其他 URL。这让我们很容易测试 CDNS 的使用情况。

###### mockmvc 和 htmlunit 的使用

现在,我们可以像通常那样使用 HTMLUnit,但不需要将我们的应用程序部署到 Servlet 容器中。例如,我们可以请求该视图创建带有以下内容的消息:

爪哇

```
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
```

Kotlin

```
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
```

|   |默认的上下文路径是`""`。或者,我们可以指定上下文路径,<br/>,如[advanced`MockMvcWebClientBuilder`](# Spring-mvc-test-server-htmlunit-mah-advanced-builder)中所述。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

一旦我们有了对`HtmlPage`的引用,我们就可以填写表单并提交它来创建消息,如下例所示:

爪哇

```
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
```

Kotlin

```
val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()
```

最后,我们可以验证新消息是否已成功创建。以下断言使用[AssertJ](https://assertj.github.io/doc/)库:

爪哇

```
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
```

Kotlin

```
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")
```

前面的代码在许多方面改进了我们的[MockMVC 测试](#spring-mvc-test-server-htmlunit-mock-mvc-test)。首先,我们不再需要显式地验证表单,然后创建一个看起来像表单的请求。相反,我们请求表单,填写它,然后提交它,从而大大减少了开销。

另一个重要的因素是[HTMLUnit 使用 Mozilla Rhino 引擎](http://htmlunit.sourceforge.net/javascript.html)来评估 爪哇Script。这意味着我们还可以在页面中测试 爪哇Script 的行为。

有关使用 htmlunit 的更多信息,请参见[htmlunit 文档](http://htmlunit.sourceforge.net/gettingStarted.html)

###### 高级`MockMvcWebClientBuilder`

在到目前为止的示例中,我们已经以尽可能简单的方式使用`MockMvcWebClientBuilder`,通过基于 Spring TestContext 框架为我们加载的`WebApplicationContext`构建`WebClient`。下面的示例重复了这种方法:

爪哇

```
WebClient webClient;

@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
```

Kotlin

```
lateinit var webClient: WebClient

@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}
```

我们还可以指定其他配置选项,如下例所示:

爪哇

```
WebClient webClient;

@BeforeEach
void setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
}
```

Kotlin

```
lateinit var webClient: WebClient

@BeforeEach
fun setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates applying a MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build()
}
```

作为一种选择,我们可以通过分别配置`MockMvc`实例并将其提供给`MockMvcWebClientBuilder`来执行完全相同的设置,如下所示:

爪哇

```
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
```

Kotlin

```
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
```

这更详细,但是,通过使用`MockMvc`实例构建`WebClient`实例,我们可以在指尖获得 MockMVC 的全部功能。

|   |有关创建`MockMvc`实例的更多信息,请参见[设置选项](#spring-mvc-test-server-setup-options)。|
|---|-----------------------------------------------------------------------------------------------------------------------|

##### MockMVC 和 WebDriver

在前面的部分中,我们已经了解了如何将 MockMVC 与原始的 HTMLUnitAPI 结合使用。在本节中,我们在 Selenium[WebDriver](https://docs.seleniumhq.org/projects/webdriver/)中使用了额外的抽象,以使事情变得更简单。

###### 为什么是 Webdriver 和 MockMVC?

我们已经可以使用 HTMLUnit 和 MockMVC 了,那么为什么我们要使用 WebDriver 呢?Selenium WebDriver 提供了一个非常优雅的 API,可以让我们轻松地组织代码。为了更好地展示它是如何工作的,我们将在本节中探讨一个示例。

|   |尽管是[Selenium](https://docs.seleniumhq.org/)的一部分,WebDriver 并不需要<br/>的 Selenium 服务器来运行测试。|
|---|-------------------------------------------------------------------------------------------------------------------------------------|

假设我们需要确保消息是正确创建的。测试包括查找 HTML 表单输入元素,填写它们,并做出各种断言。

这种方法会导致许多单独的测试,因为我们也希望测试错误条件。例如,如果我们只填写表单的一部分,我们希望确保得到一个错误。如果我们填写了整个表单,那么新创建的消息将在之后显示。

如果其中一个字段被命名为“Summary”,那么我们可能会在测试中的多个地方重复类似于以下内容的内容:

爪哇

```
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
```

Kotlin

```
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
```

那么,如果我们将`id`更改为`smmry`会发生什么呢?这样做将迫使我们更新所有的测试,以纳入这一变化。这违反了 dry 原则,因此我们最好将该代码提取到它自己的方法中,如下所示:

爪哇

```
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
    setSummary(currentPage, summary);
    // ...
}

public void setSummary(HtmlPage currentPage, String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
}
```

Kotlin

```
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
    setSummary(currentPage, summary);
    // ...
}

fun setSummary(currentPage:HtmlPage , summary: String) {
    val summaryInput = currentPage.getHtmlElementById("summary")
    summaryInput.setValueAttribute(summary)
}
```

这样做可以确保在更改 UI 时不需要更新所有的测试。

我们甚至可以更进一步,将这个逻辑放在一个`Object`中,该逻辑表示我们当前所在的`HtmlPage`,如下例所示:

爪哇

```
public class CreateMessagePage {

    final HtmlPage currentPage;

    final HtmlTextInput summaryInput;

    final HtmlSubmitInput submit;

    public CreateMessagePage(HtmlPage currentPage) {
        this.currentPage = currentPage;
        this.summaryInput = currentPage.getHtmlElementById("summary");
        this.submit = currentPage.getHtmlElementById("submit");
    }

    public <T> T createMessage(String summary, String text) throws Exception {
        setSummary(summary);

        HtmlPage result = submit.click();
        boolean error = CreateMessagePage.at(result);

        return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
    }

    public void setSummary(String summary) throws Exception {
        summaryInput.setValueAttribute(summary);
    }

    public static boolean at(HtmlPage page) {
        return "Create Message".equals(page.getTitleText());
    }
}
```

Kotlin

```
    class CreateMessagePage(private val currentPage: HtmlPage) {

        val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")

        val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")

        fun <T> createMessage(summary: String, text: String): T {
            setSummary(summary)

            val result = submit.click()
            val error = at(result)

            return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
        }

        fun setSummary(summary: String) {
            summaryInput.setValueAttribute(summary)
        }

        fun at(page: HtmlPage): Boolean {
            return "Create Message" == page.getTitleText()
        }
    }
}
```

以前,这种模式被称为[页面对象模式](https://github.com/SeleniumHQ/selenium/wiki/PageObjects)。虽然我们可以通过 HTMLUnit 实现这一点,但 WebDriver 提供了一些工具,我们将在下面的小节中对这些工具进行探讨,以使这种模式更容易实现。

###### MockMVC 和 WebDriver 设置

要在 Spring MVC 测试框架中使用 Selenium WebDriver,请确保你的项目包含对`org.seleniumhq.selenium:selenium-htmlunit-driver`的测试依赖关系。

我们可以通过使用`MockMvcHtmlUnitDriverBuilder`很容易地创建与 MockMVC 集成的 Selenium WebDriver,如下例所示:

爪哇

```
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
```

Kotlin

```
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}
```

|   |这是使用`MockMvcHtmlUnitDriverBuilder`的一个简单示例。有关更高级的<br/>用法,请参见[advanced`MockMvcHtmlUnitDriverBuilder`](# Spring-mvc-test-server-htmlunit-webdriver-advanced-builder)。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

前面的示例确保将引用`localhost`作为服务器的任何 URL 都指向我们的`MockMvc`实例,而不需要真正的 HTTP 连接。正常情况下,通过使用网络连接来请求任何其他 URL。这让我们很容易测试 CDNS 的使用情况。

###### MockMVC 和 WebDriver 的使用

现在,我们可以像通常那样使用 WebDriver,但不需要将我们的应用程序部署到 Servlet 容器中。例如,我们可以请求该视图创建带有以下内容的消息:

爪哇

```
CreateMessagePage page = CreateMessagePage.to(driver);
```

Kotlin

```
val page = CreateMessagePage.to(driver)
```

然后,我们可以填写表单并提交它来创建一条消息,如下所示:

爪哇

```
ViewMessagePage viewMessagePage =
        page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
```

Kotlin

```
val viewMessagePage =
    page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
```

通过利用页面对象模式,这改进了[htmlunit 测试](#spring-mvc-test-server-htmlunit-mah-usage)的设计。正如我们在[为什么是 Webdriver 和 MockMVC?](#spring-mvc-test-server-htmlunit-webdriver-why)中提到的,我们可以在 HTMLUnit 中使用 Page 对象模式,但是使用 WebDriver 要容易得多。考虑以下`CreateMessagePage`实现:

爪哇

```
public class CreateMessagePage
        extends AbstractPage { (1)

    (2)
    private WebElement summary;
    private WebElement text;

    (3)
    @FindBy(css = "input[type=submit]")
    private WebElement submit;

    public CreateMessagePage(WebDriver driver) {
        super(driver);
    }

    public <T> T createMessage(Class<T> resultPage, String summary, String details) {
        this.summary.sendKeys(summary);
        this.text.sendKeys(details);
        this.submit.click();
        return PageFactory.initElements(driver, resultPage);
    }

    public static CreateMessagePage to(WebDriver driver) {
        driver.get("http://localhost:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}
```

|**1**|`CreateMessagePage`扩展了`AbstractPage`。我们没有讨论`AbstractPage`的详细信息,但是,总而言之,它包含了我们所有页面的通用功能。<br/>例如,如果我们的应用程序具有导航栏、全局错误消息和其他<br/>功能,我们可以将此逻辑放置在共享位置。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|对于 HTML 页面中我们感兴趣的每个部分,我们都有一个成员变量<br/>。这些是`WebElement`型。Webdriver 的[`PageFactory`](https://github.com/seleniumhq/selenium/wiki/pageFactory)让我们通过自动解析`CreateMessagePage`每个`WebElement`,从 htmlunit 版本的`CreateMessagePage`中删除大量代码。[`PageFactory#initElements(WebDriver,Class<T>)`](https://seleniumhq.github.io/selenium/DOCS/api/java/org/openqa/selenium/support/pageFactory.html#initelements-org.openqa.selenium.webdriver-java.lang.class-)方法通过使用字段名并查找<br/>由页面中元素的`id``name`自动解析每个`WebElement`。|
|**3**|我们可以使用[`@FindBy`注释](https://github.com/seleniumhq/selenium/wiki/pageFactory#making-the-example-work-using-annotations)来覆盖默认的查找行为。我们的示例展示了如何使用`@FindBy`注释来使用`css`选择器(**input[type=submit]**)查找我们的 Submit 按钮。|

Kotlin

```
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)

    (2)
    private lateinit var summary: WebElement
    private lateinit var text: WebElement

    (3)
    @FindBy(css = "input[type=submit]")
    private lateinit var submit: WebElement

    fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
        this.summary.sendKeys(summary)
        text.sendKeys(details)
        submit.click()
        return PageFactory.initElements(driver, resultPage)
    }
    companion object {
        fun to(driver: WebDriver): CreateMessagePage {
            driver.get("http://localhost:9990/mail/messages/form")
            return PageFactory.initElements(driver, CreateMessagePage::class.java)
        }
    }
}
```

|**1**|`CreateMessagePage`扩展了`AbstractPage`。我们没有讨论`AbstractPage`的详细信息,但是,总而言之,它包含了我们所有页面的通用功能。<br/>例如,如果我们的应用程序具有导航栏、全局错误消息和其他<br/>功能,我们可以将此逻辑放置在共享位置。|
|-----|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|对于 HTML 页面中我们感兴趣的每个部分,我们都有一个成员变量<br/>。它们是`WebElement`型。Webdriver 的[`PageFactory`](https://github.com/seleniumhq/selenium/wiki/pageFactory)让我们通过自动解析`WebElement`每个`WebElement`,从 htmlunit 版本的`CreateMessagePage`中删除大量代码。[`PageFactory#initElements(WebDriver,Class<T>)`](https://seleniumhq.github.io/selenium/DOCS/api/java/org/openqa/selenium/support/pageFactory.html#initelements-org.openqa.selenium.webdriver-java.lang.class-)方法通过使用字段名并查找<br/>由页面中元素的`id``name`自动解析每个<gt r=“2696”。|
|**3**|我们可以使用[`@FindBy`注释](https://github.com/seleniumhq/selenium/wiki/pageFactory#making-the-example-work-using-annotations)来覆盖默认的查找行为。我们的示例展示了如何使用`@FindBy`注释来使用`css`选择器(**input[type=submit]**)查找我们的 Submit 按钮。|

最后,我们可以验证新消息是否已成功创建。以下断言使用[AssertJ](https://assertj.github.io/doc/)断言程序库:

爪哇

```
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
```

Kotlin

```
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")
```

我们可以看到,我们的`ViewMessagePage`允许我们与自定义域模型进行交互。例如,它公开了一个返回`Message`对象的方法:

爪哇

```
public Message getMessage() throws ParseException {
    Message message = new Message();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}
```

Kotlin

```
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
```

然后,我们可以在断言中使用富域对象。

最后,当测试完成时,我们一定不要忘记关闭`WebDriver`实例,如下所示:

爪哇

```
@AfterEach
void destroy() {
    if (driver != null) {
        driver.close();
    }
}
```

Kotlin

```
@AfterEach
fun destroy() {
    if (driver != null) {
        driver.close()
    }
}
```

有关使用 WebDriver 的更多信息,请参见 Selenium[WebDriver 文档](https://github.com/SeleniumHQ/selenium/wiki/Getting-Started)

###### 高级`MockMvcHtmlUnitDriverBuilder`

在到目前为止的示例中,我们已经以尽可能简单的方式使用`MockMvcHtmlUnitDriverBuilder`,通过基于 Spring TestContext 框架为我们加载的`WebApplicationContext`构建`WebDriver`。这种方法在此重复如下:

爪哇

```
WebDriver driver;

@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
```

Kotlin

```
lateinit var driver: WebDriver

@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}
```

我们还可以指定其他配置选项,如下所示:

爪哇

```
WebDriver driver;

@BeforeEach
void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build();
}
```

Kotlin

```
lateinit var driver: WebDriver

@BeforeEach
fun setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates applying a MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for illustration only - defaults to ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // the following will use MockMvc for example.com and example.org as well
            .useMockMvcForHosts("example.com","example.org")
            .build()
}
```

作为一种选择,我们可以通过分别配置`MockMvc`实例并将其提供给`MockMvcHtmlUnitDriverBuilder`来执行完全相同的设置,如下所示:

Java

```
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // the following will use MockMvc for example.com and example.org as well
        .useMockMvcForHosts("example.com","example.org")
        .build();
```

Kotlin

```
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
```

这更详细,但是,通过使用`MockMvc`实例构建`WebDriver`,我们可以在指尖获得 MockMVC 的全部功能。

|   |有关创建`MockMvc`实例的更多信息,请参见[设置选项](#spring-mvc-test-server-setup-options)。|
|---|-----------------------------------------------------------------------------------------------------------------------|

##### MOCKMVC 和 GEB

在上一节中,我们了解了如何在 WebDriver 中使用 MockMVC。在这一节中,我们使用[Geb](http://www.gebish.org/)使我们的测试更加 Groovy-er。

###### 为什么是 GEB 和 MOCKMVC?

GEB 由 WebDriver 支持,因此它提供了许多我们从 WebDriver 获得的[相同的好处](#spring-mvc-test-server-htmlunit-webdriver-why)。然而,通过为我们处理一些样板代码,GEB 使事情变得更加简单。

###### mockmvc 和 geb 设置

我们可以使用使用使用 MockMVC 的 Selenium WebDriver 轻松地初始化 GEB`Browser`,如下所示:

```
def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}
```

|   |这是使用`MockMvcHtmlUnitDriverBuilder`的一个简单示例。有关更高级的<br/>用法,请参见[advanced`MockMvcHtmlUnitDriverBuilder`](# Spring-mvc-test-server-htmlunit-webdriver-advanced-builder)。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

这确保将引用`localhost`作为服务器的任何 URL 指向我们的`MockMvc`实例,而不需要真正的 HTTP 连接。正常情况下,通过使用网络连接来请求任何其他 URL。这让我们可以很容易地测试 CDNS 的使用情况。

###### MockMVC 和 GEB 的使用

现在,我们可以像通常那样使用 GEB,但不需要将我们的应用程序部署到 Servlet 容器中。例如,我们可以请求该视图创建带有以下内容的消息:

```
to CreateMessagePage
```

然后,我们可以填写表单并提交它来创建一条消息,如下所示:

```
when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)
```

未找到的任何未识别的方法调用或属性访问或引用都将转发到当前页对象。这删除了我们在直接使用 WebDriver 时所需的大量样板代码。

与直接使用 WebDriver 一样,通过使用页面对象模式,这改进了[htmlunit 测试](#spring-mvc-test-server-htmlunit-mah-usage)的设计。正如前面提到的,我们可以在 HTMLUnit 和 WebDriver 中使用页面对象模式,但是使用 GEB 则更容易。考虑一下我们新的基于 Groovy 的`CreateMessagePage`实现:

```
class CreateMessagePage extends Page {
    static url = 'messages/form'
    static at = { assert title == 'Messages : Create'; true }
    static content =  {
        submit { $('input[type=submit]') }
        form { $('form') }
        errors(required:false) { $('label.error, .alert-error')?.text() }
    }
}
```

我们的`CreateMessagePage`扩展了`Page`。我们不讨论`Page`的详细信息,但总而言之,它包含了我们所有页面的通用功能。我们定义了一个可以在其中找到此页面的 URL。这让我们可以导航到该页面,如下所示:

```
to CreateMessagePage
```

我们还有一个`at`闭包,它确定我们是否在指定的页面上。如果我们在正确的页面上,它应该返回`true`。这就是为什么我们可以断言我们在正确的页面上,如下所示:

```
then:
at CreateMessagePage
errors.contains('This field is required.')
```

|   |我们在闭包中使用断言,这样我们就可以确定问题出在哪里<br/>,如果我们在错误的页面上。|
|---|---------------------------------------------------------------------------------------------------------------------|

接下来,我们创建一个`content`闭包,该闭包指定页面中所有感兴趣的区域。我们可以使用[jQuery-ish Navigator API](http://www.gebish.org/manual/current/#the-jquery-ish-navigator-api)来选择我们感兴趣的内容。

最后,我们可以验证新消息是否已成功创建,如下所示:

```
then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage
```

有关如何最大限度地利用 GEB 的更多详细信息,请参见[GEB 之书](http://www.gebish.org/manual/current/)用户手册。

### 3.8.测试客户端应用程序

你可以使用客户端测试来测试内部使用`RestTemplate`的代码。其思想是声明预期的请求并提供“存根”响应,这样你就可以专注于孤立地测试代码(也就是说,在不运行服务器的情况下)。下面的示例展示了如何做到这一点:

Java

```
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());

// Test code that uses the above RestTemplate ...

mockServer.verify();
```

Kotlin

```
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())

// Test code that uses the above RestTemplate ...

mockServer.verify()
```

在前面的示例中,`MockRestServiceServer`(客户端 REST 测试的中心类)使用自定义的`RestTemplate`配置`ClientHttpRequestFactory`,该自定义配置根据预期断言实际请求并返回“存根”响应。在这种情况下,我们期望请求`/greeting`,并希望返回带有`text/plain`内容的 200 响应。我们可以根据需要定义额外的期望请求和存根响应。当我们定义期望的请求和存根响应时,`RestTemplate`可以像往常一样在客户端代码中使用。在测试结束时,可以使用`mockServer.verify()`来验证所有的期望都已满足。

默认情况下,请求的预期顺序是声明期望的顺序。在构建服务器时,可以设置`ignoreExpectOrder`选项,在这种情况下,将检查所有期望(按顺序)以找到给定请求的匹配项。这意味着请求可以按任何顺序提出。下面的示例使用`ignoreExpectOrder`:

Java

```
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
```

Kotlin

```
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()
```

即使默认情况下是无序的请求,每个请求也只允许运行一次。`expect`方法提供了一个重载变量,该变量接受一个指定计数范围的`ExpectedCount`参数(例如,`once``manyTimes``max``min``between`,等等)。下面的示例使用`times`:

Java

```
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());

// ...

mockServer.verify();
```

Kotlin

```
val restTemplate = RestTemplate()

val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())

// ...

mockServer.verify()
```

请注意,当`ignoreExpectOrder`未设置(默认值),因此,请求是按照声明的顺序进行的,那么该顺序仅适用于任何预期请求中的第一个。例如,如果“/something”预期两次,然后是“/somewhere”三次,那么在向“/somewhere”发出请求之前,应该有一个对“/something”的请求,但是,除了随后的“/something”和“/somewhere”之外,请求可以随时出现。

作为上述所有方法的替代方案,客户端测试支持还提供了`ClientHttpRequestFactory`实现,你可以将其配置为`RestTemplate`,以将其绑定到`MockMvc`实例。这允许使用实际的服务器端逻辑处理请求,但不需要运行服务器。下面的示例展示了如何做到这一点:

Java

```
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));

// Test code that uses the above RestTemplate ...
```

Kotlin

```
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))

// Test code that uses the above RestTemplate ...
```

#### 3.8.1.静态导入

与服务器端测试一样,客户端测试的 Fluent API 需要一些静态导入。搜索`MockRest*`很容易找到。Eclipse 用户应该在 Java Editor Content Assist Favorites 下的 Eclipse 首选项中添加`MockRestRequestMatchers.*``MockRestResponseCreators.*`作为“最喜欢的静态成员”。这允许在输入静态方法名的第一个字符后使用内容辅助。其他 IDE(例如 IntelliJ)可能不需要任何额外的配置。检查对静态成员的代码完成的支持。

#### 3.8.2.客户端 REST 测试的进一步示例

Spring MVC 测试自己的测试包括[示例测试](https://github.com/spring-projects/spring-framework/tree/main/spring-test/src/test/java/org/springframework/test/web/client/samples)客户端 REST 测试。

## 4. 更多资源

有关测试的更多信息,请参见以下参考资料:

* [JUnit](https://www.junit.org/):“一个程序员友好的 Java 测试框架”。 Spring 框架在其测试套件中使用,并在[Spring TestContext Framework](#testcontext-framework)中支持。

* [TestNG](https://testng.org/):受 JUnit 启发的测试框架,增加了对测试组、数据驱动测试、分布式测试和其他特性的支持。在[Spring TestContext Framework](#testcontext-framework)中支持

* [AssertJ](https://assertj.github.io/doc/):“Fluent Assertions for Java”,包括对 Java8Lambdas、Streams 和其他功能的支持。

* [模拟对象](https://en.wikipedia.org/wiki/Mock_Object):维基百科条目。

* [MockObjects.com](http://www.mockobjects.com/):专门用于模拟对象的网站,这是一种在测试驱动开发中改进代码设计的技术。

* [Mockito](https://mockito.github.io):基于[Test Spy](http://xunitpatterns.com/Test%20Spy.html)模式的 Java 模拟库。 Spring 框架在其测试套件中使用。

* [EasyMock](https://easymock.org/):Java 库“通过使用 Java 的代理机制动态生成接口的模拟对象(以及通过类扩展的对象),为接口提供模拟对象。”

* [JMock](https://jmock.org/):支持使用模拟对象对 Java 代码进行测试驱动开发的库。

* [DbUnit](https://www.dbunit.org/):JUnit 扩展(也可用于 Ant 和 Maven),它的目标是数据库驱动的项目,其中包括在测试运行之间使数据库处于已知状态。

* [测试容器](https://www.testcontainers.org/):支持 JUnit 测试的 Java 库,提供通用数据库、Selenium Web 浏览器或任何其他可以在 Docker 容器中运行的轻量级一次性实例。

* [研磨机](https://sourceforge.net/projects/grinder/):Java 负载测试框架。

* [斯普林莫克](https://github.com/Ninja-Squad/springmockk):支持使用[MockK](https://mockk.io/)代替 mockito 编写的 Spring 引导集成测试。