为什么要使用JUnit进行测试?

134
也许我的问题很基础,但我真的不明白在什么情况下我会使用
无论是编写简单应用程序还是更大的应用程序,我都使用System.out语句进行测试,这对我来说似乎非常容易。
为什么要使用JUnit创建测试类,在项目中创建不必要的文件夹,如果我们仍然需要调用相同的方法,检查它们返回什么,那么我们就有了注释一切的额外负担?
为什么不编写一个类并使用System.out一次性进行测试,而不创建测试类呢?
附注:我从未参与过大型项目,我只是在学习。
那么它的目的是什么?

1
阅读《单元测试的艺术》(http://www.amazon.com/Art-Unit-Testing-Examples-Net/dp/1933988274)。 - rgeorge
7
您意识到每次在程序中更改任何内容时,您先前手动检查输出的所有工作都将无效,并且您必须从头开始重新做吗? - Thorbjørn Ravn Andersen
不仅仅是“测试”,而是“聪明的测试”非常重要。这里有一个很好的例子:http://wp.me/prMeE-11 - akcasoy
11个回答

149
那不是测试,而是“手动查看输出”(业内称为LMAO)。更正式地说,它被称为“手动查找异常输出”(LMFAO)。任何时候你改变代码,你必须运行应用程序并对所有受这些更改影响的代码进行LMFAO。即使在小项目中,这也是有问题和容易出错的。现在扩展到50k、250k、1m行或更多的代码,并且每次进行编码更改时都要进行LMFAO。这不仅不愉快,而且不可能:你已经扩展了输入、输出、标志、条件的组合,而且很难激活所有可能的分支。更糟糕的是,LMFAO可能意味着访问网站页面、运行报告、检查数百万行跨越数十个文件和机器的日志、阅读生成和传送的电子邮件、检查短信、检查机器人的路径、灌装苏打水、从一百个Web服务聚合数据、检查财务交易的审计跟踪等等... 你明白了吧。“输出”不仅仅意味着几行文本,“输出”意味着系统行为的综合。最后,单元测试和行为测试定义了系统的行为。测试可以由持续集成服务器运行并检查正确性。当然,System.out也可以这样做,但CI服务器不会知道其中一个是否错误 - 如果确实如此,它们就是单元测试,你可能想使用一个框架。无论我们认为自己有多好,人类都不是好的单元测试框架或CI服务器。
注:LMAO实际上是测试,但仅在非常有限的意义上。它不能在整个项目中或作为过程的一部分以任何有意义的方式重复。这类似于在REPL中逐步开发,但从未规范化这些递增测试。

3
第一句话完全不真实,毫无根据。 - Michael Borgwardt

51
我们编写测试来验证程序行为的正确性。
通过检查输出语句的内容使用您的眼睛来验证程序行为的正确性是一种手动,或更具体地说,是一种视觉过程。
你可以争辩说

视觉检查有效,我检查代码是否按照预期运行,在这些情况下,一旦我确认它是正确的,我们就可以继续进行。

现在首先,你对代码是否正确工作感兴趣是很好的。这是一件好事。你比别人更先进!不幸的是,这种方法存在问题。
视觉检查的第一个问题是,如果发生严重事故导致你失去了检查代码正确性的能力,那么你就会遇到麻烦。
第二个问题是,用于视觉检查的一对眼睛与其所有者的大脑紧密耦合。如果代码的作者也拥有视觉检查过程中使用的眼睛,则验证正确性的过程取决于视觉检查者大脑中关于程序的内部化知识。

对于新的眼睛来说,仅仅因为他们没有与原始编码者的大脑合作,所以要验证代码的正确性是很困难的。第二双眼睛的所有者必须与代码的原始作者进行交流,才能充分理解有问题的代码。作为共享知识的手段,谈话是出了名的不可靠。如果原始编码者无法接触到新的眼睛,那么这一点就毫无意义了。在这种情况下,新的眼睛必须阅读原始代码。

阅读没有单元测试覆盖的其他人的代码比阅读有关联的单元测试的代码更加困难。最好的情况是阅读别人的代码是棘手的工作,而在软件工程中,这是最艰巨的任务。雇主在招聘职位时强调项目是全新的原因是有道理的。从头开始编写代码比修改现有代码更容易,因此使广告招聘更具吸引力。

通过单元测试,我们将代码分解成其组成部分。对于每个组件,我们都会陈述程序应该如何行动。每个单元测试都讲述了程序的一部分在特定情况下应该如何运作的故事。每个单元测试就像合同中的条款,描述了客户端代码视角下应该发生什么。
这意味着一个新的视角会有两个关于所讨论代码的实时和准确的文档。首先,他们有代码本身,实现方式是怎样的;其次,他们拥有原始编码器在一系列正式陈述中描述的所有知识,这些陈述讲述了这段代码应该如何运作。
单元测试捕获并正式描述了原始作者在实现类时所具备的知识。它们提供了一个描述当客户端使用该类时,该类如何行为的描述。
你对这个问题的有用性提出质疑是正确的,因为编写无用的单元测试、未覆盖所有相关代码、变得陈旧过时等情况都是可能发生的。我们如何确保单元测试不仅模拟而且改进了一个有知识、有责任感的作者在运行时视觉检查他们代码输出语句的过程呢?先编写单元测试,然后编写代码使该测试通过。完成后,让计算机运行测试,它们快速、擅长执行重复任务,非常适合这项工作。
每次修改被测试的代码时,通过审核以确保测试质量,并为每个构建运行测试。如果测试失败,则立即修复。
我们自动化了测试运行的过程,以便在每次项目构建时都会运行测试。我们还自动化了代码覆盖率报告的生成,该报告详细说明了测试覆盖并执行了多少百分比的代码。我们力求高百分比。一些公司将阻止未编写足够单元测试来描述代码行为变化的代码更改被检入到源代码控制中。通常,在作者进行代码更改的同时,另一个人会审查这些更改。评审人将仔细查看更改,确保更改理解和被充分测试。因此,审查过程是手动的,但当测试(单元测试、集成测试和可能的用户验收测试)通过此手动审查过程后,它们就成为了自动构建过程的一部分。每次检入更改时都会运行这些测试。服务器作为构建过程的一部分执行此任务。
自动运行的测试可以保持代码行为的完整性,并有助于预防未来对代码库的更改破坏代码。

最后,提供测试可以让您积极地重构代码,因为您可以在确信更改不会破坏现有测试的情况下进行大规模的代码改进。

测试驱动开发有一个警告,那就是您必须编写可测试的代码。这涉及到编写接口并使用诸如依赖注入之类的技术来实例化协作对象。请查看Kent Beck的工作,他很好地描述了TDD。查找编写接口并学习


13

当你使用像System.out这样的东西进行测试时,你只能测试一小部分可能的用例。当你处理可能接受无限数量不同输入的系统时,这并不是非常彻底的。

单元测试旨在允许您使用非常大而且多样化的不同数据输入集来快速运行应用程序测试。此外,最好的单元测试还考虑边界情况,例如恰好位于有效范围边缘的数据输入。

对于一个人来测试所有这些不同的输入可能需要几周的时间,而机器只需要几分钟。

可以这样想:您也没有在“测试”静态内容。您的应用程序很可能会不断变化。因此,这些单元测试旨在在编译或部署周期的不同时间点运行。也许最大的优点是:

如果您在代码中出现问题,您将立即知道,而不是在部署后,也不是当QA测试人员发现错误时,也不是当您的客户取消时。您也有更好的机会立即修复故障,因为很明显导致代码问题的原因很可能是自上次编译以来发生的。因此,需要解决问题的调查工作量大大降低。


9

我添加了其他System.out无法做到的一些事情:

  • 使每个测试用例独立(这很重要)

    JUnit可以做到:每次都会创建新的测试用例实例并调用@Before方法。

  • 将测试代码与源代码分开

    JUnit可以做到。

  • 与CI集成

    JUnit可以通过Ant和Maven来实现。

  • 轻松安排和组合测试用例

    JUnit可以使用@Ignore和测试套件来实现。

  • 轻松检查结果

    JUnit提供了许多Assert方法(assertEqualsassertSame等)

  • Mock和Stub让您专注于测试模块

    JUnit可以做到:使用Mock和Stub让您设置正确的夹具,并专注于测试模块逻辑。


9

单元测试确保代码按照预期工作。如果您需要更改代码以构建新功能或修复错误,它们还非常有用,以确保代码仍然按照预期工作。高测试覆盖率可以让您在不进行大量手动测试的情况下继续开发功能。

您使用的System.out的手动方法是好的,但并不是最好的方法。这是您执行的一次性测试。在现实世界中,需求经常发生变化,大多数时候您会对现有函数和类进行大量修改。因此...您不必每次都测试已编写的代码。

JUnit 还具有一些更高级的特性,例如:

断言语句

JUnit 提供了用于测试某些条件的方法,这些方法通常以 assert 开头,并允许您指定错误消息、期望结果和实际结果。

其中一些方法包括:

  1. fail([message]) - 让测试失败。可以用来检查代码的某个部分是否被执行。或者在测试代码实现之前使用失败的测试。
  2. assertTrue(true) / assertTrue(false) - 始终为true / false。如果测试尚未实现,可以预定义测试结果。
  3. assertTrue([message,] condition) - 检查布尔值condition是否为真。
  4. assertEquals([message,] expected, actual) - 测试两个值是否相等(根据实现的equals方法,否则使用==引用比较)。注意:对于数组,检查的是引用而不是内容,请使用assertArrayEquals([message,] expected, actual)
  5. assertEquals([message,] expected, actual, delta) - 测试两个浮点数或双精度值是否相距一定距离,由delta值控制。
  6. assertNull([message,] object) - 检查对象是否为null

等等,这里 这里查看完整的Javadoc示例。

测试套件

使用测试套件,您可以在某种意义上将多个测试类组合成一个单元,以便一次性执行它们。一个简单的例子,将测试类 MyClassTest MySecondClassTest组合成名为AllTests的一个套件:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ MyClassTest.class, MySecondClassTest.class })
public class AllTests { } 

