ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

翻译

2021-05-18 23:02:17  阅读:107  来源: 互联网

标签:翻译 错误 代码 单元测试 套件 测试用例 测试


这里写自定义目录标题

阅读3:测试

阅读练习和Java Tutor练习应在上课前一天晚上进行。 您必须在2月12日星期日晚上10:00之前完成本阅读中的练习。

Java导师练习
通过完成Java辅导老师中的以下三个类别,继续在Java上取得进步:

✓平等1/1

✓列表和数组1/1

✓方法1/1

6.031中的软件
防虫 容易明白 准备改变
今天改正,在未知的未来改正。 与未来的程序员(包括未来的您)进行清晰的沟通。 旨在适应变化而无需重写。
今日课程的目标
今天的课后,您应该:

了解测试的价值,并了解测试优先编程的过程;
通过划分方法的输入和输出空间并选择好的测试用例,可以设计一种方法的测试套件;
能够通过测量其代码覆盖率来判断测试套件;和
了解并知道何时使用黑盒与白盒测试,单元测试与集成测试以及自动回归测试。

验证

验证的目的是发现程序中的问题,从而增加您对程序正确性的信心。验证包括:
关于程序的正式推理,通常称为验证。验证构成了程序正确的正式证明。手工验证很繁琐,并且自动化工具支持验证仍然是研究的活跃领域。然而,可以对程序的关键小部分进行形式验证,例如操作系统中的调度程序,虚拟机中的字节码解释器或操作系统中的文件系统。
代码审查。 让其他人仔细阅读您的代码并进行非正式地推理,可能是发现错误的好方法。这就像让其他人校对您写的文章一样。在下一篇文章中,我们将更多地讨论代码审查。
测试。在精心选择的输入上运行程序并检查结果。
即使进行了最佳验证,也很难在软件中获得完美的质量。以下是每千位(一千行源代码)的一些典型残留缺陷率(软件交付后遗留的错误):

1-10个缺陷/千位:典型的行业软件。
0.1-1个缺陷/千位:高质量验证。Java库可能会达到这种正确性级别。
0.01-0.1个缺陷/千公里:最好的,对安全性至关重要的验证。NASA和Praxis等公司都可以达到这一水平。
对于大型系统,这可能会令人望而却步。例如,如果您已交付了一百万行典型的行业源代码(每个缺陷1个缺陷),则意味着您错过了1000个错误!

为什么软件测试很难
不幸的是,这里有一些方法在软件世界中不能很好地工作。

详尽的测试是不可行的。可能的测试用例的空间通常太大,无法穷尽。想象一下,彻底测试32位浮点乘法运算a*b。有2 ^ 64个测试用例!

危害测试(“只要尝试一下,看看它是否可以工作”)就不太可能发现错误,除非该程序有太多错误,以至于任意选择的输入更有可能失败而不是成功。这也不会增加我们对程序正确性的信心。

随机或统计测试不适用于软件。其他工程学科可以测试少量随机样本(例如,制造的硬盘驱动器的1%),并推断整个生产批次的缺陷率。物理系统可以使用许多技巧来加快时间,例如在24小时内打开冰箱1000次而不是10年。这些技巧可提供已知的故障率(例如,硬盘的平均寿命),但它们在缺陷空间中具有连续性或一致性。这对于物理伪像是正确的。

但这对于软件而言并非如此。软件行为在可能的输入空间中不连续且离散地变化。该系统似乎可以在广泛的输入范围内正常运行,然后在单个边界点突然失效。在著名的Pentium部门的bug 9个十亿部门大约1的影响。堆栈溢出,内存不足错误和数字溢出错误往往会突然发生,并且总是以相同的方式发生,而不会发生概率变化。这与物理系统不同,在物理系统中,通常有明显的证据表明系统正在接近故障点(桥梁中的裂缝)或故障概率性地分布在故障点附近(因此,统计测试即使在到达故障点之前也会观察到某些故障) )。

相反,必须仔细,系统地选择测试用例,这就是我们接下来要讨论的内容。

阅读练习
测试基础
戴上测试帽
测试需要有正确的态度。在编写代码时,您的目标是使程序正常运行,但是作为测试人员,您希望使其失败。

