ทำไม fixtureSetup ของ jUnit ต้องเป็นแบบคงที่?


109

ฉันทำเครื่องหมายวิธีการด้วยคำอธิบายประกอบ @BeforeClass ของ jUnit และได้รับข้อยกเว้นนี้ว่าต้องเป็นแบบคงที่ อะไรคือเหตุผล? สิ่งนี้บังคับให้การเริ่มต้นทั้งหมดของฉันอยู่ในฟิลด์แบบคงที่โดยไม่มีเหตุผลที่ดีเท่าที่ฉันเห็น

ใน. Net (NUnit) ไม่เป็นเช่นนั้น

แก้ไข - ข้อเท็จจริงที่ว่าเมธอดที่ใส่คำอธิบายประกอบด้วย @BeforeClass ทำงานเพียงครั้งเดียวไม่ได้เกี่ยวข้องกับวิธีการแบบคงที่ - สามารถมีวิธีการที่ไม่คงที่รันเพียงครั้งเดียว (เช่นเดียวกับใน NUnit)

คำตอบ:


122

JUnit จะสร้างหนึ่งอินสแตนซ์ของคลาสทดสอบสำหรับแต่ละวิธี @Test เสมอ นี่เป็นการตัดสินใจในการออกแบบขั้นพื้นฐานเพื่อให้ง่ายต่อการเขียนแบบทดสอบโดยไม่มีผลข้างเคียง การทดสอบที่ดีจะไม่มีการขึ้นต่อกันของคำสั่งซื้อ (ดูที่FIRST ) และการสร้างอินสแตนซ์ใหม่ของคลาสทดสอบและตัวแปรอินสแตนซ์สำหรับการทดสอบแต่ละครั้งเป็นสิ่งสำคัญในการบรรลุเป้าหมายนี้ กรอบการทดสอบบางส่วนใช้อินสแตนซ์คลาสทดสอบเดียวกันซ้ำสำหรับการทดสอบทั้งหมดซึ่งนำไปสู่ความเป็นไปได้มากขึ้นในการสร้างผลข้างเคียงระหว่างการทดสอบโดยไม่ได้ตั้งใจ

และเนื่องจากแต่ละวิธีการทดสอบมีอินสแตนซ์ของตัวเองจึงไม่มีเหตุผลที่วิธีการ @ BeforeClass / @ AfterClass จะเป็นวิธีการอินสแตนซ์ มิฉะนั้นจะเรียกใช้เมธอดของคลาสทดสอบใด หากเป็นไปได้ที่วิธีการ @ BeforeClass / @ AfterClass จะอ้างอิงตัวแปรอินสแตนซ์จะมีเพียงหนึ่งในวิธีการ @Test เท่านั้นที่สามารถเข้าถึงตัวแปรอินสแตนซ์เดียวกันเหล่านั้นส่วนที่เหลือจะมีตัวแปรอินสแตนซ์ที่ค่าดีฟอลต์ - และ @ วิธีทดสอบจะถูกสุ่มเลือกเนื่องจากลำดับของเมธอดในไฟล์. class นั้นไม่ได้ระบุ / ขึ้นอยู่กับคอมไพเลอร์ (IIRC, API การสะท้อนของ Java จะส่งคืนเมธอดในลำดับเดียวกับที่ประกาศไว้ในไฟล์. class แม้ว่าจะมีพฤติกรรมนั้นด้วยก็ตาม ไม่ระบุ - ฉันเขียนห้องสมุดแล้วสำหรับการเรียงลำดับตามหมายเลขบรรทัด)

ดังนั้นการบังคับใช้วิธีการเหล่านั้นเป็นแบบคงที่จึงเป็นทางออกเดียวที่สมเหตุสมผล

นี่คือตัวอย่าง:

public class ExampleTest {

    @BeforeClass
    public static void beforeClass() {
        System.out.println("beforeClass");
    }

    @AfterClass
    public static void afterClass() {
        System.out.println("afterClass");
    }

    @Before
    public void before() {
        System.out.println(this + "\tbefore");
    }

