首页 > 其他分享 >测试驱动开发(TDD)学习分享-上篇

测试驱动开发(TDD)学习分享-上篇

时间:2024-09-06 15:49:56浏览次数:7  
标签:上篇 4.3 代码 接口 TDD 替身 测试 分享

1. 概述

1.1 什么是TDD

测试驱动开发(TDD)是一种增量式软件开发技术。简单地说,就是在没有失败的单元测试的前提下不可以写产品代码。这些测试要很小,而且要自动化。用测试来驱动其实很合理。相对于直接写产品代码,TDD的实践者们会先用测试来表达他们希望产品代码会有什么样的行为。然后这个测试显然会失败。只有在这时,他们才开始写产品代码;以便让测试通过。

1.2 TDD的工作流程

测试自动化是TDD的关键。在TDD的进程中,首先会生成新的自动化测试,紧跟着是满足这些测试的产品代码。一套单元测试伴随着产品代码同时不断增长,它也是像产品代码一样具有价值的资产。每次微小改动代码时,这套测试都会运行一次。这样不仅保证了新代码是可工作的,同时也保证了已存在的代码与新的改动兼容。

1.3 TDD起到的作用

尽管你的确要写很多有价值的自动化测试,但是TDD并不是一种测试技术。它是解决编程问题的一种方法。它能帮助软件开发人员做出更好的设计决策。对于方向错误,或者疏漏了实际约束的解决方案,测试将会给出明确的警告。测试抓住了产品代码预期的行为。

TDD还很有趣!这就像一个游戏,你在技术迷宫中将软件引向高可靠的方向,同时还避免了无聊的调试过程。伴随着每一个测试的通过都会有新的成就感,并能感受到向目标更近了一步。自动化测试会记录那些假设,抓住决策,并且让我们能专心面对下一个挑战。

2. 为什么要做TDD

2.1 现有开发方式存在的问题

现有的开发模式我们称为“后期调试式编程”,它存在着如下的问题:


  1. 后期调试式编程需要花费一半以上的时间进行调试,时间不可控
  2. 可能存在在隐藏的bug,要到很后期才发现,发现bug的时间越长,定位bug的时间也会越长

当发现bug的时间(Td)趋近于0,定位问题的时间也趋近于0,刚刚引入到代码中的问题往往很明显。就算不明显,开发者也可以简单回退恢复到以前的工作状态。

  1. 随着时间的流逝及程序员的流动,存在早期代码上的bug会变得更难定位及修复

2.2 使用TDD带来的好处

软件是很脆弱的,任何改动都可能产生意料之外的结果。如果只有手工测试,我们很难负担这么多测试来预防所有意料之外的情况发生。重新测试的代价太高了,所以我们只去运行那些我们认为必需的手工测试。有时我们没那么幸运,引入了bug却没发现。如果使用TDD,测试会帮我们检查这些意料之外的情况,我们不用放弃检查之前的那些代码的行为。

1.产生的bug更少

2.调试时间更短

3.边际效应所带来的bug更少

测试会对代码进行约束,违反了这些约束测试会给出警告

4. 内心的平静

彻底、全面的回归测试给予我们信心。TDD开发者据称晚上睡得更香且周末更少被打扰

5. 改善设计

好的设计一定是可测试的设计。长函数、紧耦合以及复杂的条件判断,这些都会导致复杂且难测试的代码。如果开发者发现对计划中的代码改动很难编写测试,那就说明设计有问题,TDD会在早期给出提示。TDD是监视代码变坏的雷达

6.有趣且回报丰厚

测试用例可以一直使用,可以减少代码改动引入的bug

3. 如何做TDD

3.1 嵌入式TDD循环

  1. 平台1:TDD微循环
  2. 平台2:编译器兼容性检查
  3. 平台3:在评估板上运行单元测试
  4. 平台4:在目标硬件上运行单元测试
  5. 平台5:在目标硬件上运行验收测试

3.1.1 TDD微循环