这是一个微妙但重要的区别。将您刚刚编写的代码视为一件珍贵的,脆弱的蛋壳,并对其进行轻巧的测试以使其正常工作,这真是太诱人了。

相反,您必须是残酷的。优秀的测试人员会挥动大锤,并在可能容易受到攻击的任何地方击败该程序,从而可以消除这些漏洞。

测试优先编程

尽早并经常进行测试。直到大量未验证的代码结束时,才进行测试。直到最后才进行测试,这只会使调试时间更长,更痛苦,因为错误可能在代码中的任何地方。在开发代码时测试代码要令人愉快得多。

在测试优先编程中,您甚至在编写任何代码之前都要编写测试。单个功能的开发按以下顺序进行:

编写该功能的规范。
编写符合规范的测试。
编写实际的代码。一旦代码通过您编写的测试,就可以完成。
该规范描述了功能的输入和输出行为。它给出了参数的类型以及对它们的任何其他约束(例如sqrt的参数必须为非负数)。它还提供了返回值的类型以及返回值与输入的关系。在该课程中,您已经看到并使用了关于问题集的规范。在代码中,规范包括方法签名和描述其作用的注释。从现在开始,我们将在几节课中对规格进行更多说明。

首先编写测试是理解规范的好方法。规范也可能是错误的—不正确,不完整,模棱两可,缺少极端情况。在您浪费时间编写越野车规范的实现之前,尝试编写测试可以尽早发现这些问题。

通过分区选择测试用例

创建一个好的测试套件是一个具有挑战性和有趣的设计问题。我们想选择一组测试用例,这些测试用例足够小,可以快速运行,但又足够大,可以验证程序。

划分函数的输入空间
为此,我们将输入空间划分为子域,每个子域由一组输入组成。子域合在一起完全覆盖了输入空间,因此每个输入都位于至少一个子域中。然后,我们从每个子域中选择一个测试用例,这就是我们的测试套件。

子域的思想是将输入空间划分为程序在其上具有相似行为的相似输入集。然后,我们每个集合使用一个代表。该方法通过选择不同的测试用例,并迫使测试探索随机测试可能无法达到的部分输入空间,从而充分利用有限的测试资源。

如果需要确保测试将探索输出空间的不同部分,我们还可以将输出空间划分为子域(程序在其上具有相似行为的相似输出)。在大多数情况下,对输入空间进行分区就足够了。

例子: BigInteger.multiply()
让我们来看一个例子。 BigInteger是Java库中内置的一个类,可以表示任意大小的整数,这与基本类型不同,int并且long范围有限。BigInteger具有multiply将两个BigInteger值相乘的方法:

/**

  • @param val another BigInteger
  • @return a BigInteger whose value is (this * val).
    */
    public BigInteger multiply(BigInteger val)
    例如,这是可能的用法:

BigInteger a = …;
BigInteger b = …;
BigInteger ab = a.multiply(b);
此示例表明,即使在方法的声明中仅显式显示一个参数,但multiply实际上它是两个参数的函数:您要在其上调用方法的对象(a在上面的示例中)以及您要传入的参数括号(b在此示例中)。在Python中,接收方法调用的对象将被明确命名为self在方法声明中调用的参数。在Java中,您不会在参数中提及接收对象,this而是将其称为self。

因此,我们应该考虑multiply一个函数,该函数接受两个输入,每个输入为type BigInteger,并产生一个type的输出BigInteger:

multiply : BigInteger × BigInteger → BigInteger

因此,我们有一个二维输入空间,其中包含所有成对的整数(a,b)。现在让我们对其进行分区。考虑乘法的工作原理,我们可以从以下分区开始:

a和b均为正
a和b都是负数
a为正,b为负
a为负,b为正
我们还应检查一些特殊的乘法情况:0、1和-1。

a或b为0、1或-1
最后,作为一个试图发现错误的可疑测试人员,我们可能会怀疑BigInteger的实现者可能会尝试通过使用int或在long内部将其更快地运行,并且仅在返回时才使用昂贵的通用表示形式(例如数字列表)。价值太大。因此,我们绝对也应该尝试使用很大的整数,大于最大的整数long。

