来自云龙湖轮廓分明的月亮 发表于 2024-5-18 17:51:30

Junit 4 测试方法

1. JUnit 最佳实践指南

原文: https://howtodoinjava.com/best-practices/unit-testing-best-practices-junit-reference-guide/
我假设您相识 JUnit 的基础知识。 如果您没有基础知识,请首先阅读(已针对 JUnit 5 更新)。 现在,我们将介绍在编写测试用例时必须考虑的 junit 最佳实践。
编写糟糕的单元测试非常容易,这会给项目增长很少的价值,同时又会天文数字地增长代码更改的成本。
Table of Contents

Unit testing is not about finding bugs
Tips for writing great unit tests
   Test only one code unit at a time
   Don’t make unnecessary assertions
   Make each test independent to all the others
   Mock out all external services and state
   Don’t unit-test configuration settings
   Name your unit tests clearly and consistently
   Write tests for methods that have the fewest dependencies first, and work your way up
   All methods, regardless of visibility, should have appropriate unit tests
   Aim for each unit test method to perform exactly one assertion
   Create unit tests that target exceptions
   Use the most appropriate assertion methods.
   Put assertion parameters in the proper order
   Ensure that test code is separated from production code
   Do not print anything out in unit tests
   Do not use static members in a test class
   Do not write your own catch blocks that exist only to fail a test
   Do not rely on indirect testing
   Integrate Testcases with build script
   Do not skip unit tests
   Capture results using the XML formatter
Summary在编程中,“单元测试是一种测试源代码的各个单元以确定它们是否得当使用的方法。” 现在,这个源代码单元可以在不同的环境下使用。
例如:在过程编程中,一个单元可以是一个完备的模块,但更常见的是一个单独的函数或过程。 在面向对象编程中,一个单元通常是一个完备的接口,例如一个类,但可以是一个单独的方法。 直观上,应该将一个单元视为应用中最小的可测试部门。
单元测试不是寻找回归错误

好吧,相识单元测试的动机很重要。 单元测试不是查找应用范围的错误或检测回归缺陷的有效方法。 根据界说,单元测试将分别检查代码的每个单元。 但是,当您的应用实际运行时,所有这些单元都必须协同工作,并且整个过程比独立测试部门的总和更加复杂和微妙。 证实 X 和 Y 组件都可以独立工作,并不表示它们相互兼容或配置正确。
因此,如果您要查找回归错误,则实际上一起运行整个应用会更有效,由于它将在生产环境中运行,就像手动测试 。 如果您将这种测试主动化以检测将来发生的破坏,则称为集成测试,通常使用与单元测试不同的技术。
“从本质上讲,单元测试应该被视为计划过程的一部门,由于它是 TDD(测试驱动开发)中的一部门。” 这应该用于支持计划过程,以便计划人员可以辨认体系中的每个最小模块并分别进行测试。
编写出色的单元测试的提示

1.一次仅测试一个代码单元

首先,也许是最重要的。 当我们实验测试代码单元时,该单元可能有多个用例。 我们应该始终在单独的测试用例中测试每个用例。 例如,如果我们为一个函数编写测试用例,该函数应该具有两个参数,并且在进行一些处理后应返回一个值,那么不同的用例可能是:

[*]第一个参数可以为空。 它应该抛出无效的参数异常。
[*]第二个参数可以为空。 它应该抛出无效的参数异常。
[*]两者都可以为空。 它应该抛出无效的参数异常。
[*]最后,测试功能的有效输出。 它应该返回有效的预定输出。
当您进行了一些代码更改或重构,然后测试功能没有破坏时,这将很有资助,运行测试用例就足够了。 同样,如果您更改任何举动,则需要更改单个或最少数量的测试用例。
2.不要做出不必要的断言

请记着,单元测试是某种举动应该怎样工作的计划规范,而不是对代码可巧所做的所有事情的观察列表。
不要试图断言所有事情都只专注于要测试的内容,否则,由于一个原因,您最终将导致多个测试用例失败,这无助于实现任何目标。
3.使每个测试独立于其他所有测试