    @After
    public void after() {
        System.out.println(this + "\tafter");
    }

    @Test
    public void test1() {
        System.out.println(this + "\ttest1");
    }

    @Test
    public void test2() {
        System.out.println(this + "\ttest2");
    }

    @Test
    public void test3() {
        System.out.println(this + "\ttest3");
    }
}

ซึ่งพิมพ์:

beforeClass
ExampleTest@3358fd70    before
ExampleTest@3358fd70    test1
ExampleTest@3358fd70    after
ExampleTest@6293068a    before
ExampleTest@6293068a    test2
ExampleTest@6293068a    after
ExampleTest@22928095    before
ExampleTest@22928095    test3
ExampleTest@22928095    after
afterClass

อย่างที่คุณเห็นการทดสอบแต่ละรายการดำเนินการด้วยอินสแตนซ์ของตัวเอง สิ่งที่ JUnit ทำโดยพื้นฐานแล้วจะเหมือนกับสิ่งนี้:

ExampleTest.beforeClass();

ExampleTest t1 = new ExampleTest();
t1.before();
t1.test1();
t1.after();

ExampleTest t2 = new ExampleTest();
t2.before();
t2.test2();
t2.after();

ExampleTest t3 = new ExampleTest();
t3.before();
t3.test3();
t3.after();

ExampleTest.afterClass();

1
"มิฉะนั้นจะเรียกใช้วิธีใดในอินสแตนซ์ของคลาสทดสอบ" - ในอินสแตนซ์การทดสอบที่การทดสอบ JUnit ทำงานสร้างขึ้นเพื่อดำเนินการทดสอบ
HDave

1
ในตัวอย่างนั้นได้สร้างอินสแตนซ์ทดสอบสามชุด ไม่มีเช่นการทดสอบ
Esko Luontola

ใช่ - ฉันพลาดสิ่งนั้นในตัวอย่างของคุณ ฉันคิดมากขึ้นเกี่ยวกับเวลาที่เรียกใช้ JUnit จากการทดสอบที่เรียกใช้ ala Eclipse หรือ Spring Test หรือ Maven ในกรณีดังกล่าวจะมีการสร้างคลาสทดสอบหนึ่งอินสแตนซ์
HDave

ไม่ได้ JUnit จะสร้างอินสแตนซ์ของคลาสทดสอบจำนวนมากเสมอไม่ว่าเราจะใช้การทดสอบแบบใดก็ตาม ก็ต่อเมื่อคุณมี Runner ที่กำหนดเองสำหรับคลาสทดสอบซึ่งอาจมีบางอย่างที่แตกต่างกัน
Esko Luontola

ในขณะที่ฉันเข้าใจการตัดสินใจในการออกแบบ แต่คิดว่าไม่ได้คำนึงถึงความต้องการทางธุรกิจของผู้ใช้ ดังนั้นในตอนท้ายการตัดสินใจออกแบบภายใน (ซึ่งฉันไม่ควรสนใจมากเท่ากับผู้ใช้ทันทีที่ lib ทำงานได้ดี) บังคับให้ฉันออกแบบตัวเลือกในการทดสอบซึ่งเป็นการปฏิบัติที่ไม่ดีจริงๆ มันไม่คล่องตัวเลยจริงๆ: D
gicappa

43

คำตอบสั้น ๆ คือ: ไม่มีเหตุผลที่ดีที่จะหยุดนิ่ง

ในความเป็นจริงการทำให้เป็นแบบคงที่ทำให้เกิดปัญหาทุกประเภทหากคุณใช้ Junit เพื่อดำเนินการทดสอบการรวม DAO ที่ใช้ DBUnit ข้อกำหนดแบบคงที่รบกวนการแทรกการพึ่งพาการเข้าถึงบริบทของแอปพลิเคชันการจัดการทรัพยากรการบันทึกและทุกสิ่งที่ขึ้นอยู่กับ "getClass"