TDD的核心是由小步骤来不断重复的循环组成的

  1. 增加一个小的测试
  2. 运行所有的测试并期待新测试失败,也可能根本连编译都通不过
  3. 为了让测试通过来做一些小的改动
  4. 运行所有的测试并期待新的测试通过
  5. 重构,以移除重复并改进代码的表达方式

3.1.2 单元测试例编写的四阶段

  1. 建立:创建测试的前置条件
  2. 运行:对系统进行操作
  3. 验证:检查预期的输出
  4. 拆卸:把被测系统恢复到测试前的初始状态

为了让测试过程清晰、明了,要让测试中的这种模式清晰可见。当这种模式被打破时,测试作为文档的价值就打了折扣,阅读测试代码的人将很难读懂测试要表达的需求是什么

3.2 TDD实践的一般流程

1.写一个测试列表

测试列表由需求衍生而来,测试列表定义了你对需要完成的功能的最好理解。这个列表不需要很完美。他只是各临时文档。

当心在创建测试列表时的报酬递减。一旦你写下几个测试,其它的会很容易想到。当进展很慢时,你可能已达到了报酬递减的编辑,这可能就是一个停止工作在测试列表转而去测试驱动设计的好时机。在驱动设计的同时你会想到其它的测试。有时一个测试将来可能会被拆开。有些可能会被合并。

2.当对事情毫无头绪或者看起来比较复杂时可以从简单的地方入手,慢慢进入状态

3. 先测试驱动接口再测试驱动内部实现

好的接口对于设计良好的模块来讲很关键。前面几个测试会驱动接口设计,关注于接口意味着我们是从外向内开发代码。测试作为接口的首个用户,从调用者的角度给出了开发代码的使用方式。从使用者的角度出发会产生可用性更强的接口

4. 先仿冒再建造

当你只写了几个简单的测试时,通常最简单的事就是仿冒它。那么什么时候停止仿冒转而写真实的代码?对此简单的首要原则是,一旦去仿冒它比做出它还要麻烦时就做出它

5. 保持小而专注的测试

刚刚做TDD的程序员往往在每个测试中放入太多东西。这会破坏可读性和关注点。对于一个测试用例中该有多少行断言并无限制,正如对一个函数中该有多少行代码并无限制一样。但是我们要保持测试可读、小巧并且专注。四段测试模式的每一步(建立、执行、验证和拆除)应该在每个测试用例中都清晰可见。

6. 绿了之后就重构

唯一可以安全地进行重构的时刻是当所有的测试都通过时,不要在测试不通过的情况下重构!当有测试失败时,你并没有锁住代码的行为。结构化发生改动,当它与失败的测试纠缠在一起时可能会非常难以重新回到所有测试都通过的状态

3.3 TDD需要遵循的原则

3.3.1 TDD的三原则

  1. 除非是让一个失败的单元测试通过,否则不要写产品代码。
  2. 不要写比足以失败更多的单元测试,构建失败也可以。
  3. 不要写比足以让单元测试通过更多的产品代码。

尽管这听上去很有约束感,但是它却是很高产并很有趣的软件开发方式。

3.3.2 测试FIRST原则

  1. F  Fast(快速的)︰测试要快速,快到程序员可以在每个微小的改动后都运行它们而且不会打断工作流。
  2. I  Isolated(独立的)∶测试用例要是独立的。一个测试不会为另一个测试创建环境。测试中的失败也要分离开。
  3. R  Repeatable(可重复的)∶测试要可以重复地去运行。可重复意味着自动化。一个循环中的测试总是给出同样的结果。
  4. S Self-verifying(自校验的)︰测试会自己检查产出,通过时只给出简单的“OK",失败时给出详细的细节。
  5. T Timely(及时的)︰测试要及时做。程序员要及时写测试,与写产品代码紧连(但必须是在前面〉来防止软件bug的产生。

4. 测试有合作者的模块

对于TDD来讲,一个更让人敬畏的挑战是测试位于中间的代码,后者包含那些必须通过与其他模块、函数及数据存储共同工作来完成它们自身任务的模块。

合作者(collaborator)就是在被测代码(Code Under Test,CUT))之外的一些函数、数据、模块或者设备,并且CUT依赖于它们。

4.1 测试替身