不要制作单元测试用例链。 这将阻止您确定测试用例失败的根本原因,并且您将不得不调试代码。 同样,它会创建依赖关系,这意味着如果您必须更改一个测试用例,那么您就不必要在多个测试用例中进行更改。
实验对所有测试用例使用@Before和@After方法。 如果您需要多种支持@Before或@After中的不同测试用例的方法,请考虑创建新的Test类。
4.模拟所有外部服务和状态

否则,这些外部服务中的举动会与多个测试重叠,并且状态数据意味着不同的单元测试会影响相互的效果。 如果您必须按照特定的次序运行测试,或者只有在数据库或网络连接处于运动状态时才能运行,则您肯定走错了路。
别的,这很重要,由于您不希望调试由于某些外部体系中的错误而实际失败的测试用例。
(顺便说一句,有时您的架构可能意味着您的代码在单元测试期间会触及静态变量。如果可以,请制止这样做,但如果不能这样做,请至少确保每个测试在运行之前将相关的静态操作重置为已知状态。 )
5.不进行单元测试配置设置

根据界说,您的配置设置不是任何代码单元的一部门(这就是为什么您将设置提取到某些属性文件中的原因)。 即使您可以编写用于检查配置的单元测试,也可以只编写一个或两个测试用例,以验证配置加载代码是否正常工作,仅此而已。
在每个单独的测试用例中测试所有配置设置仅证实了一件事:“ 您知道怎样复制和粘贴。”
6.清晰一致地定名您的单元测试

好吧,这可能是最重要的一点,要记着并保持关注。 您必须根据测试案例的实际用途和测试来定名它们。 使用类名和方法名作为测试用例名称的测试用例定名约定从来都不是一个好主意。 每次更改方法名称或类名称时,您最终也会更新很多测试用例。
但是,如果您的测试用例名称是逻辑的,即基于操作,则几乎不需要修改,由于大多数应用逻辑将保持不变。
例如。 测试用例名称应类似于:
1) TestCreateEmployee_NullId_ShouldThrowException
2) TestCreateEmployee_NegativeId_ShouldThrowException
3) TestCreateEmployee_DuplicateId_ShouldThrowException
4) TestCreateEmployee_ValidId_ShouldPass7.首先对依赖关系最少的方法编写测试,然后逐步进行

该原则表明,如果要测试Employee模块,则应该首先测试Employee模块的创建,由于它对外部测试用例的依赖项最小。 一旦完成,就开始编写Employee修改的测试用例,由于它们需要一些雇员在数据库中。
要在数据库中拥有一些员工,您的创建员工测试用例必须先通过,然后才能继承。 这样,如果员工创建逻辑中存在一些错误,则可以更早地发现它。
8.所有方法,无论是否可见,都应进行适当的单元测试

好吧,这确实是有争议的。 您需要查找代码中最关键的部门,并且应该对其进行测试,而不必担心它们是否是私有的。 这些方法可以具有从一到两个类调用的某些关键算法,但是它们起着重要的作用。 您想确保它们按预期工作。
9.针对每种单元测试方法,精确执行一个断言

即使这不是经验法则,您也应该实验在一个测试用例中仅测试一件事。 不要在单个测试用例中使用断言来测试多个事物。 这样,如果某个测试用例失败,则可以确切地知道出了什么问题。
10.创建针对异常的单元测试

如果某些测试用例希望从应用中抛出异常,请使用“Expected”属性。 实验制止在catch块中捕捉异常,并使用fail或asset方法结束测试。
@Test(expected=SomeDomainSpecificException.SubException.class) 11.使用最符合的断言方法

每个测试用例都可以使用很多断言方法。 运用最适当的理由和思想。 他们在那里是有目标的。 尊敬他们。
12.以正确的次序放置断言参数

断言方法通常接纳两个参数。 一个是盼望值,第二个是原始值。 根据需要依次转达它们。 如果出现问题,这将有助于正确的消息剖析。
13.确保测试代码与生产代码分开