4
ฉันเขียน superclass กรณีทดสอบของตัวเองและใช้คำอธิบายประกอบ Spring @PostConstructสำหรับการตั้งค่าและ@AfterClassสำหรับการฉีกขาดและฉันไม่สนใจสิ่งที่คงที่จาก Junit โดยสิ้นเชิง สำหรับการทดสอบ DAO ฉันเขียนTestCaseDataLoaderคลาสของตัวเองซึ่งฉันเรียกใช้จากวิธีการเหล่านี้
HDave

9
นั่นเป็นคำตอบที่แย่มากเห็นได้ชัดว่ามีเหตุผลที่ทำให้มันคงที่เนื่องจากคำตอบที่ยอมรับระบุไว้อย่างชัดเจน คุณอาจไม่เห็นด้วยกับการตัดสินใจในการออกแบบ แต่ก็ยังไม่ได้หมายความว่าการตัดสินใจนั้น "ไม่มีเหตุผลที่ดี"
Adam Parkin

8
แน่นอนว่าผู้เขียน JUnit มีเหตุผลฉันว่ามันไม่ใช่เหตุผลที่ดี ... ดังนั้นที่มาของ OP (และคนอื่น ๆ อีก 44 คน) จึงรู้สึกประหลาดใจ มันเป็นเรื่องเล็กน้อยที่จะใช้วิธีการอินสแตนซ์และให้นักวิ่งทดสอบใช้การประชุมเพื่อเรียกพวกเขา ในท้ายที่สุดนั่นคือสิ่งที่ทุกคนทำเพื่อแก้ไขข้อ จำกัด นี้ไม่ว่าจะหมุนตัววิ่งของคุณเองหรือม้วนชั้นเรียนของคุณเอง
HDave

1
@HDave ผมคิดว่าวิธีการแก้ปัญหาของคุณด้วย@PostConstructและ@AfterClassเพียงประพฤติเช่นเดียวกับและ@Before @Afterในความเป็นจริงเมธอดของคุณจะถูกเรียกใช้สำหรับแต่ละวิธีการทดสอบไม่ใช่ครั้งเดียวสำหรับทั้งคลาส (ตามที่ Esko Luontola ระบุในคำตอบของเขาอินสแตนซ์ของคลาสจะถูกสร้างขึ้นสำหรับวิธีทดสอบแต่ละวิธี) ฉันไม่เห็นยูทิลิตี้ของโซลูชันของคุณดังนั้น (เว้นแต่ฉันจะพลาดบางอย่าง)
magnum87

1
ทำงานได้อย่างถูกต้องเป็นเวลา 5 ปีแล้วดังนั้นฉันคิดว่าโซลูชันของฉันใช้ได้ผล
HDave

14

เอกสาร JUnit ดูเหมือนจะหายาก แต่ฉันจะเดาว่า: บางที JUnit จะสร้างอินสแตนซ์ใหม่ของคลาสทดสอบของคุณก่อนที่จะรันแต่ละกรณีการทดสอบดังนั้นวิธีเดียวที่จะทำให้สถานะ "ฟิกซ์เจอร์" ของคุณคงอยู่ตลอดการรันคือการกำหนดให้เป็นแบบคงที่ซึ่งสามารถ บังคับใช้โดยตรวจสอบให้แน่ใจว่า fixtureSetup ของคุณ (วิธี @BeforeClass) เป็นแบบคงที่


2
ไม่เพียง แต่บางที JUnit ยังสร้างอินสแตนซ์ใหม่ของกรณีทดสอบ นี่คือเหตุผลเดียว
guerda

นี่เป็นเหตุผลเดียวที่พวกเขามี แต่ในความเป็นจริง Junit runner สามารถทำงานในการเรียกใช้วิธี BeforeTests และ AfterTests ได้เหมือนที่ testng ทำ
HDave

TestNG สร้างอินสแตนซ์ของคลาสทดสอบหนึ่งชุดและแชร์กับแบบทดสอบทั้งหมดในคลาสหรือไม่ ทำให้เสี่ยงต่อผลข้างเคียงระหว่างการทดสอบมากขึ้น
Esko Luontola

3