测试替身在测试时会模仿一些函数、数据、模块或库。被测试代码并不知道它在使用测试替身,它与替身之间的交互和与真实的合作者采用同样的方式。

你之前可能用过测试替身。最简单的形式是,测试替身就是一个桩,用来代替真实的产品代码。你可能曾经写过一些桩,但也可能你只是把桩作为没有真实代码时的一个短期解决方案。一旦你有了真实的代码,你就不再使用桩了。在TDD中,测试替身在整个产品代码开发过程中都会用到和维护,以帮助自动化单元测试。

4.1.1 不同的测试替身

4.1.2 何时使用测试替身

为被测代码选择是否要用测试替身并不是非此即彼的命题。最常见的情况是你会有不同的真实的和仿冒的合作者,并且,对于一些测试你会希望采用产品代码合作者,而对于另一些测试你会希望采用测试替身

1. 独立于硬件

有了代替硬件交互的测试替身就可以让测试独立于硬件,它同时还有为系统核心给出大量不同输入的能力,这些输入在实验室或现场可能会很难或很耗时

2.注入难以产生的输入

一些需计算过的或者由硬件产生的事件场景可能很难产生。通过调整测试替身的返回值,被测试代码就能得到所需的条件,以触发那些不太可能执行到的路径

3.加速缓慢的合作者

如果测试不能运行得很快,你可能会不再足够频繁地去运行他们。动作缓慢的合作者,如数据库、网络服务、或者数字运算,可以通过返回一个由测试用例控制的结果来仿冒,从而提高测试速度

4.依赖于不稳定的事物

不稳定合作者的一个经典例子就是时钟。

5.对在开发的事物的依赖

设计往往包含未知区域,尤其是当硬件与软件并行开发时。在你访问一个未知区域时,可以开发一个测试替身,它带有被测试代码所需的接口。被测代码的进度仍可继续,同时也有机会来探索被测代码对于现在尚未实现的服务的需求

6.对于难以配置的事物的依赖

如果一个DOC很难配置和被设置到一个或多个期望的状态,可能最好的办法还是用测试替身来换掉它。数据库是一个很好的这种DOC的例子,你的确可以测试它,但是这样的测试很难配置

4.2 测试替身的替换方法

C语言只有以下这些替换方法:链接时代换、函数指针代换以及预编译代换

4.2.1 链接时代换

在你希望为整个单元测试替换DOC时使用链接时代换

4.2.2函数指针代换

当你希望只为一部分测试用例替换DOC的时候可以使用函数指针

4.2.3 预编译代换

在链接器和函数指针都不能工作时使用预编译代换。你可以用预编译来断开一系列不想要的包含。你也可以用它有选择性地或临时地重载名字。预编译代换是代换的最后选择。

拓展知识点:

CppUTest使用预编译代换来重载标准库函数free()、ma1loc()、calloc()以及rea1loc()函数,让它可以监视堆的使用情况。它使用GCC命令行开关-include来强行让每个文件的开头都包含一个头文件。这里是一个通过强行包含头文件来让 CppUTest 监视堆的例子:

4.3 测试替身使用实战

为了管理对被测代码执行环境的依赖,所有对执行环境的访问都必须经由已定义的接口完成。可以让测试替身替代有麻烦的依赖组件(DOC)来拦截并监视接口调用。测试用例可以控制测试替身的返回值,间接地驱动被测代码。基本的想法是测试用例和测试替身一起形成一个测试夹具包围起被测代码,驱动它的输入并监视和检查它的输出。

测试用例在这里扮演客户的角色,驱动着被测代码的直接输入,同时测试替身扮演DOC的角色。测试替身可以监视为DOC提供的数据,以及以返回值的方式提供间接输入来依照测试用例的需要驱动被测代码

我们要创建一个系统的核心功能,它需要与硬件及操作系统交互。我们不会让代码直接访问硬件或操作系统。我们会让它通过一个可以用测试替身替换的薄薄的层次进行访问,这也就用到了测试间谍,它可以帮助校验被测代码的行为

