小架構step系列13:測試用例的加載

1 概述

測試用例的編寫要有一些基礎的規范,在本文先定義文件名稱和測試用例方法名的規范。

2 文件加載原理

先從源碼來看一下測試用例的文件加載原理。

2.1 文件的匹配

主要是通過注解來掃描測試用例。

// 在IDEA測試用例啟動時,調用junit-platform-launcher-x.x.x.jar包里的類啟動,最終會調JupiterTestEngine.discover()來加載測試用例
// 估計在不同的地方,啟動的方式可能有所不同,junit-platform-launcher-x.x.x.jar包并沒有引入到工程里
// 源碼位置:org.junit.jupiter.engine.JupiterTestEngine
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {JupiterConfiguration configuration = new CachingJupiterConfiguration(new DefaultJupiterConfiguration(discoveryRequest.getConfigurationParameters()));JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration);// 1. 先在DiscoverySelectorResolver()初始化resolver,然后使用resolver去加載測試用例new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor);return engineDescriptor;
}// 源碼位置:org.junit.jupiter.engine.discovery.DiscoverySelectorResolver
public class DiscoverySelectorResolver {// 初始化多個resolverprivate static final EngineDiscoveryRequestResolver<JupiterEngineDescriptor> resolver = EngineDiscoveryRequestResolver.<JupiterEngineDescriptor>builder()// 2. 設置加載測試用例文件的ClassContainerSelectorResolver,IsTestClassWithTests是最終的過濾類ClassFilter.addClassContainerSelectorResolver(new IsTestClassWithTests()) .addSelectorResolver(context -> new ClassSelectorResolver(context.getClassNameFilter(), context.getEngineDescriptor().getConfiguration())).addSelectorResolver(context -> new MethodSelectorResolver(context.getEngineDescriptor().getConfiguration())).addTestDescriptorVisitor(context -> new ClassOrderingVisitor(context.getEngineDescriptor().getConfiguration())).addTestDescriptorVisitor(context -> new MethodOrderingVisitor(context.getEngineDescriptor().getConfiguration())).addTestDescriptorVisitor(context -> TestDescriptor::prune).build();public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescriptor engineDescriptor) {resolver.resolve(request, engineDescriptor);}
}// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.Builder
public Builder<T> addClassContainerSelectorResolver(Predicate<Class<?>> classFilter) {Preconditions.notNull(classFilter, "classFilter must not be null");// 3. 把ClassFilter包到ClassContainerSelectorResolver里面,這個ClassFilter是IsTestClassWithTestsreturn addSelectorResolver(context -> new ClassContainerSelectorResolver(classFilter, context.getClassNameFilter()));
}
public Builder<T> addSelectorResolver(Function<InitializationContext<T>, SelectorResolver> resolverCreator) {// 4. ClassContainerSelectorResolver最終存儲在resolverCreators變量里resolverCreators.add(resolverCreator);return this;
}// 回到DiscoverySelectorResolver,調用resolveSelectors()
// 源碼位置:org.junit.jupiter.engine.discovery.DiscoverySelectorResolver
public class DiscoverySelectorResolver {// 初始化多個resolverprivate static final EngineDiscoveryRequestResolver<JupiterEngineDescriptor> resolver = EngineDiscoveryRequestResolver.<JupiterEngineDescriptor>builder()// 2. 設置加載測試用例文件的ClassContainerSelectorResolver,IsTestClassWithTests是最終的過濾類ClassFilter.addClassContainerSelectorResolver(new IsTestClassWithTests()) .addSelectorResolver(context -> new ClassSelectorResolver(context.getClassNameFilter(), context.getEngineDescriptor().getConfiguration())).addSelectorResolver(context -> new MethodSelectorResolver(context.getEngineDescriptor().getConfiguration())).addTestDescriptorVisitor(context -> new ClassOrderingVisitor(context.getEngineDescriptor().getConfiguration())).addTestDescriptorVisitor(context -> new MethodOrderingVisitor(context.getEngineDescriptor().getConfiguration())).addTestDescriptorVisitor(context -> TestDescriptor::prune).build();public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescriptor engineDescriptor) {5. 執行resolver.resolve加載測試用例resolver.resolve(request, engineDescriptor);}
}// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver
public void resolve(EngineDiscoveryRequest request, T engineDescriptor) {Preconditions.notNull(request, "request must not be null");Preconditions.notNull(engineDescriptor, "engineDescriptor must not be null");InitializationContext<T> initializationContext = new DefaultInitializationContext<>(request, engineDescriptor);// 6. 初始化resolver,把resolverCreators作為參數傳入List<SelectorResolver> resolvers = instantiate(resolverCreators, initializationContext);List<TestDescriptor.Visitor> visitors = instantiate(visitorCreators, initializationContext);new EngineDiscoveryRequestResolution(request, engineDescriptor, resolvers, visitors).run();
}// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver
private <R> List<R> instantiate(List<Function<InitializationContext<T>, R>> creators, InitializationContext<T> context) {// 7. 遍歷Resolver執行lambda表達式函數,這個函數執行的時候,就是執行Resolver的resolve接口,//    其中重點在于ClassContainerSelectorResolver,里面放了IsTestClassWithTests這個ClassFilter,通過該類進行過濾return creators.stream().map(creator -> creator.apply(context)).collect(toCollection(ArrayList::new));
}// 回到EngineDiscoveryRequestResolver
// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver
public void resolve(EngineDiscoveryRequest request, T engineDescriptor) {Preconditions.notNull(request, "request must not be null");Preconditions.notNull(engineDescriptor, "engineDescriptor must not be null");InitializationContext<T> initializationContext = new DefaultInitializationContext<>(request, engineDescriptor);// 6. 初始化resolver,把resolverCreators作為參數傳入List<SelectorResolver> resolvers = instantiate(resolverCreators, initializationContext);List<TestDescriptor.Visitor> visitors = instantiate(visitorCreators, initializationContext);// 8. 執行resolvernew EngineDiscoveryRequestResolution(request, engineDescriptor, resolvers, visitors).run();
}// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution
void run() {remainingSelectors.addAll(request.getSelectorsByType(DiscoverySelector.class));while (!remainingSelectors.isEmpty()) {// 9. 執行,參數為ClasspathRootSelector,里面放了測試用例class文件的根目錄,如果是IDEA環境則是target/test-classes目錄resolveCompletely(remainingSelectors.poll());}visitors.forEach(engineDescriptor::accept);
}
private void resolveCompletely(DiscoverySelector selector) {EngineDiscoveryListener discoveryListener = request.getDiscoveryListener();UniqueId engineId = engineDescriptor.getUniqueId();try {// 10. 執行,參數為ClasspathRootSelectorOptional<Resolution> result = resolve(selector);if (result.isPresent()) {discoveryListener.selectorProcessed(engineId, selector, resolved());enqueueAdditionalSelectors(result.get());}else {discoveryListener.selectorProcessed(engineId, selector, unresolved());}}catch (Throwable t) {UnrecoverableExceptions.rethrowIfUnrecoverable(t);discoveryListener.selectorProcessed(engineId, selector, failed(t));}
}// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution
private Optional<Resolution> resolve(DiscoverySelector selector) {if (resolvedSelectors.containsKey(selector)) {return Optional.of(resolvedSelectors.get(selector));}if (selector instanceof UniqueIdSelector) {return resolveUniqueId((UniqueIdSelector) selector);}// 11. 執行resolver,重點關注ClassContainerSelectorResolverreturn resolve(selector, resolver -> {Context context = getContext(selector);if (selector instanceof ClasspathResourceSelector) {return resolver.resolve((ClasspathResourceSelector) selector, context);}if (selector instanceof ClasspathRootSelector) {return resolver.resolve((ClasspathRootSelector) selector, context);}if (selector instanceof ClassSelector) {return resolver.resolve((ClassSelector) selector, context);}if (selector instanceof NestedClassSelector) {return resolver.resolve((NestedClassSelector) selector, context);}if (selector instanceof DirectorySelector) {return resolver.resolve((DirectorySelector) selector, context);}if (selector instanceof FileSelector) {return resolver.resolve((FileSelector) selector, context);}if (selector instanceof MethodSelector) {return resolver.resolve((MethodSelector) selector, context);}if (selector instanceof NestedMethodSelector) {return resolver.resolve((NestedMethodSelector) selector, context);}if (selector instanceof ModuleSelector) {return resolver.resolve((ModuleSelector) selector, context);}if (selector instanceof PackageSelector) {return resolver.resolve((PackageSelector) selector, context);}if (selector instanceof UriSelector) {return resolver.resolve((UriSelector) selector, context);}return resolver.resolve(selector, context);});
}
private Optional<Resolution> resolve(DiscoverySelector selector,Function<SelectorResolver, Resolution> resolutionFunction) {// 12. resolutionFunction是第11步的lambda表達式return resolvers.stream().map(resolutionFunction).filter(Resolution::isResolved).findFirst() // 觸發lambda表達式執行.map(resolution -> {contextBySelector.remove(selector);resolvedSelectors.put(selector, resolution);resolution.getMatches().forEach(match -> resolvedUniqueIds.put(match.getTestDescriptor().getUniqueId(), match));return resolution;});
}// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution
private Optional<Resolution> resolve(DiscoverySelector selector) {if (resolvedSelectors.containsKey(selector)) {return Optional.of(resolvedSelectors.get(selector));}if (selector instanceof UniqueIdSelector) {return resolveUniqueId((UniqueIdSelector) selector);}// 11. 執行resolver,重點關注ClassContainerSelectorResolverreturn resolve(selector, resolver -> {Context context = getContext(selector);if (selector instanceof ClasspathResourceSelector) {return resolver.resolve((ClasspathResourceSelector) selector, context);}if (selector instanceof ClasspathRootSelector) {// 13. 執行resolver的resolve()方法return resolver.resolve((ClasspathRootSelector) selector, context);}if (selector instanceof ClassSelector) {return resolver.resolve((ClassSelector) selector, context);}if (selector instanceof NestedClassSelector) {return resolver.resolve((NestedClassSelector) selector, context);}if (selector instanceof DirectorySelector) {return resolver.resolve((DirectorySelector) selector, context);}if (selector instanceof FileSelector) {return resolver.resolve((FileSelector) selector, context);}if (selector instanceof MethodSelector) {return resolver.resolve((MethodSelector) selector, context);}if (selector instanceof NestedMethodSelector) {return resolver.resolve((NestedMethodSelector) selector, context);}if (selector instanceof ModuleSelector) {return resolver.resolve((ModuleSelector) selector, context);}if (selector instanceof PackageSelector) {return resolver.resolve((PackageSelector) selector, context);}if (selector instanceof UriSelector) {return resolver.resolve((UriSelector) selector, context);}return resolver.resolve(selector, context);});
}// 源碼位置:org.junit.platform.engine.support.discovery.ClassContainerSelectorResolver
public Resolution resolve(ClasspathRootSelector selector, Context context) {return classSelectors(findAllClassesInClasspathRoot(selector.getClasspathRoot(), classFilter, classNameFilter));
}
private SelectorResolver.Resolution classSelectors(List<Class<?>> classes) {return classes.isEmpty() ? Resolution.unresolved() : Resolution.selectors((Set)classes.stream().map(DiscoverySelectors::selectClass).collect(Collectors.toSet()));
}
public static List<Class<?>> findAllClassesInClasspathRoot(URI root, Predicate<Class<?>> classFilter, Predicate<String> classNameFilter) {// 14. classFilter即前面提供的IsTestClassWithTests,查找指定目錄(root)的類文件,并通過classFilter過濾//     ReflectionUtils里使用classpathScanner掃描測試用例文件,調用classFilter的test()方法進行過濾return ReflectionUtils.findAllClassesInClasspathRoot(root, classFilter, classNameFilter);
}
// 源碼位置:org.junit.platform.commons.util.ReflectionUtils
public static List<Class<?>> findAllClassesInClasspathRoot(URI root, Predicate<Class<?>> classFilter, Predicate<String> classNameFilter) {return findAllClassesInClasspathRoot(root, ClassFilter.of(classNameFilter, classFilter));
}
public static List<Class<?>> findAllClassesInClasspathRoot(URI root, ClassFilter classFilter) {return Collections.unmodifiableList(classpathScanner.scanForClassesInClasspathRoot(root, classFilter));
}// 源碼位置:org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests
public class IsTestClassWithTests implements Predicate<Class<?>> {// 根據方法上有@Test注解過濾private static final IsTestMethod isTestMethod = new IsTestMethod();// 根據有@TestFactory注解過濾private static final IsTestFactoryMethod isTestFactoryMethod = new IsTestFactoryMethod();// 根據有@TestTemplate注解過濾private static final IsTestTemplateMethod isTestTemplateMethod = new IsTestTemplateMethod();// 15. 執行過濾public boolean test(Class<?> candidate) {return isPotentialTestContainer.test(candidate)&& (hasTestOrTestFactoryOrTestTemplateMethods(candidate) || hasNestedTests(candidate));}// 省略其它代碼
}// 源碼位置:org.junit.jupiter.engine.discovery.predicates.IsTestMethod
public class IsTestMethod extends IsTestableMethod {public IsTestMethod() {super(Test.class, true); // @Test注解}
}
// 源碼位置:org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod
public class IsTestFactoryMethod extends IsTestableMethod {public IsTestFactoryMethod() {super(TestFactory.class, false); //@TestFactory注解}
}
// 源碼位置:org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod
public class IsTestTemplateMethod extends IsTestableMethod {public IsTestTemplateMethod() {super(TestTemplate.class, true); // @TestTemplate注解}
}// org.junit.jupiter.engine.discovery.predicates.IsTestableMethod
abstract class IsTestableMethod implements Predicate<Method> {private final Class<? extends Annotation> annotationType;private final boolean mustReturnVoid;IsTestableMethod(Class<? extends Annotation> annotationType, boolean mustReturnVoid) {this.annotationType = annotationType;this.mustReturnVoid = mustReturnVoid;}@Overridepublic boolean test(Method candidate) {// Please do not collapse the following into a single statement.if (isStatic(candidate)) { // 不支持靜態方法的測試用例return false;}if (isPrivate(candidate)) { // 不支持私有方法的測試用例return false;}if (isAbstract(candidate)) { // 不支持抽象方法的測試用例return false;}if (returnsVoid(candidate) != this.mustReturnVoid) { // 不支持返回值不是Void的測試用例return false;}// 16. 根據注解過濾return isAnnotated(candidate, this.annotationType);}
}// org.junit.platform.commons.util.AnnotationUtils
public static boolean isAnnotated(AnnotatedElement element, Class<? extends Annotation> annotationType) {// 17. 查找對應的注解,如果注解存在則是滿足要求的測試用例類//     即帶有@Test、@TestFactory、@TestTemplate這三種注解的測試用例類才是符合要求的測試用例類return findAnnotation(element, annotationType).isPresent();
}// 回到EngineDiscoveryRequestResolution,存儲找到的測試用例
// 源碼位置:org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution
private void resolveCompletely(DiscoverySelector selector) {EngineDiscoveryListener discoveryListener = request.getDiscoveryListener();UniqueId engineId = engineDescriptor.getUniqueId();try {// 10. 執行,參數為ClasspathRootSelectorOptional<Resolution> result = resolve(selector);if (result.isPresent()) {discoveryListener.selectorProcessed(engineId, selector, resolved());// 17. 把找到的測試用例類存到變量中,供后面使用enqueueAdditionalSelectors(result.get());}else {discoveryListener.selectorProcessed(engineId, selector, unresolved());}}catch (Throwable t) {UnrecoverableExceptions.rethrowIfUnrecoverable(t);discoveryListener.selectorProcessed(engineId, selector, failed(t));}
}

2.2 規則

從上面原理看,可以分為文件和方法兩個角度來分析。

從文件角度來說,得有@Test、@TestFactory、@TestTemplate三種注解中的一種,測試用例文件才會被加載到。@TestFactory是用于動態創建多個測試用例,感覺測試用例還是一個個分開寫比較容易維護,此注解爭取不使用。

@TestTemplate注解則可以幫助提供動態參數給測試用例,在某些場景可能需要用,建議封裝到框架里使用。

從方法角度來看,私有方法、靜態方法、抽象方法、有返回值的方法,都不能成為測試用例,需要規避這些情況。

@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {@Autowiredprivate MockMvc mockMvc;@Testpublic void should_say_string() throws Exception {String messge = "abc";MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/sayHello").contentType(MediaType.APPLICATION_FORM_URLENCODED).content("message=" + messge)).andExpect(status().isOk()).andReturn();assertThat(result.getResponse().getContentAsString()).isEqualTo("Hello world: " + messge);}@Testprivate String check_empty_in_private_method() {String str = "abc";assertThat(str).isNotEmpty();return str;}@Testpublic String check_empty_with_return_value() {String str = "abc";assertThat(str).isNotEmpty();return str;}@Testpublic static void check_empty_in_static_method() {String str = "abc";assertThat(str).isNotEmpty();}
}

3 架構一小步

規范:測試用例文件名用xxxTest.java為后綴。不使用Tests.java后綴,避免容易漏。

規范:測試用例方法都加上@Test注解,需要動態參數的地方用@TestTemplate,盡量不用@TestFactory。

規范:測試用例方法名使用下劃線的方式,用表意的方式描述清楚測試用例的用途,長度可以長一些。

規范:測試用例方法名不需要指定test關鍵字。

規范:測試用例方法必須為public、非靜態、非抽象、無返回值方法。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88742.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88742.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88742.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

K8S的CNI之calico插件升級至3.30.2

前言宿主機ping不通K8S的pod&#xff0c;一直存在丟包的現象&#xff0c;排查了防火墻、日志、詳細信息等沒發現什么問題&#xff0c;最后搜索發現&#xff0c;是因為把K8S的版本升級之后&#xff0c;舊版本的CNI插件不適配原因導致的&#xff0c;于是就把calico也一并升級并且…

Spring Boot RESTful API 設計指南:查詢接口規范與最佳實踐

Spring Boot RESTful API 設計指南&#xff1a;查詢接口規范與最佳實踐 引言 在 Spring Boot 開發中&#xff0c;查詢接口的設計直接影響著系統的可用性、可維護性和性能。本文將深入探討如何規范設計查詢接口&#xff0c;包括 GET/POST 的選擇、參數定義、校驗規則等&#xff…

ctfshow萌新題集

記錄一下前半部分是能自己寫出來的&#xff0c;后半部分是需要提示的&#xff0c;感覺自己歸來兩年仍是萌新 misc部分 知識點 base家族密文特征 Base16 (Hex) 字符集&#xff1a;0-9, A-F&#xff08;不區分大小寫&#xff09;。特征&#xff1a; 長度是 2 的倍數&#xff…

2025年語言處理、大數據與人機交互國際會議(DHCI 2025)

&#x1f310;&#x1f916;&#x1f9e0; 語言處理、大數據與人機交互&#xff1a;探索智能未來 —— DHCI 2025國際會議2025年語言處理、大數據與人機交互國際會議&#xff08;DHCI 2025&#xff09; 將于2025年在中國重慶市召開。這次盛會將匯聚全球頂尖專家、學者及行業領袖…

RIP實驗以及核心原理

RIP&#xff08;Routing Information Protocol&#xff0c;路由信息協議&#xff09;是一種內部網關協議&#xff0c;基于距離矢量算法&#xff0c;用于在自治系統內交換路由信息。RIP 核心原理距離矢量算法&#xff1a;RIP 使用跳數作為路徑選擇的唯一度量標準。每經過一個路由…

基于大數據的電力系統故障診斷技術研究

摘要本文提出了一種創新性的基于大數據技術的電力系統故障診斷方法&#xff0c;該方法通過整合先進的機器學習算法和交互式可視化技術&#xff0c;實現了對電力系統各類故障的智能化識別與深度分析。該系統采用隨機森林算法作為核心分類器&#xff0c;構建了高精度的故障分類模…

MySQL 分區功能應用專門實現全方位詳解與示例

MySQL 分區功能允許將表的數據分散存儲在不同的物理分區中,同時保持邏輯上的單一表結構。下面我將從基礎概念到高級應用,全面講解 MySQL 分區實現。 一、分區核心作用 1. 性能提升 分區剪枝(Partition Pruning):查詢時自動跳過不相關的分區,減少數據掃描量 并行處理:不…

汽車功能安全-嵌入式軟件測試(軟件合格性測試)【目的、驗證輸入、集成驗證要求】11

文章目錄1 嵌入式軟件測試&#xff08;Testing of the embedded Software&#xff09;2 測試輸入3 驗證要求和建議3.1 測試環境3.2 測試方法3.2.1 基于需求的測試3.2.2 故障注入測試3.2.3 兩種方法的區別與聯系總結3.3 測試用例導出方法4 嵌入式軟件的測試結果評價5 測試輸出物…

【webrtc】gcc當前可用碼率1:怎么決策的

【webrtc】當前最大碼率是怎么決策的1 看日志,跟蹤代碼最大碼率 是probe的上限 默認值很大 外部設置的較小,調用堆棧 無限大作為默認值 默認是無限大,所以使用預設值 【webrtc】碼率設定中的 int64_t 的無限大

UE5 C++計時器

UE5 C計時器 計時器一&#xff1a; .h文件 FTimerHandle TimerHandle_BetweenShot;//定義時間句柄 void StartFire();void EndFire();.cpp文件 #include “TimerManager.h” void ASpaceShip::StartFire() {GetWorldTimerManager().SetTimer(TimerHandle_BetweenShot, this, &a…

【hivesql 已知維度父子關系加工層級表】

這里寫自定義目錄標題1. 維度表示例1.1清單表1.2層級表2.從清單表加工層級表2.1 注意點2.2 加工方式&#xff08;join&#xff09;2.3 使用函數3.清單表字段加工3.1通過上級編碼信息加工級別信息3.2 通過級別信息&#xff0c;加工上級編碼信息4.創建維度表的一般注意點1. 維度表…

Ubuntu重裝系統后ssh連接不上(遇到 ??“Unit ssh.service not found“?? 錯誤)

重裝系統時不知道為什么SSH 服務未安裝&#xff0c;以下是解決方案&#xff1a;先檢查ssh服務安裝沒安裝 sudo systemctl status ssh # Ubuntu/Debian如果 systemctl 找不到服務&#xff0c;可能是 SSH 未安裝&#xff1a;sudo apt update sudo apt install openssh-serve…

2025社交電商新風口:推客小程序的商業邏輯與技術實現

一、推客小程序市場前景與商業價值在當今社交電商蓬勃發展的時代&#xff0c;推客小程序已成為連接商家與消費者的重要橋梁。推客模式結合了社交傳播與電商變現的雙重優勢&#xff0c;通過用戶自發分享帶來裂變式增長&#xff0c;為商家創造了全新的營銷渠道。推客小程序的核心…

Go 單元測試進階:AI 加持下的高效實踐與避坑指南

單元測試的必要性與基礎單元測試不僅是保障代碼質量的手段&#xff0c;也是優秀的設計工具和文檔形式&#xff0c;對軟件開發具有重要意義。另一種形式的文檔&#xff1a;好的單元測試是一種活文檔&#xff0c;能清晰展示代碼單元的預期用途和行為&#xff0c;有時比注釋更有用…

VScode SSH遠程連接Ubuntu(通過SSH密鑰對的方式)

我們都知道在VScode上通過SSH插件的方式可以遠程連接到虛擬機的Ubuntu系統&#xff0c;這樣開發者就可以在Windows下的Vscode編譯器下直接遠程連接Ubuntu&#xff0c;這種方式是 “用 Windows 的便捷性操作 Linux 的專業性”—— 既保留了Windows系統的易用性和VS Code的強大功…

學術繪圖(各種神經網絡)

23種神經網絡設計&可視化工具匯總 下面做簡要羅列&#xff0c;具體請看相關鏈接 1.draw_convnet Github: https://github.com/gwding/draw_convnet? star 數量&#xff1a;1.7k? 這個工具最后一次更新是2018年的時候&#xff0c;一個Python腳本來繪制卷積神經網絡的工…

Redis的高可用性與集群架構

Redis的高可用性與集群架構 引言&#xff1a;解釋高可用性的重要性及Redis如何實現主從復制&#xff08;Replication&#xff09; 原理&#xff1a;異步復制&#xff0c;主從數據同步配置方法優缺點分析 哨兵模式&#xff08;Sentinel&#xff09; 功能&#xff1a;監控、通知、…

TCP的連接

TCP 三次握手過程是怎樣的&#xff1f;TCP 是面向連接的協議&#xff0c;所以使用 TCP 前必須先建立連接&#xff0c;而建立連接是通過三次握手來進行的。三次握手的過程如下圖&#xff1a;一開始&#xff0c;客戶端和服務端都處于 CLOSE 狀態。先是服務端主動監聽某個端口&…

Excel的學習

一、熟悉界面 1.功能區 點擊“視圖”,點擊凍結窗格,選擇目標行 2.表格區 3.自定義功能區 在上面的空白編輯欄處,右鍵選擇自定義功能區 4.數據輸入規范 (1)格式不統一(日期格式不規范,姓名亂加空格,亂合并單元格) 姓名對齊:右鍵選擇編輯單元格格式,選擇對齊,…