แม้ว่าจะไม่ตอบคำถามเดิม มันจะตอบโจทย์การติดตามที่ชัดเจน วิธีสร้างกฎที่ใช้ได้ผลก่อนและหลังชั้นเรียนและก่อนและหลังการทดสอบ

เพื่อให้บรรลุคุณสามารถใช้รูปแบบนี้:

@ClassRule
public static JPAConnection jpaConnection = JPAConnection.forUITest("my-persistence-unit");

@Rule
public JPAConnection.EntityManager entityManager = jpaConnection.getEntityManager();

ในก่อน (คลาส) JPAConnection จะสร้างการเชื่อมต่อหนึ่งครั้งหลังจาก (คลาส) จะปิดการเชื่อมต่อ

getEntityMangerส่งคืนคลาสภายในJPAConnectionที่ใช้ EntityManager ของ jpa และสามารถเข้าถึงการเชื่อมต่อภายในไฟล์jpaConnection. ก่อน (ทดสอบ) จะเริ่มทำธุรกรรมในภายหลัง (ทดสอบ) จะย้อนกลับอีกครั้ง

สิ่งนี้ไม่ปลอดภัยต่อเธรด แต่สามารถทำให้เป็นเช่นนั้นได้

รหัสที่เลือกของ JPAConnection.class

package com.triodos.general.junit;

import com.triodos.log.Logger;
import org.jetbrains.annotations.NotNull;
import org.junit.rules.ExternalResource;

import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.Metamodel;
import java.util.HashMap;
import java.util.Map;

import static com.google.common.base.Preconditions.checkState;
import static com.triodos.dbconn.DB2DriverManager.DRIVERNAME_TYPE4;
import static com.triodos.dbconn.UnitTestProperties.getDatabaseConnectionProperties;
import static com.triodos.dbconn.UnitTestProperties.getPassword;
import static com.triodos.dbconn.UnitTestProperties.getUsername;
import static java.lang.String.valueOf;
import static java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;

public final class JPAConnectionExample extends ExternalResource {

  private static final Logger LOG = Logger.getLogger(JPAConnectionExample.class);

  @NotNull
  public static JPAConnectionExample forUITest(String persistenceUnitName) {
    return new JPAConnectionExample(persistenceUnitName)
        .setManualEntityManager();
  }

  private final String persistenceUnitName;
  private EntityManagerFactory entityManagerFactory;
  private javax.persistence.EntityManager jpaEntityManager = null;
  private EntityManager entityManager;

  private JPAConnectionExample(String persistenceUnitName) {
    this.persistenceUnitName = persistenceUnitName;
  }

  @NotNull
  private JPAConnectionExample setEntityManager(EntityManager entityManager) {
    this.entityManager = entityManager;
    return this;
  }

  @NotNull
  private JPAConnectionExample setManualEntityManager() {
    return setEntityManager(new RollBackAfterTestEntityManager());
  }