a或b小
a或b的绝对值大于Long.MAX_VALUEJava中可能的最大原始整数,大约为2 ^ 63。
让我们将所有这些观察结果放到整个(a,b)空间的直接分区中。我们将选择a和b独立地:

分区multiple()
0
1个
-1
小正整数
小负整数
巨大的正整数
巨大的负整数
因此,这将产生7×7 = 49个分区,这些分区将完全覆盖整数对空间。

为了生成测试套件,我们将从网格的每个正方形中选择一个任意对(a,b),例如:

(a,b)=(-3,25)覆盖(小负数,小正数)
(a,b)=(0,30)覆盖(0,小正数)
(a,b)=(2 ^ 100,1)覆盖(大正数,1)
等等。
右图显示了二维(a,b)空间如何被该分区划分,这些点是我们可以选择完全覆盖该分区的测试用例。

例子: max()
让我们看一下Java库中的另一个示例:max()在Math类中找到的整数函数。

/**

  • @param a an argument
  • @param b another argument
  • @return the larger of a and b.
    */
    public static int max(int a, int b)
    从数学上讲,此方法是以下类型的函数:

max : int × int → int

最大分区
根据规范,将此功能划分为:

a b
我们的测试套件可能是:

(a,b)=(1,2)覆盖a <b
(a,b)=(9,9)覆盖a = b
(a,b)=(-5,-6)覆盖a> b
在分区中包括边界
错误通常发生在子域之间的边界处。一些例子:

0是正数和负数之间的边界
数值类型(例如int和)的最大值和最小值double
集合类型的空性(空字符串,空列表,空数组)
集合的第一个和最后一个元素
为什么错误经常发生在边界处?原因之一是程序员经常犯一个错误(例如写<=而不是<,或者将计数器初始化为0而不是1)。另一个是,某些边界可能需要作为代码中的特殊情况来处理。另一个是边界可能是代码行为中不连续的地方。int例如,当变量超过其最大正值时,它突然变为负数。

在您的分区中将边界作为子域包含进来很重要,这样您就可以从边界中选择一个输入。

让我们重做max : int × int → int。

划分为:

a和b之间的关系
a b
一个的值
a = 0
a <0

0
a =最小整数
a =最大整数
b的值
b = 0
b <0
b> 0
b =最小整数
b =最大整数
现在让我们选择覆盖所有这些类的测试值:

(1,2)覆盖a <b,a> 0,b> 0
(-1,-3)覆盖a> b,a <0,b <0
(0,0)覆盖a = b,a = 0,b = 0
(Integer.MIN_VALUE,Integer.MAX_VALUE)覆盖了<b,a = minint,b = maxint
(Integer.MAX_VALUE,Integer.MIN_VALUE)覆盖a> b,a = maxint,b = minint
覆盖分区的两个极端
划分输入空间后,我们可以选择希望测试套件的详尽程度:

完整的笛卡尔积。
每个分区大小的合法组合都包含在一个测试用例中。这就是我们为multiply示例所做的工作,它为我们提供了7×7 = 49个测试用例。对于max包含边界的示例,该示例具有三个维度,分别具有3个部分,5个部分和5个部分,这将意味着最多3×5×5 = 75个测试用例。实际上,并非所有这些组合都是可行的。例如,没有办法覆盖组合a <b,a = 0,b = 0,因为a它不能同时小于零和等于零。

覆盖每个部分。
每个维度的每个部分都至少包含一个测试用例,但不一定是每个组合。通过这种方法,max如果仔细选择,用于的测试套件可能只有5个测试用例。这就是我们上面采用的方法,它使我们可以选择5个测试用例。

根据人类的判断和谨慎,并经常受到白盒测试和代码覆盖工具的影响,我们经常在这两个极端之间达成某种折衷,我们将在本文后面的内容中看到这些折衷。

阅读练习
分区
分割字符串

使用JUnit进行自动化的单元测试

一个经过良好测试的程序将对其包含的每个单独模块(模块是方法或类)进行测试。如果可能的话,一个单独测试单个模块的测试称为单元测试。隔离地测试模块可以使调试更加容易。当某个模块的单元测试失败时,您可以更有信心在该模块中找到该错误,而不是在程序中的任何位置。