在您的构建脚本中,确保测试代码未与实际源代码一起部署。 这浪费了资源。
14.不要在单元测试中打印任何内容

如果您正确地遵循了所有准则,那么您将不需要在测试用例中添加任何打印语句。 如果您想拥有一个,请重新访问您的测试用例,您做错了什么。
15.不要在测试类中使用静态成员。 如果您已经使用过,则针对每个测试用例重新初始化

我们已经说过,每个测试用例都应该相互独立,因此永远不需要静态数据成员。 但是,如果您在紧急环境下需要任何资助,请记着在执行每个测试用例之前将其重新初始化为初始值。
16.不要写本身的只能使测试失败的catch块

如果测试代码中的任何方法引发某些异常,则不要编写catch块只是为了捕捉异常并使测试用例失败。 而是在测试用例声明本身中使用throws Exception语句。 我将建议使用Exception类,并且不要使用Exception的特定子类。 这也将增长测试范围。
17.不要依赖间接测试

不要假定特定的测试用例也会测试另一种环境。 这增长了歧义。 而是为每种环境编写另一个测试用例。
18.将测试用例与构建脚本集成

最好将测试用例与构建脚本集成在一起,以使其在生产环境中主动执行。 这提高了应用以及测试设置的可靠性。
19.不要跳过单元测试

如果某些测试用例现在无效,则将其从源代码中删除。 不要使用@Ignore或svn.test.skip来跳过它们的执行。 在源代码中包含无效的测试用例不会资助任何人。
20.使用 XML 格式器捕捉效果

这是感觉良好的因素。 它绝对不会带来直接的好处,但是可以使运行单元测试变得有趣和有趣。 您可以将 JUnit 与 ant 构建脚本集成,并天生测试用例,并使用一些颜色编码以 XML 格式运行报告。 遵循也是一种很好的做法。
总结

毫无疑问,单元测试可以显着提高项目质量。 我们这个行业中的很多学者声称,任何单元测试总比没有好,但是我不同意:测试套件可以成为一项重要资产,但是不良套件可以成为负担不起的同样巨大的负担。 这取决于这些测试的质量,这似乎取决于其开发人员对单元测试的目标和原理的明白程度。
如果您明白上述准则,并实验在下一组测试用例中实现此中的大多数准则,那么您一定会感到与众不同。
请让我知道您的想法。
学习愉快!
2. 用 JUnit 编写测试

在 JUnit 中,测试方法带有@Test注解。 为了运行该方法,JUnit 首先构造一个新的类实例,然后调用带注解的方法。 测试抛出的任何异常将由 JUnit 报告为失败。 如果未引发任何异常,则假定测试成功。
import java.util.ArrayList;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class Example {
        @BeforeClass
        public static void setup() {
        }

        @Before
        public void setupThis() {
        }

        @Test
        public void method() {
                org.junit.Assert.assertTrue(new ArrayList().isEmpty());
        }

        @After
        public void tearThis() {
        }

        @AfterClass
        public static void tear() {
        }
}3. 断言

在JUnit 4中,org.junit.Assert 类提供了一系列的静态方法,用于在单元测试中进行断言(Assertion)。断言是测试代码中的一种关键机制,用于验证代码的举动是否符合预期。如果断言失败,即实际效果与预期效果不一致,那么测试就会失败。
以下是一些常用的Assert方法:

[*]assertEquals: 验证两个值是否相等。
Assert.assertEquals(expected, actual);
[*]assertTrue: 验证一个布尔表达式是否为true。
Assert.assertTrue(booleanExpression);
[*]assertFalse: 验证一个布尔表达式是否为false。
Assert.assertFalse(booleanExpression);
[*]assertNotNull: 验证一个引用是否不是null。
Assert.assertNotNull(object);
[*]assertNull: 验证一个引用是否是null。
Assert.assertNull(object);
[*]assertSame: 验证两个引用是否指向同一个对象。
Assert.assertSame(expected, actual);
[*]assertNotSame: 验证两个引用是否指向不同的对象。
Assert.assertNotSame(val1, val2);
[*]assertArrayEquals: 验证两个数组是否相等。
Assert.assertArrayEquals(expectedArray, actualArray);
[*]fail: 手动标志测试为失败。
Assert.fail("Test failed with a message");
下面是一个使用Assert方法的JUnit测试示例:
import org.junit.Test;
import static org.junit.Assert.*;