  @Override
  protected void before() {
    entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName, createEntityManagerProperties());
    jpaEntityManager = entityManagerFactory.createEntityManager();
  }

  @Override
  protected void after() {

    if (jpaEntityManager.getTransaction().isActive()) {
      jpaEntityManager.getTransaction().rollback();
    }

    if(jpaEntityManager.isOpen()) {
      jpaEntityManager.close();
    }
    // Free for garbage collection as an instance
    // of EntityManager may be assigned to a static variable
    jpaEntityManager = null;

    entityManagerFactory.close();
    // Free for garbage collection as an instance
    // of JPAConnection may be assigned to a static variable
    entityManagerFactory = null;
  }

  private Map<String,String> createEntityManagerProperties(){
    Map<String, String> properties = new HashMap<>();
    properties.put("javax.persistence.jdbc.url", getDatabaseConnectionProperties().getURL());
    properties.put("javax.persistence.jtaDataSource", null);
    properties.put("hibernate.connection.isolation", valueOf(TRANSACTION_READ_UNCOMMITTED));
    properties.put("hibernate.connection.username", getUsername());
    properties.put("hibernate.connection.password", getPassword());
    properties.put("hibernate.connection.driver_class", DRIVERNAME_TYPE4);
    properties.put("org.hibernate.readOnly", valueOf(true));

    return properties;
  }

  @NotNull
  public EntityManager getEntityManager(){
    checkState(entityManager != null);
    return entityManager;
  }


  private final class RollBackAfterTestEntityManager extends EntityManager {

    @Override
    protected void before() throws Throwable {
      super.before();
      jpaEntityManager.getTransaction().begin();
    }

    @Override
    protected void after() {
      super.after();

      if (jpaEntityManager.getTransaction().isActive()) {
        jpaEntityManager.getTransaction().rollback();
      }
    }
  }

  public abstract class EntityManager extends ExternalResource implements javax.persistence.EntityManager {

    @Override
    protected void before() throws Throwable {
      checkState(jpaEntityManager != null, "JPAConnection was not initialized. Is it a @ClassRule? Did the test runner invoke the rule?");

      // Safety-close, if failed to close in setup
      if (jpaEntityManager.getTransaction().isActive()) {
        jpaEntityManager.getTransaction().rollback();
        LOG.error("EntityManager encountered an open transaction at the start of a test. Transaction has been closed but should have been closed in the setup method");
      }
    }

    @Override
    protected void after() {
      checkState(jpaEntityManager != null, "JPAConnection was not initialized. Is it a @ClassRule? Did the test runner invoke the rule?");
    }

    @Override
    public final void persist(Object entity) {
      jpaEntityManager.persist(entity);
    }

    @Override
    public final <T> T merge(T entity) {
      return jpaEntityManager.merge(entity);
    }

    @Override
    public final void remove(Object entity) {
      jpaEntityManager.remove(entity);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey) {
      return jpaEntityManager.find(entityClass, primaryKey);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) {
      return jpaEntityManager.find(entityClass, primaryKey, properties);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) {
      return jpaEntityManager.find(entityClass, primaryKey, lockMode);
    }

    @Override
    public final <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) {
      return jpaEntityManager.find(entityClass, primaryKey, lockMode, properties);
    }

    @Override
    public final <T> T getReference(Class<T> entityClass, Object primaryKey) {
      return jpaEntityManager.getReference(entityClass, primaryKey);
    }

    @Override
    public final void flush() {
      jpaEntityManager.flush();
    }

    @Override
    public final void setFlushMode(FlushModeType flushMode) {
      jpaEntityManager.setFlushMode(flushMode);
    }

    @Override
    public final FlushModeType getFlushMode() {
      return jpaEntityManager.getFlushMode();
    }

    @Override
    public final void lock(Object entity, LockModeType lockMode) {
      jpaEntityManager.lock(entity, lockMode);
    }

    @Override
    public final void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) {
      jpaEntityManager.lock(entity, lockMode, properties);
    }

    @Override
    public final void refresh(Object entity) {
      jpaEntityManager.refresh(entity);
    }

    @Override
    public final void refresh(Object entity, Map<String, Object> properties) {
      jpaEntityManager.refresh(entity, properties);
    }

    @Override
    public final void refresh(Object entity, LockModeType lockMode) {
      jpaEntityManager.refresh(entity, lockMode);
    }

    @Override
    public final void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) {
      jpaEntityManager.refresh(entity, lockMode, properties);
    }

    @Override
    public final void clear() {
      jpaEntityManager.clear();
    }

    @Override
    public final void detach(Object entity) {
      jpaEntityManager.detach(entity);
    }

    @Override
    public final boolean contains(Object entity) {
      return jpaEntityManager.contains(entity);
    }

    @Override
    public final LockModeType getLockMode(Object entity) {
      return jpaEntityManager.getLockMode(entity);
    }

    @Override
    public final void setProperty(String propertyName, Object value) {
      jpaEntityManager.setProperty(propertyName, value);
    }

    @Override
    public final Map<String, Object> getProperties() {
      return jpaEntityManager.getProperties();
    }

    @Override
    public final Query createQuery(String qlString) {
      return jpaEntityManager.createQuery(qlString);
    }

    @Override
    public final <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) {
      return jpaEntityManager.createQuery(criteriaQuery);
    }

    @Override
    public final <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass) {
      return jpaEntityManager.createQuery(qlString, resultClass);
    }

    @Override
    public final Query createNamedQuery(String name) {
      return jpaEntityManager.createNamedQuery(name);
    }

    @Override
    public final <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass) {
      return jpaEntityManager.createNamedQuery(name, resultClass);
    }

    @Override
    public final Query createNativeQuery(String sqlString) {
      return jpaEntityManager.createNativeQuery(sqlString);
    }

    @Override
    public final Query createNativeQuery(String sqlString, Class resultClass) {
      return jpaEntityManager.createNativeQuery(sqlString, resultClass);
    }

    @Override
    public final Query createNativeQuery(String sqlString, String resultSetMapping) {
      return jpaEntityManager.createNativeQuery(sqlString, resultSetMapping);
    }

    @Override
    public final void joinTransaction() {
      jpaEntityManager.joinTransaction();
    }

    @Override
    public final <T> T unwrap(Class<T> cls) {
      return jpaEntityManager.unwrap(cls);
    }

    @Override
    public final Object getDelegate() {
      return jpaEntityManager.getDelegate();
    }

    @Override
    public final void close() {
      jpaEntityManager.close();
    }

    @Override
    public final boolean isOpen() {
      return jpaEntityManager.isOpen();
    }

    @Override
    public final EntityTransaction getTransaction() {
      return jpaEntityManager.getTransaction();
    }

    @Override
    public final EntityManagerFactory getEntityManagerFactory() {
      return jpaEntityManager.getEntityManagerFactory();
    }

    @Override
    public final CriteriaBuilder getCriteriaBuilder() {
      return jpaEntityManager.getCriteriaBuilder();
    }

    @Override
    public final Metamodel getMetamodel() {
      return jpaEntityManager.getMetamodel();
    }
  }
}