JUnit是一个广泛采用的Java单元测试库,我们将在6.031中大量使用它。JUnit单元测试被编写为在批注之前加注的方法@Test。单元测试方法典型地包含一个或多个调用的模块被测试,然后使用像断言方法还检查结果assertEquals,assertTrue和assertFalse。

例如,Math.max()为JUnit实施时,我们上面选择的测试可能看起来像这样:

@Test
public void testALessThanB() {
assertEquals(2, Math.max(1, 2));
}

@Test
public void testBothEqual() {
assertEquals(9, Math.max(9, 9));
}

@Test
public void testAGreaterThanB() {
assertEquals(-5. Math.max(-5, -6));
}
请注意,参数to的顺序assertEquals很重要。第一个参数应该是测试想要看到的预期结果,通常是一个常数。第二个参数是实际结果,即代码实际执行的操作。如果将它们切换开,那么当测试失败时,JUnit将产生一个令人困惑的错误消息。 JUnit支持的所有断言始终遵循此顺序:期望第一,实际第二。

如果测试方法中的断言失败,则该测试方法立即返回,并且JUnit记录该测试失败。一个测试类可以包含任意数量的@Test方法,当您使用JUnit运行该测试类时,这些方法可以独立运行。即使一种测试方法失败,其他方法仍将运行。

记录您的测试策略
对于左侧的示例功能,右侧是我们如何记录在上面的分区练习中工作的测试策略的方法。该策略还解决了一些我们之前未考虑的边界值。

/**

  • Reverses the end of a string.
  • For example:
  • reverseEnd(“Hello, world”, 5)
  • returns “Hellodlrow ,”
  • With start == 0, reverses the entire text.
  • With start == text.length(), reverses nothing.
  • @param text non-null String that will have
  •            its end reversed
    
  • @param start the index at which the
  •            remainder of the input is
    
  •            reversed, requires 0 <=
    
  •            start <= text.length()
    
  • @return input text with the substring from
  •           start to the end of the string
    
  •           reversed
    

*/
static String reverseEnd(String text, int start)
在测试课程的顶部记录策略:

/*

  • Testing strategy
  • Partition the inputs as follows:
  • text.length(): 0, 1, > 1
  • start: 0, 1, 1 < start < text.length(),
  •            text.length() - 1, text.length()
    
  • text.length()-start: 0, 1, even > 1, odd > 1
  • Include even- and odd-length reversals because
  • only odd has a middle element that doesn’t move.
  • Exhaustive Cartesian coverage of partitions.
    */
    每个测试方法上方应有一条注释,说明如何选择其测试用例,即它涵盖分区的哪些部分:

// covers test.length() = 0,
// start = 0 = text.length(),
// text.length()-start = 0
@Test public void testEmpty() {
assertEquals("", reverseEnd("", 0));
}

黑盒和白盒测试

从上面回顾,规范是对函数行为的描述-参数的类型,返回值的类型以及它们之间的约束和关系。

黑盒测试意味着仅从规范中选择测试用例,而不是从功能的实现中选择。到目前为止,这就是我们在示例中所做的。我们对分区进行了划分,multiply并max在其中查找边界,而未查看这些功能的实际代码。

白盒测试(也称为玻璃盒测试)是指选择具有实际功能实现知识的测试用例。例如,如果实现根据输入选择不同的算法,则应根据这些域进行分区。如果该实现保留一个内部缓存来记住先前输入的答案,那么您应该测试重复的输入。

在进行白盒测试时,必须注意您的测试用例不需要规范未明确要求的特定实现行为。例如,如果规范说“抛出一个异常,如果输入的格式不”,那么你的测试应该不会检查专门的NullPointerException只是因为这是当前实现做什么。这种情况下的规范允许抛出任何异常,因此您的测试用例同样应该具有通用性,以保持实现者的自由。在规范类中,我们将对此有更多的话要说。

阅读练习
黑盒和白盒测试

覆盖范围

判断测试套件的一种方法是询问其如何充分执行程序。这个概念称为覆盖率。以下是三种常见的覆盖范围:

语句覆盖率:每个语句是否都由某个测试用例运行?
分支覆盖率:对于程序中的每个if或while语句,某个测试用例是否同时遵循正确与错误的方向?
路径覆盖:某个测试用例是否采用了分支的所有可能组合(程序中的每个路径)?
分支覆盖范围比语句覆盖范围要强(需要更多测试才能完成),路径覆盖范围比分支覆盖范围要强。在行业中,100%的声明覆盖率是一个常见的目标,但是即使由于防御性代码无法到达(例如“永远不能到达这里”的断言),也很难实现这一目标。100%的分支机构覆盖率是非常需要的,并且安全性至关重要的行业法规具有更加艰巨的标准(例如,MC / DC,修改后的条件/决策覆盖率)。不幸的是,100%的路径覆盖是不可行的,需要指数级的测试套件才能实现。

一种标准的测试方法是添加测试,直到测试套件达到足够的语句覆盖率:即,以便程序中的每个可访问语句都由至少一个测试用例执行。在实践中,语句覆盖率通常由代码覆盖率工具衡量,该工具计算您的测试套件运行每个语句的次数。使用这种工具,白盒测试很容易。您只需衡量黑盒测试的覆盖范围,然后添加更多测试用例,直到所有重要语句都记录为已执行即可。

Eclipse的EclEmma代码覆盖工具
右图是EclEmma的一个很好的Eclipse代码覆盖工具。

测试套件已执行的行被涂成绿色,尚未覆盖的行被涂成红色。如果您从覆盖率工具中看到了此结果,那么下一步将是提出一个导致while循环主体执行的测试用例,并将其添加到您的测试套件中,以使红线变为绿色。

阅读练习
使用覆盖率工具
单元测试与集成测试以及存根
到目前为止,我们一直在讨论独立测试单个模块(如方法或类)的单元测试。隔离地测试模块可以使调试更加容易。当某个模块的单元测试失败时,您可以更有信心在该模块中找到该错误,而不是在程序中的任何位置。

与单元测试相反,集成测试对模块的组合甚至整个程序进行测试。如果您拥有的只是集成测试,那么当测试失败时,您必须寻找错误。它可能在程序中的任何位置。集成测试仍然很重要,因为程序可能会在模块之间的连接上失败。例如,一个模块可能期望的输入与从另一个模块实际获得的输入不同。但是,如果您有一套完整的单元测试,可以使您对各个模块的正确性充满信心,那么您将无需进行太多的工作来查找错误。

假设您正在构建一个网络搜索引擎。您的getWebPage()其中两个模块可能是,用于下载网页,和extractWords(),用于将页面分成其组成词:

/** @return the contents of the web page downloaded from url
*/
public static String getWebPage(URL url) {…}

/** @return the words in string s, in the order they appear,

  •      where a word is a contiguous sequence of 
    
  •      non-whitespace and non-punctuation characters 
    

*/
public static List extractWords(String s) { … }
这些方法可能被另一个模块makeIndex()用作构成搜索引擎索引的网络爬网程序的一部分:

/** @return an index mapping a word to the set of URLs

  •      containing that word, for all webpages in the input set 
    

*/
public static Map<String, Set> makeIndex(Set urls) {

for (URL url : urls) {
String page = getWebPage(url);
List words = extractWords(page);

}

}
在我们的测试套件中,我们希望:

单元测试仅用于getWebPage()在各种URL上进行测试
单元测试只是针对extractWords()在各种字符串上进行测试
makeIndex()对该单元进行各种单元测试的单元测试
程序员有时犯的一个错误是以extractWords()这种方式编写测试用例的:测试用例依赖于getWebPage()正确的方法。最好单独考虑和测试extractWords(),然后对其进行分区。使用涉及网页内容的测试分区可能是合理的,因为这extractWords()是程序中实际使用的方式。但是实际上不要getWebPage()从测试用例中调用,因为getWebPage()可能有错误!而是将网页内容存储为文字字符串,然后将其直接传递给extractWords()。这样,您就可以编写一个隔离的单元测试,并且如果失败,您可以更有把握地确定该错误位于实际测试的模块中extractWords()。

请注意,makeIndex()无法通过这种方式轻易地隔离的单元测试。当一个测试用例调用时makeIndex(),它不仅测试了内部代码的正确性makeIndex(),而且还测试了由makeIndex()。如果测试失败,则可能是这些方法中的任何一个所引起的错误。这就是为什么我们要对getWebPage()和extractWords()进行单独的测试,以提高我们对这些模块的信心,并将问题定位到makeIndex()将它们连接在一起的代码中。