public class ExampleTest {

    @Test
    public void testAddition() {
      int expected = 5;
      int actual = 2 + 3;
      assertEquals("Addition method failed", expected, actual);
      assertTrue("2 + 3 should be greater than 4", actual > 4);
      assertNotNull("The result should not be null", actual);
    }
}在这个例子中,我们测试了一个简朴的加法操作,使用了assertEquals来验证实际效果是否与预期效果相等,使用了assertTrue来验证一个布尔表达式,以及assertNotNull来确保效果不为null。
断言方法通常接受两个参数:一个是预期值,另一个是实际值。有些断言方法还可以接受一个字符串消息,该消息在断言失败时显示,以资助快速定位问题。
使用断言是编写有效单元测试的关键部门,它确保了测试的正确性和可维护性。
2. 注解

1. @Test

- 表示该方法是一个测试方法。JUnit将会执行标记了此注解的方法。2. @Before

- 表示在每个测试方法之前执行的方法,通常用于测试环境的初始化。3. @After

- 表示在每个测试方法之后执行的方法,通常用于清理测试环境。4. @BeforeClass

`
- 表示在所有测试方法之前仅执行一次的方法,必须是静态方法。通常用于一次性的全局设置。
5. @AfterClass

- 表示在所有测试方法之后仅执行一次的方法,必须是静态方法。通常用于一次性的全局清理。6. @Ignore

- 表示暂时忽略该测试方法,不执行它。7. @RunWith

- 用于指定一个自定义的测试运行器。例如,可以使用`@RunWith(Parameterized.class)`来进行参数化测试。8. @Test(expected = Exception.class)

- 表示预期在执行该测试方法时抛出指定类型的异常。如果没有抛出异常或抛出不同类型的异常,测试将失败。9. @Test(timeout = 1000)

- 表示测试方法应该在指定的毫秒数内完成。如果超出指定时间,测试将失败。3. 测试类监听器

在JUnit 4中,RunListener 是一个接口,它允许开发者参与测试运行的生命周期中的各个点。通过实现 RunListener 接口,可以自界说测试执行过程中的举动,例如测试开始、结束、发生错误或失败时的处理等。
以下是 RunListener 中一些关键的方法:

[*]testRunStarted: 当整个测试运行开始时调用。
[*]testRunFinished: 当整个测试运行结束时调用。
[*]testStarted: 当单个测试方法开始执行时调用。
[*]testFinished: 当单个测试方法执行结束时调用。
[*]testFailure: 当测试失败时调用。
[*]testAssumptionFailure: 当测试由于一个假设(assumption)失败而不是失败(failure)时调用。
[*]testIgnored: 当测试被忽略时调用。
要使用 RunListener,你需要实现该接口,并重写你感爱好的方法。然后,你需要告诉 JUnit 你的 RunListener 实现应该被使用。这可以通过几种方式完成:

[*]通过步伐化注册:在你的测试代码中,使用 Request.runListeners 方法注册 RunListener。
[*]通过 JUnit 的 @Rule 机制:创建一个实现 TestRule 接口的类,该类在其 apply 方法中注册 RunListener。
[*]作为 JUnit 的启动参数:在命令行或 IDE 配置中指定你的 RunListener 实现。
下面是一个简朴的 RunListener 实现示例,它在控制台输出每个测试的开始和结束:
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.RunListener;

public class SimpleRunListener extends RunListener {
    @Override
    public void testStarted(Description description) {
      System.out.println("Test started: " + description.getMethodName());
    }

    @Override
    public void testFinished(Description description) {
      System.out.println("Test finished: " + description.getMethodName());
    }
}要在测试套件中使用这个监听器,你可以在测试类上使用 @Rule 注解一个字段:
import org.junit.Rule;
import org.junit.Test;

public class MyTest {
    @Rule
    public SimpleRunListener simpleRunListener = new SimpleRunListener();

    @Test
    public void test1() {
      // Your test code here
    }

    @Test
    public void test2() {
      // Your test code here
    }
}这样,每当测试运行时,SimpleRunListener 都会输出测试的开始和结束信息。
4. 有序执行测试案例

编写 JUnit 有序测试案例被认为是不良做法。 但是,如果仍然遇到测试用例排序是唯一出路的环境,则可以使用MethodSorters类
在JUnit 4中,@FixMethodOrder 是一个注解,它确保测试方法按照指定的次序执行。默认环境下,JUnit 可能会随机化测试方法的执行次序,以制止依赖测试执行次序的问题。但是,有些环境下,你可能需要确保测试按照特定的次序运行,比如当测试方法之间存在依赖关系时。
要使用 @FixMethodOrder,首先需要导入相应的包:
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;然后,你可以在测试类上使用 @FixMethodOrder 注解,并指定排序策略。JUnit 4 提供了 MethodSorters 工具类,此中包含了几种预界说的排序方式:

[*]MethodSorters.NAME_ASCENDING:按照方法名称的字典次序升序排列。
[*]MethodSorters.NAME_DESCENDING:按照方法名称的字典次序降序排列。
[*]等等。
下面是一个使用 @FixMethodOrder 的例子:
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MyTestClass {

    @Test
    public void test1() {
      // 测试逻辑
    }

    @Test
    public void test2() {
      // 测试逻辑
    }

    @Test
    public void test3() {
      // 测试逻辑
    }
}在这个例子中,即使测试方法的名称可能不会决定它们的执行次序,@FixMethodOrder 注解确保了 test1、test2 和 test3 将按照它们在类中出现的次序执行。
请注意,依赖测试执行次序的做法通常不是一个好习惯,由于它可能导致测试变得脆弱和难以维护。在大多数环境下,你应该尽量编写独立且不依赖于其他测试的测试用例。
5. 创建暂时文件夹

在JUnit 4中,TemporaryFolder 是一个JUnit的Rule,它提供了创建和删除暂时文件夹和文件的便捷方式,这在需要测试文件和文件夹操作的单元测试中非常有效。TemporaryFolder可以确保测试运行结束后,所有的暂时文件和文件夹都被清理干净,制止测试之间的相互干扰。
以下是如安在JUnit 4测试中使用TemporaryFolder的根本步骤:

[*]首先,确保你的项目中包含了JUnit依赖。如果你使用的是Maven或Gradle,确保你的pom.xml或build.gradle文件中包含了JUnit的依赖项。
[*]导入TemporaryFolder类和JUnit的Rule注解:
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
[*]在测试类中,使用@Rule注解声明一个TemporaryFolder的实例:
public class TemporaryFolderExampleTest {

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();
}
[*]在测试方法中,使用TemporaryFolder的实例来创建暂时文件和文件夹:
import org.junit.Rule;
import org.junit.Test;

import java.io.File;
import java.io.IOException;

public class TemporaryFolderExampleTest {

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    @Test
    public void testUsingTemporaryFolder() throws IOException {
      // 创建一个临时文件夹
      File tempFolder = folder.newFolder();
      System.out.println("Temporary folder created at: " + tempFolder.getAbsolutePath());

      // 在临时文件夹中创建一个文件
      File tempFile = folder.newFile("test.txt");
      System.out.println("Temporary file created at: " + tempFile.getAbsolutePath());

      // 执行测试逻辑,例如写入文件等

      // 测试结束后,TemporaryFolder Rule会自动删除这些临时文件和文件夹
    }
}在上述示例中,TemporaryFolder会在测试方法执行前创建一个新的暂时文件夹和文件。测试完成后,不管测试是否通过,TemporaryFolder都会删除这些暂时文件和文件夹,确保测试环境的整齐。
使用TemporaryFolder可以资助你编写更干净、更可重复的测试,特别是在测试需要文件体系操作的代码时。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Junit 4 测试方法