2

ดูเหมือนว่า JUnit จะสร้างอินสแตนซ์ใหม่ของคลาสทดสอบสำหรับวิธีทดสอบแต่ละวิธี ลองใช้รหัสนี้

public class TestJunit
{

    int count = 0;

    @Test
    public void testInc1(){
        System.out.println(count++);
    }

    @Test
    public void testInc2(){
        System.out.println(count++);
    }

    @Test
    public void testInc3(){
        System.out.println(count++);
    }
}

เอาต์พุตคือ 0 0 0

ซึ่งหมายความว่าหากวิธีการ @BeforeClass ไม่คงที่จะต้องดำเนินการก่อนวิธีการทดสอบแต่ละวิธีและจะไม่มีทางแยกความแตกต่างระหว่างความหมายของ @Before และ @BeforeClass


มันไม่ได้ดูเหมือนเป็นอย่างนั้น แต่เป็นอย่างนั้น คำถามถูกถามมาหลายปีแล้วนี่คือคำตอบ: martinfowler.com/bliki/JunitNewInstance.html
Paul

1

คำอธิบายประกอบมีสองประเภท:

  • @BeforeClass (@AfterClass) เรียกหนึ่งครั้งต่อคลาสทดสอบ
  • @Before (และ @After) เรียกก่อนการทดสอบแต่ละครั้ง

ดังนั้น @BeforeClass จึงต้องประกาศแบบคงที่เนื่องจากมีการเรียกใช้ครั้งเดียว นอกจากนี้คุณควรพิจารณาด้วยว่าการเป็นแบบคงที่เป็นวิธีเดียวที่จะทำให้แน่ใจว่ามีการเผยแพร่ "สถานะ" ที่เหมาะสมระหว่างการทดสอบ (โมเดล JUnit กำหนดหนึ่งอินสแตนซ์การทดสอบต่อ @Test) และเนื่องจากใน Java มีเพียงวิธีการแบบคงที่เท่านั้นที่สามารถเข้าถึงข้อมูลแบบคงที่ ... @BeforeClass และ @ AfterClass สามารถใช้ได้กับวิธีการแบบคงที่เท่านั้น

ตัวอย่างการทดสอบนี้ควรชี้แจงการใช้งาน @BeforeClass vs @Before:

public class OrderTest {

    @BeforeClass
    public static void beforeClass() {
        System.out.println("before class");
    }

    @AfterClass
    public static void afterClass() {
        System.out.println("after class");
    }

    @Before
    public void before() {
        System.out.println("before");
    }

    @After
    public void after() {
        System.out.println("after");
    }    

    @Test
    public void test1() {
        System.out.println("test 1");
    }

    @Test
    public void test2() {
        System.out.println("test 2");
    }
}

เอาต์พุต:

------------- เอาต์พุตมาตรฐาน ---------------
ก่อนเข้าเรียน
ก่อน
ทดสอบ 1
หลังจาก
ก่อน
ทดสอบ 2
หลังจาก
หลังเลิกเรียน
------------- ---------------- ---------------

19
ฉันพบว่าคำตอบของคุณไม่เกี่ยวข้อง ฉันรู้ความหมายของ BeforeClass และ Before นี่ไม่ได้อธิบายว่าทำไมต้องเป็นแบบคงที่ ...
ripper234

1
"สิ่งนี้บังคับให้การเริ่มต้นของฉันทั้งหมดอยู่ในสมาชิกคงที่โดยไม่มีเหตุผลที่ดีเท่าที่ฉันเห็น" คำตอบของฉันควรแสดงให้คุณเห็นว่าการเริ่มต้นของคุณสามารถไม่คงที่โดยใช้ @Before แทน @BeforeClass
dfa

2
ฉันต้องการทำการเริ่มต้นเพียงครั้งเดียวในช่วงเริ่มต้นของคลาส แต่สำหรับตัวแปรที่ไม่คงที่
ripper234

คุณไม่สามารถใช้ JUnit ได้ขออภัย คุณต้องใช้ตัวแปรคงไม่มีทาง
dfa

1
หากการเริ่มต้นมีราคาแพงคุณสามารถเก็บตัวแปรสถานะไว้เพื่อบันทึกว่าคุณได้ทำการ init หรือไม่และ (ตรวจสอบและเป็นทางเลือก) ดำเนินการ init ในเมธอด @Before ...
แบลร์คอนราด

0

ตาม JUnit 5 ดูเหมือนว่าปรัชญาในการสร้างอินสแตนซ์ใหม่อย่างเคร่งครัดต่อวิธีการทดสอบจะคลายลงไปบ้าง พวกเขาได้เพิ่มคำอธิบายประกอบที่จะสร้างอินสแตนซ์คลาสทดสอบเพียงครั้งเดียว ดังนั้นคำอธิบายประกอบนี้ยังอนุญาตให้เมธอดที่ใส่คำอธิบายประกอบด้วย @ BeforeAll / @ AfterAll (การแทนที่เป็น @ BeforeClass / @ AfterClass) เป็นแบบไม่คงที่ ดังนั้นคลาสทดสอบเช่นนี้:

@TestInstance(Lifecycle.PER_CLASS)
class TestClass() {
    Object object;

    @BeforeAll
    void beforeAll() {
        object = new Object();
    }

    @Test
    void testOne() {
        System.out.println(object);
    }

    @Test
    void testTwo() {
        System.out.println(object);
    }
}

จะพิมพ์:

java.lang.Object@799d4f69
java.lang.Object@799d4f69

ดังนั้นคุณสามารถสร้างอินสแตนซ์อ็อบเจ็กต์ได้หนึ่งครั้งต่อคลาสทดสอบ แน่นอนว่าสิ่งนี้ทำให้คุณมีความรับผิดชอบในการหลีกเลี่ยงการกลายพันธุ์ของวัตถุที่สร้างอินสแตนซ์ด้วยวิธีนี้


-11

ในการแก้ไขปัญหานี้เพียงแค่เปลี่ยนวิธีการ

public void setUpBeforeClass 

ถึง

public static void setUpBeforeClass()

staticและสิ่งที่ถูกกำหนดไว้ในวิธีการนี้เพื่อ


2
นี่ไม่ตอบคำถามเลย
rgargente
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.