makeIndex()如果我们编写它调用的模块的存根版本,则可以隔离更高级别的模块。例如,的存根getWebPage()根本不会访问Internet,但是无论将什么URL传递给它,它都将返回模拟网页内容。类的存根通常称为模拟对象。存根是构建大型系统时的一项重要技术,但是我们通常不会在6.031中使用它们。

自动化测试和回归测试

没有什么比完全自动化使测试更容易运行,更有可能运行了。 自动化测试是指运行测试并自动检查其结果。测试驱动程序不应是提示您输入信息并打印出结果以供您手动检查的交互式程序。相反,测试驱动程序应在固定的测试用例上调用模块本身,并自动检查结果是否正确。测试驱动程序的结果应为“所有测试均正常”或“这些测试失败:……”良好的测试框架(如JUnit)可帮助您构建自动化测试套件。

注意,像JUnit这样的自动化测试框架使运行测试变得容易,但是您仍然必须自己提出好的测试用例。 自动测试生成是一个难题,仍然是活跃的计算机科学研究的主题。

一旦有了测试自动化,在修改代码时重新运行测试就非常重要。软件工程师从痛苦的经历中知道,对大型或复杂程序的任何更改都是危险的。无论您是要修复另一个错误,添加新功能还是优化代码以使其更快,一个保留正确行为基线的自动化测试套件(即使只是几次测试)也可以节省您的培根。在更改代码时经常运行测试可防止程序退化-在修复新错误或添加新功能时会引入其他错误。每次更改后运行所有测试称为回归测试。

每当您发现并修复错误时,请输入引发该错误的输入并将其作为测试用例添加到您的自动化测试套件中。这种测试用例称为回归测试。这有助于用良好的测试用例填充您的测试套件。请记住,如果测试会引发错误,则它是很好的-每个回归测试都在您的代码的一个版本中完成!保存回归测试还可以防止因重新引入错误而导致的回归。该错误可能很容易犯,因为它已经发生过一次。

这个想法也导致了测试优先的调试。当出现错误时,立即为其编写一个测试用例,并立即将其添加到您的测试套件中。找到并修复该错误后,所有测试用例都将通过,并且您将完成调试并对该错误进行回归测试。

实际上,自动化测试和回归测试这两个想法几乎总是结合使用。
回归测试只有在测试可以自动频繁运行的情况下才是切实可行的。相反,如果您已经为项目准备了自动测试,则最好使用它来防止回归。因此,自动化回归测试是现代软件工程的最佳实践。

阅读练习
回归测试
运行自动化测试
测试技术

概括

在阅读过程中,我们看到了以下想法:

测试优先编程。在编写代码之前,先编写测试。
系统地选择测试用例的分区和边界。
白盒测试和声明范围,用于填写测试套件。
尽可能孤立地对每个模块进行单元测试。
自动化的回归测试可防止错误再次出现。
今天的阅读主题与我们的优质软件的三个关键属性有关,如下所示:

安全的错误。 测试是关于在代码中查找错误,而测试优先编程是关于在引入错误后尽快发现它们。

容易明白。 测试不能像代码审查那样帮助您。

准备好进行更改。 通过编写仅依赖于规范中行为的测试来考虑是否准备好进行更改。我们还讨论了自动回归测试,该测试有助于防止对代码进行更改时错误再次出现。

读者练习

至此,您应该已经完成​​了上面的所有阅读练习。

完成阅读练习后,您将在每次课堂会议开始时进行纳米测验,并且必须在上课前一天晚上10点之前提交习题。

此时,您还应该完成以下Java Tutor级别:

✓平等1/1

✓列表和数组1/1

✓方法1/1

合作作者:Saman Amarasinghe,Adam Chlipala,Srini Devadas,Michael Ernst,Max Goldman,John Guttag,Daniel Jackson,Rob Miller,Martin Rinard和Armando Solar-Lezama。这项工作已获得CC BY-SA 4.0的许可。

标签:翻译,错误,代码,单元测试,套件,测试用例,测试
来源: https://blog.csdn.net/weixin_45038507/article/details/117002532

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有