作为嵌入式软件开发者,你可能会听说过术语“操作系统抽象层”和“硬件抽象层”。这些层次带来了执行环境之间的可移植性。我们使用同样的想法来让系统的核心逻辑可测。现在你更有理由把这些层次引入到你的代码中

4.3.1 使用链接时代换替换测试替身

列出测试列表

不要过于操心把测试搞得完全准确,在进行得过程中你会修改顺序。你会发现一些遗忘得测试或者发现测试列表中的某一项实际上应该是多个测试。不要在测试列表上花太多时间。如果你尝试让它变得完美,那你绝对花太多时间在这上面了

4.3.1.1 关于硬件和操作系统的依赖

让我们看看如何设计和测试家庭自动化系统中的灯光调度这一部分。用来处理灯光调度的组件叫做LightScheduler,如下图。LightScheduler对于硬件和操作系统有传递依赖。如果不断开他们,这意味着只有在目标硬件上面才能测试这个灯光调度器

4.3.1.2 使用链接时代换断开依赖

想断开产品代码上的依赖,只能从这些合作者的接口角度来看待他们。在下图中,我们可以看到接口从实现中分离出来了接口由头文件来声明,并且由源文件来实现。

单元测试利用链接代换的优势来为LightController和TimeService提供另一种实现,如下图所示

一个比较好的组织测试构建的方式是把所有的产品代码编译到一个库中。测试替身留在外面成为对象(.o)文件。当为测试构建时,makefile明确地在链接产品代码库之前先链接测试替身对象文件。这样测试替身就可以用同样的名字覆盖产品代码。

4.3.2 监视被测代码

测试间谍:想象一下间谍在进行一项隐秘的操作。它截获传向产品代码的输人,稍后会把它提供给测试用例。作为其隐秘行动的一部分,它可能还要向客户代码提供返回结果,让被测代码能完成测试交给它的任务。真是相当得鬼鬼祟祟。

间谍的头文件包含了它要取代的接口的头文件。间谍本身是LightController接口的一个实现。包含接口头文件是对这一点的强调

间谍的实现定义了一个秘密情报放置点 (deaddrop),由文件作用域的数据构成,当完成它重要的隐秘操作后间谍把情报放在其中。

在间谍执行任务的过程中,它从替换掉的合作者的接口上截获重要信息。

被间谍监视的对方对此毫无怀疑。在被测代码执行后,通过秘密访问器从情报放置点中取出情报。

4.3.3 测试路径先0到1再N

完整实现以后的LightScheduler将会管理一个调度项的集合。如果从一个包含很多调度事件的测试用例开始,可能需要很多的代码。一个发起进攻并征服这类问题的切入点是从“没有”调度项的测试用例开始。然后是“一个”调度项,把“多个”的用例留在以后做

什么也不做的测试是通向测试通过的最短路径。它所需的只是对产品代码调用的接口定义。不要担心,实际上它什么也没测。这里的目标是把边界测试调整正确。当以后完整的实现就位时,这些测试仍将继续保证这些边界用例的行为是正确的。你会被诱惑来写比一个空函数体要多的代码,不要这样做。那是通向不可测代码之路。

0->1->N的模式在应对集合行为时有帮助。首先我们来处理0的用例,就是没有任何调度。然后处理1个事件的各种变化,来驱动出对所有的日子和时间变化的支持。在日期和时间的各种变化都测试通过后,我们会把注意力转到N个的情况,也就是测试驱动开发多个调度事件的场景

4.3.4 测试完后重构

4.3.4.1 为移除重复而重构
4.3.4.2 为责任而重构

在这段代码中,Lightscheduler_wakeup()被重构成了几个分开的责任。我们做子两个函数抽取来让代码更好地与我们沟通。

重构前:

重构后:

4.3.4.3 重构测试

请注意测试中的重复。这些测试用例并不算长,但它们却很难读,因为需要去解读所有的细节。重复的操作及检查可以抽取成辅助函数,让测试变得简单易读

4.3.4.4 复杂的条件逻辑

4.3.4.5 不要烧掉退路原则

保留原有代码,新增新的代码,保证测试通过后再去掉原有代码,这样就能保证再新的代码出现问题后可以进行回退