6
JUnit的主要优点在于它是自动化的,而不是您手动检查打印输出。您编写的每个测试都与系统保持一致。这意味着,如果您进行了意外的副作用更改,您的测试将捕获它并失败,而不是您需要记住在每次更改后手动测试所有内容。

4
JUnit是Java编程语言的单元测试框架之一,它在测试驱动开发中非常重要。JUnit属于xUnit家族中的一种。
JUnit倡导“先测试再编码”的理念,强调为可测试的代码建立测试数据,并在实现之前对其进行测试。这种方法就像“少测一点,写一点,再测一点,写一点……”一样,可以增加程序员的生产力和程序代码的稳定性,减少程序员的压力以及调试所花费的时间。
特点: JUnit是一个开源框架,用于编写和运行测试。
提供注释来标识测试方法。
提供断言来测试预期结果。
提供测试运行器来运行测试。
JUnit测试允许您更快地编写代码,同时提高质量。
JUnit简洁而优雅。它不太复杂,需要的时间较短。
JUnit测试可以自动运行并检查自己的结果,并提供即时反馈。无需手动查看测试结果报告。
JUnit测试可以组织成包含测试用例甚至其他测试套件的测试套件。
JUnit显示测试进度的条形图,如果测试进行得很好,则为绿色,如果测试失败,则变为红色。

