ChatGPT解决这个技术问题 Extra ChatGPT

Junit - 运行一次设置方法

我设置了一个包含几个测试的类,而不是使用 @Before,我希望有一个在所有测试之前只执行一次的 setup 方法。 Junit 4.8有可能吗?


C
Community

尽管我同意@assylias 的观点,即使用 @BeforeClass 是一种经典的解决方案,但它并不总是很方便。用 @BeforeClass 注释的方法必须是静态的。对于一些需要测试用例实例的测试来说非常不方便。例如,基于 Spring 的测试使用 @Autowired 来处理在 spring 上下文中定义的服务。

在这种情况下,我个人使用带有 @Before 注释的常规 setUp() 方法并管理我的自定义 static(!) boolean 标志:

private static boolean setUpIsDone = false;
.....
@Before
public void setUp() {
    if (setUpIsDone) {
        return;
    }
    // do the setup
    setUpIsDone = true;
}

添加到 Kenny Cason 关于为什么它必须是静态的评论。它必须是静态的,因为 JUnit 会为每个 @Test 方法实例化一个新的测试类实例。如果实例变量不是静态的,则每个实例的实例变量都将重置为其默认值 (false)。查看更多信息:martinfowler.com/bliki/JunitNewInstance.html
除了 setUp() 方法位于超类中的情况外,此方法有效 - 在下方发布了 answer 以尝试解决此问题。
我犹豫要不要对有 84k 代表的人这么说,但 BeforeClass 实际上并没有回答这个问题:BeforeClass 是在每个测试课程开始时运行的。但是 OP 要求一个“在所有测试之前只运行一次”的。您提出的解决方案可以做到这一点,但您必须让所有测试类扩展“CommonTest”类......
@mikerodent,恕我直言,OP询问了他的测试用例中的所有测试,而不是所有测试。因此,您的评论不太相关。顺便说一句,即使他的声誉很高,也不要担心对任何人说什么。至少这是我所做的:)。 2012 年 8 月,当我回答这个问题时,我的声誉明显下降。
不适用于我的情况,在设置中初始化的变量在每次测试后都会重置,所以只初始化一次是没有意义的。
a
assylias

您可以使用 the BeforeClass annotation

@BeforeClass
public static void setUpClass() {
    //executed only once, before the first test
}

我不能使用它,我有几个基于非静态组件的设置方法,例如 getClass()
@Bober02 BeforeClass 确实需要是静态的。如果您不能使用它,则另一个答案提供了一种解决方法。
确定不能使用 TheClassYouWant.class 代替 getClass() 调用吗?这是实际的 Java:String.class.getName()
@mikerodent我把这个问题理解为“课堂上的所有测试”——但你是对的,它可能不是OP想要的。
B
Brian

JUnit 5 现在有一个 @BeforeAll 注释:

表示注解的方法应该在当前类或类层次结构中的所有@Test 方法之前执行;类似于 JUnit 4 的 @BeforeClass。此类方法必须是静态的。

JUnit 5 的生命周期注释似乎终于搞定了!您甚至无需查看即可猜出可用的注释(例如@BeforeEach @AfterAll)


它有同样的问题是@BeforeClass,它需要是static。 IMO @AlexR 的解决方案更好。
@zengr 倾向于同意你的观点:正如我对 AlexR 所说,如果它只运行一次,他的解决方案要求所有测试类从 CommonTest 类继承。但这很简单,恕我直言,当该语言提供简单的机制时,您可能不应该使用“花哨的”框架提供的解决方案。当然,除非有充分的理由。此外,使用像他这样的简单的东西,具有良好的“做它在锡上所说的”类型名称,有助于提高可读性。
话虽如此,恕我直言,拥有“AfterAll”注释似乎更有理由:设计一种检测所有测试何时完成的机制将非常困难和人为。相反,当然,纯粹主义者可能会说您永远不必进行“最终清理”,即每次“拆卸”都应使所有资源处于原始状态……他们可能是对的!
这是否适用于有多个模块的 Maven,每个模块都有自己的测试?
@mike rodent,就我而言,在每次测试之前/之后在文件系统中设置和拆除测试文件似乎会导致文件死锁。目前,我已经独立了解了 AlexR 设置一次的解决方案。我有两个静态标志,已经设置和脏。如果最初检测到脏状态,或者设置失败导致脏状态,则 setup() 调用 cleanup()。为了在运行测试后进行清理,我再次运行它们。凌乱,根本不理想,不在我们的构建过程中。仍在寻找更好的方法(jUnit 4.12)。
S
Steve Chambers

setUp() 在测试类的超类中时(例如下面的 AbstractTestBase),接受的答案可以修改如下:

public abstract class AbstractTestBase {
    private static Class<? extends AbstractTestBase> testClass;
    .....
    public void setUp() {
        if (this.getClass().equals(testClass)) {
            return;
        }

        // do the setup - once per concrete test class
        .....
        testClass = this.getClass();
    }
}

这应该适用于单个非静态 setUp() 方法,但我无法为 tearDown() 生成等价物而不会误入复杂反射的世界......赏金指向任何可以的人!


佚名

编辑:我刚刚在调试时发现该类也在每次测试之前实例化。我猜@BeforeClass 注释在这里是最好的。

你也可以在构造函数上设置,测试类毕竟是一个类。我不确定这是否是一种不好的做法,因为几乎所有其他方法都已注释,但它确实有效。你可以像这样创建一个构造函数:

public UT () {
    // initialize once here
}
@Test
// Some test here...

ctor 将在测试之前被调用,因为它们不是静态的。


A
Abhishek Chatterjee

使用 Spring 的 @PostConstruct 方法完成所有初始化工作,并且此方法在任何 @Test 执行之前运行


D
Deepak

JUnit 5 @BeforeAll 可以是非静态的,只要测试类的生命周期是每个类的,即用 @TestInstance(Lifecycle.PER_CLASS) 注释测试类,你就可以开始了


r
radistao

试试这个解决方案:https://stackoverflow.com/a/46274919/907576

使用 @BeforeAllMethods/@AfterAllMethods 注释,您可以在实例上下文中执行 Test 类中的任何方法,其中所有注入的值都可用。


依赖第三方库。
c
codeporn

我的肮脏解决方案是:

public class TestCaseExtended extends TestCase {

    private boolean isInitialized = false;
    private int serId;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        if(!isInitialized) {
            loadSaveNewSerId();
            emptyTestResultsDirectory();
            isInitialized = true;
        }
    }

   ...

}

我用它作为我所有测试用例的基础。


公共类 TestCaseExtended 扩展 TestCase { private static boolean isInitialized = false;私有静态TestCaseExtended caseExtended;私人 int serId; @Override public void setUp() 抛出异常 { super.setUp(); if (!isInitialized) { caseExtended = new TestCaseExtended(); caseExtended.loadSaveNewSerId(); caseExtended.emptyTestResultsDirectory(); isInitialized = true; } }
m
mjs

如果您不想强制声明在每个子测试上设置和检查的变量,则将其添加到 SuperTest 可以执行以下操作:

public abstract class SuperTest {

    private static final ConcurrentHashMap<Class, Boolean> INITIALIZED = new ConcurrentHashMap<>();
    protected final boolean initialized() {
        final boolean[] absent = {false};
        INITIALIZED.computeIfAbsent(this.getClass(), (klass)-> {
            return absent[0] = true;
        });
        return !absent[0];
    }
}



public class SubTest extends SuperTest {
    @Before
    public void before() {
        if ( super.initialized() ) return;

         ... magic ... 
    }

}

S
Sergii

我这样解决了这个问题:

添加到您的 Base 抽象类(我的意思是您在 setUpDriver() 方法中初始化驱动程序的抽象类)这部分代码:

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

现在,如果您的测试类将从 Base 抽象类扩展 -> setUpDriver() 方法将在第一次 @Test 之前执行,每次运行仅一次。


I
Igor Čordaš

这是另一种建议:

我要做的是创建一个名为 _warmup 的方法,或者只是 _ 使用 @FixMethodOrder(MethodSorters.NAME_ASCENDING) 注释测试类

这仅适用于您运行类中的所有测试

它有一个包含额外测试的缺点,它还将运行一个额外的 @Before 和 @After 通常建议您的测试方法是顺序独立的,这违反了该规则,但是为什么有人希望在报告中随机排序测试我不知道所以 NAME_ASCENDING 是我一直使用的

但是这样做的好处是使用最少的代码进行简单的设置,并且不需要扩展类/运行器等......测试运行长度更准确,因为所有设置时间都在方法 _warmup 上报告


u
user123456789

经过一段时间的试验,这是我的解决方案。我需要这个进行春季启动测试。我尝试使用@PostConstruct,不幸的是它为每个测试执行。

public class TestClass {
    private static TestClass testClass = null;
    @Before
    public void setUp() {
        if (testClass == null) {
            // set up once
            ...
            testClass = this;
        }
    }
    @AfterClass
    public static void cleanUpAfterAll() {
        testClass.cleanUpAfterAllTests();
    }
    private void cleanUpAfterAllTests() {
        // final cleanup after all tests
        ...
    }
    @Test
    public void test1() {
        // test 1
        ...
    }
    @Test
    public void test2() {
        // test 2
        ...
    }
}