4.3.5 函数指针代换(运行时绑定替身)

对于那些基于一部分测试的需要而被打桩掉的代码却在另一些测试当中需要用到,我们需要比链接器所能提供的更多的灵活性。我们会使用函数指针,以便在同一个测试构建中既可以测试一个函数,又可以换掉它

4.3.5.1仿冒指针

我们用随机分钟生成器来实现随机化的灯光调度特性。调度器会有无法预知的结果,因为它用到了RandomMinute_Get()带来的无法预知的间接输入。当产品代码依赖于某些不确定因素的时候,就是要把它用测试替身换掉的时候

现在该测试的目标明确了,让我们重构设计以便用FakeRandomMinute替换RandomMinute。直接的函数调用需要转换成函数指针。头文件的定义看起来像这样:

这个指针必须是extern的,以避免在链接时产生重复定义错误。这个声明表明有一个指针指向一个称作RandomMinute_let()的函数,该函数不需要参数并且返回一个int类型。

在.c文件中,我们写下随机分钟生成器默认的产品代码实现。它后面跟着全局函数指针RandomMinute_Get()的实例定义。请注意RandomMinute_Get()被初始化成指向RandomMinute_GetImpl()。

4.3.5.2 外科手术般地插入间谍

当一个系统有打印输出时,它通常是手动检查的。打印输出的校验可能冗长乏味,因此你可能不想频繁地重新检查这些输出。我们可以通过锁定期望的行为来消除重新检查

假设你已经有一个像printf()一样的函数来产生打印输出,称作FormatOutput()。它现在是一个在头文件中与很多其他工具放在一起的直接函数调用。你希望为FormatOutput()创建一个间谍,但你不想打桩掉包含FormatOutput()的那个文件中的所有函数调用。一个更精细的做法需要能在一个编译单元中只截获对一个函数的调用。这个FormatOutput()的直接函数调用原型如下所示:

要做到这样外科手术般地截获对FormatOutput()的调用,函数指针是正确工具。为了渗透进FormatOutput()的调用者,首先把FormatOutput()的原型转换成函数指针。

在.c文件中,把FormatOutput()重命名为FormatOutput_Imp1()。然后创建FormatOutput函数指针的实体声明。为了防止别人直接调用FormatOutput_Imp1(),还应当把它变成静态的:

经过以上对.h和.c文件的改动之后,重新构建。这里不需要对FormatOutput()的调用者做任何改动,因为对于直接调用和通过函数指针调用的语法是一样的。

如果要直接使用printf(),可以采用同样的操作,把FormatOutput_Imp1()初始化为:

4.3.6 仿制对象(基于ceedling仿制对象介绍)

在测试驱动开发LightScheduler时,测试夹具通过截获对TimeServeice和LightController的调用来校验LightScheduler的行为是否正确。我们使用的测试替身很简单,并且我们声明了几个具有取值函数和赋值函数的静态变量。这   对于简单的交互来讲已经足够了。遗憾的是,不是所有的软件实体间的交互都如此简单。简单的间谍和桩不会处处好用。对于更复杂的交互,我们需要一个不同的工具---仿制对象

仿制对象(mock)是一种测试替身。它使得测试用例可以描述一个模块期望对另一个模块的调用。在测试的执行过程中,仿制对象会检查所有的调用是否都按正确的顺序发生,并且传入了正确的参数。仿制对象还可以指定以恰当的序列来给被测试代码返回特定的值。仿制对象并不是模拟器,但它却能让测试用例来模拟一个特定的场景或者一系列事件

以下是一个视频数据采集模块的例子,该模块实现从视频驱动中获取视频数据的功能,视频驱动我们使用ceedling提供的仿制对象替换,测试工程目录如下图所示:

图中的video_capture.c和video_capture.h是我们的需要测试的视频数据采集模块,video_drv.h是视频驱动,test_video_capture.c是我们的测试用例

4.3.6.1 仿制对象提供的接口

Ceedling的仿制对象为我们实现了多种功能的接口以辅助我们进行单元测试,相关接口如下图所示:

4.3.6.2 如何生成仿制接口