2
我认为JUnit的必要性有些不同。您可以自己编写所有测试用例,但这很繁琐。以下是问题:
1. 与其使用System.out,我们可以添加if(value1.equals(value2))并返回0或-1或错误消息。在这种情况下,我们需要一个“主”测试类来运行所有这些方法,并检查结果以及维护哪些测试用例失败和哪些通过。
2. 如果要添加更多测试,还需要将它们添加到此“主”测试类中。更改现有代码。如果要从测试类自动检测测试用例,则需要使用反射。
3. 所有测试和运行测试的主类都无法被eclipse检测到,因此您需要编写自定义调试/运行配置文件来运行这些测试。尽管如此,您仍然看不到那些漂亮的绿色/红色颜色输出。
而JUnit所做的是:
1. 它具有assertXXX()方法,这些方法对于从条件中打印有用的错误消息并将结果传达给“main”类非常有用。
2. “主”类称为runner,由JUnit提供,因此我们不必编写任何内容。它通过反射自动检测测试方法。如果使用@Test注释添加了新测试,则会自动检测它们。
3. JUnit还具有eclipse集成和maven / gradle集成,因此很容易运行测试,您不必编写自定义运行配置文件。
我不是JUnit的专家,所以这就是我现在所理解的内容,将来还会添加更多内容。

我猜在第一部分中,你写了如果没有JUnit来使单元测试比system.out.println语句更好的话,我们会做些什么。也许JUnit是一些程序员尝试的结果,他们感到需要编写一个单独的测试框架来进行自动化测试,因此JUnit应运而生,也许是这样。 - Saurabh Patil

1

如果你不使用测试框架,就无法编写任何测试用例,否则你将不得不编写自己的测试框架来使你的测试用例更具说服力。以下是一些关于JUnit框架的信息,除此之外,您还可以使用TestNG框架。

Junit是Java编程语言中广泛使用的测试框架。您可以使用此自动化框架进行单元测试和UI测试。它帮助我们使用不同的注释定义代码的执行流程。Junit建立在“先测试,后编码”的理念上,这有助于提高测试用例的生产率和代码的稳定性。

Junit测试的重要特点:

  1. 它是开源测试框架,允许用户有效地编写和运行测试用例。
  2. 提供各种类型的注释以识别测试方法。
  3. 提供不同类型的断言来验证测试用例执行的结果。
  4. 它还为运行测试提供了测试运行器。
  5. 它非常简单,因此节省时间。
  6. 它提供了以测试套件形式组织测试用例的方式。
  7. 它以简单而优雅的方式提供测试用例结果。
  8. 您可以将jUnit与Eclipse、Android Studio、Maven&Ant、Gradle和Jenkins集成。

0

JUNIT是Java开发人员通常接受的方法。 他们可以提供类似的预期输入给函数,并根据情况决定编写的代码是否完美,如果测试用例失败,则可能需要实现不同的方法。 JUNIT将加快开发速度,并确保函数中没有缺陷。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接