如下图所示,我们只需要在要使用仿制对象接口的测试用例所在的.c文件中#include mock_xxx.h,然后执行命令,ceedling就会自动生成我们所需要的仿制对象接口

4.3.6.3 仿制对象接口的作用介绍

 1.该接口用于忽略后面video_drv_open()调用的次数,如果没用调用取消函数,该功能会一直生效

  2.该接口用于取消video_drv_open()调用次数的忽略

 3.该函数用于设置下一次调用video_drv_open时需要的返回值,该函数的调用次数要与video_drv_open()调用次数一样,不然测试会报错。

4.3.7 演进的需求和有问题的设计

4.3.7.1 常见的有问题的设计

这个switch语句的条件逻辑会在LightController_Turnon()、LightController_Turnoff()中重复出现。而且还会有其它的LightDriver需求过来。我们还需要控制变亮、变暗以及闪烁等。这个模式至少还会被重复3次

在这些代码中还有一个问题。其中有测试代码,LightDriverSpy_TurnOn(),也混在产品代码中。代码具有可测性是件好事情。但产品代码有测试代码的知识却不是好事情

这里重复的switch语句正是设计开闭原则要解决的问题。如下图,我们可以看到LightController知道其必须管理的设备。增加一种新类型的LightDriver就意味着要修改所有的switch语句。增加一个新操作则意味着需要另一个switch语句。

下面是LightDriverSpy的接口:

像其他任何的LightDriver一样,LightDriverSpy也可以被创建、拆除、打开和关闭。可以在这个间谍接口的第一部分看到这些操作。接口的第二个部分是用来让间谍在执行完任务后做简报的

从SOLID设计原则的角度来讲,前一种设计没有为增加新类型的驱动而遵守OCP。LightController知道每种LightDriver类型。无论何时增加一个驱动类型,就会发生霰弹枪手术这样的事情

4.3.7.2 用动态接口来改进设计

动态接口使用一个或多个函数指针来允许在运行时选择某一函数实现来执行。这种单一的间接层次能提供运行时的灵活性。

应用OCP和LSP原则,在这个设计中,LightController对于新的LightDriver的扩展是开放的,同时对于修改是关闭的。和前面的设计不同,LightController不知道任何特定的LightDriver

以下代码给出了为每个驱动函数容纳函数指针的结构体:

请注意容纳数据结构的这个文件的命名。通过把它命名为LightDriverPrivate.h,我们让全世界都知道它们应当忽略这个数据结构中的细节。和CircularBuffer不同,我们不能把这个结构体声明在.c文件中,因为有多个文件需要知道这个结构体的布局。

这个接口通过如下方式安装在LightDriver 上:

通用的LightDriver_Turnon()函数通过接口结构体来调用特定的驱动,并把LightDriver的抽象数据类型传给它。

4.3.7.3 更灵活的基于类型的动态接口

在上述的设计中我们同一时刻只能支持一种类型的灯具驱动,当需求出现变更要求我们同一时刻能满足多个类型的灯具驱动明显该设计就不太满足要求了

为了给每盏灯都定制不同的驱动类型,每个驱动的结构体都需要一组关联的函数指针。不像动态接口模型中那样只有一个接口指针,而是在每个LightDrvicerStruct中有一个指向接口函数表的指针。

首先,在LightDriverStruct中增加一个vtable字段。这会使得每个LightDriver实例都指向自己类型所对应的LightDriverInterface。

LightDrtverSpy_Create()函数会初始化vtable成员变量。请注意所有同一类的实例都指向同一个函数表。

让LightDriver函数通过LightDriver的vtable成员变量来分派调用,而不再通过LightDriver的单一接口指针。下面是LightDriver_TurnOn():

标签:上篇,4.3,代码,接口,TDD,替身,测试,分享
From: https://blog.csdn.net/u010467490/article/details/141953285

相关文章

  • 新手c语言讲解及题目分享(十九)--数据类型专项练习
    本文主要讲解c语言的基础部分,常见的c语言基础数据类型,这个也非常重要。参考书目和推荐学习书目:通过网盘分享的文件:C语言程序设计电子教材(1).pdf链接:https://pan.baidu.com/s/1JFqSaCKZ0A2Lr944e72NUA?pwd=p648提取码:p648目录前言一.常量与变量1.常量2.变量二.......
  • 新手c语言讲解及题目分享(十八)--基本输入输出函数专项练习
    本文主要讲解c语言的基础部分,基本的输入与输出,通过手动的输入从而得到自己想要的预期值。参考书目和推荐学习书目:通过网盘分享的文件:C语言程序设计电子教材(1).pdf链接:https://pan.baidu.com/s/1JFqSaCKZ0A2Lr944e72NUA?pwd=p648提取码:p648目录前言一.格式输出......
  • 基于ECharts+JS+Flask 交互可视化呈现NBA近期比赛信息及球队排名及数据 | 源码分享
    目录文章|内容结语|源代码文章|内容和大家分享一个我在闲暇之余写的一个小项目。为了能够更加直观的了解近期的NBA比赛信息、球队排名以及数据,本项目采用了ECharts、JS、Flask等技术进行可视化呈现。通过这种方式,我们可以更直观的了解NBA比赛,为球迷提供更好的观赛体验......
  • 用亚马逊云科技Graviton高性能/低耗能处理器构建AI向量数据库(上篇)
    简介:今天小李哥将介绍亚马逊推出的云平台4代高性能计算处理器Gravition,并利用该处理器构建生成式AI向量数据库。利用向量数据库,我们可以开发和构建多样化的生成式AI应用,如RAG知识库,特定领域知识的聊天机器人等。我们今天将手把手带大家在亚马逊云科技上,搭建一个目前大热的Milv......
  • 三、搭建网站服务器超详细步骤——FinalShell下载安装使用流程(免费国产的SSH工具)+宝塔
    前言本篇博客是搭建网站服务器模块下的第3部分  FinalShell下载安装使用流程  在分享这篇博客之前,首先讲一下,FinalShell软件是干什么用的,用大白话进行说明一下:这个软件是一款远程控制和管理服务器的软件,通过SSH协议与远程服务器进行连接,去操控一系列的命令信息。就像......
  • 基于java+springboot+vue的考研资料分享微信小程序
    项目介绍互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱,出错率高,信息安全性差,劳动强度大,费时费力等问题,采用考研资料分享系统可以有......
  • 【Azure Policy】分享Policy实现对Azure Activity Log导出到Log A workspace中
    问题描述使用AzurePolicy服务,对公司内部全部的订阅下的ActivityLog,都需要配置导出到LogAWorkspace中。以下Policy规则可以实现此目的。 Policy内容说明在PolicyRule部分中,选择资源的类型为 "Microsoft.Resources/subscriptions",效果使用 DeployIfNotExists(如果不存在,则......
  • 【Azure Policy】分享Policy实现对Azure Activity Log导出到Log A workspace中
    问题描述使用AzurePolicy服务,对公司内部全部的订阅下的ActivityLog,都需要配置导出到LogAWorkspace中。以下Policy规则可以实现此目的。 Policy内容说明在PolicyRule部分中,选择资源的类型为 "Microsoft.Resources/subscriptions",效果使用 DeployIfNotExists(如果不......
  • 【工具分享】PyWxDump v3.1.31(最新版本) - 微信聊天记录解密
    工具介绍:PyWxDump是一款功能丰富的工具,主要用于获取微信账号信息(如昵称、账号、手机、邮箱和数据库密钥等)、解密微信数据库、查看和备份聊天记录。它支持多种数据库类型的合并查看,可以通过Web界面查看聊天记录,并且支持聊天记录的导出功能,如导出为html或csv格式,方便用户进行备份和......
  • “很高兴遇到瀛姬和BDSM”——来自于我们成员的一篇分享
    “很高兴遇到瀛姬和BDSM”——来自于我们成员的一篇分享“我成长于一个传统且严格的家庭环境。父亲深信“棍棒底下出孝子”,对我的教育总是伴随着严格的体罚。然而,与大多数人不同的是,我小时候并不害怕这些体罚,反而内心深处对它们有着一种莫名的渴望和向往。随着年岁的增长,我开......