Làm thế nào để chạy lại các bài kiểm tra JUnit bị lỗi ngay lập tức?


81

Có cách nào để có Quy tắc JUnit hoặc thứ gì đó tương tự cho phép mọi lần thử nghiệm thất bại có cơ hội thứ hai, chỉ bằng cách thử chạy lại một lần nữa.

Thông tin cơ bản: Tôi có một Bộ lớn các bài kiểm tra Selenium2-WebDriver được viết bằng JUnit. Do thời gian rất nhanh (chỉ trong khoảng thời gian chờ ngắn sau khi nhấp chuột), một số thử nghiệm (1 trên 100 và luôn là một thử nghiệm khác) có thể không thành công vì máy chủ đôi khi phản hồi chậm hơn một chút. Nhưng tôi không thể để khoảng thời gian chờ đợi quá lâu mà chắc chắn là đủ lâu, bởi vì sau đó các bài kiểm tra sẽ mất mãi mãi.) - Vì vậy, tôi nghĩ rằng có thể chấp nhận được đối với trường hợp sử dụng này rằng một bài kiểm tra có màu xanh lá cây ngay cả khi nó cần một giây thử.

Tất nhiên sẽ tốt hơn nếu có 2 trong số 3 đa số (lặp lại một bài kiểm tra không đạt 3 lần và coi chúng là đúng, nếu hai trong số các bài kiểm tra đúng), nhưng đây sẽ là một cải tiến trong tương lai.


1
Không cần thiết phải có thời gian chờ sửa chữa trong selenium 2. WebDriver nên phát hiện tải trang và chờ tương ứng. Nếu bạn muốn đợi thứ gì đó khác ngoài việc tải trang, chẳng hạn như một số JavaScript để thực thi, bạn nên sử dụng lớp WebDriverWait, xem: seleniumhq.org/docs/04_webdriver_advanced.html . Điều đó nói rằng, tôi nghĩ rằng có thể thử lại các thử nghiệm GUI, tôi chỉ muốn làm rõ rằng không cần thời gian chờ rõ ràng trong hầu hết các trường hợp.
Tim Büthe

Đó là sự thật, nhưng tôi cũng sẽ chỉ ra rằng tôi đã làm việc trên một số máy chủ thực sự, thực sự kém "ổn", nhưng chúng thực sự có thời gian hoạt động lâu dài trên một số trường hợp trang nhất định và do đó, tôi không muốn thất bại. Đây là một câu hỏi tuyệt vời, cảm ơn. (một cách tự nhiên, tôi muốn rằng thời gian luôn luôn phù hợp, và chúng ta sẽ đẩy cho điều đó, nhưng cho đến khi đó, điều này sẽ phải làm)
CGP

Nếu bạn đang sử dụng tính năng rerun.txt dưa chuột hãy tìm câu trả lời của tôi ở đây
Sugat Mankar

Nếu bạn đang sử dụng tính năng Cucumber rerun.txt, vui lòng xem ans tại đây.
Sugat Mankar

Câu trả lời:


105

Bạn có thể làm điều này với TestRule . Điều này sẽ cung cấp cho bạn sự linh hoạt cần thiết. TestRule cho phép bạn chèn logic xung quanh bài kiểm tra, vì vậy bạn sẽ triển khai vòng lặp thử lại:

public class RetryTest {
    public class Retry implements TestRule {
        private int retryCount;

        public Retry(int retryCount) {
            this.retryCount = retryCount;
        }

        public Statement apply(Statement base, Description description) {
            return statement(base, description);
        }

        private Statement statement(final Statement base, final Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    Throwable caughtThrowable = null;

                    // implement retry logic here
                    for (int i = 0; i < retryCount; i++) {
                        try {
                            base.evaluate();
                            return;
                        } catch (Throwable t) {
                            caughtThrowable = t;
                            System.err.println(description.getDisplayName() + ": run " + (i+1) + " failed");
                        }
                    }
                    System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures");
                    throw caughtThrowable;
                }
            };
        }
    }

    @Rule
    public Retry retry = new Retry(3);

    @Test
    public void test1() {
    }

    @Test
    public void test2() {
        Object o = null;
        o.equals("foo");
    }
}

Trọng tâm của a TestRulebase.evaluate(), gọi phương pháp thử nghiệm của bạn. Vì vậy, xung quanh cuộc gọi này, bạn đặt một vòng lặp thử lại. Nếu một ngoại lệ được đưa ra trong phương pháp thử nghiệm của bạn (lỗi khẳng định thực sự là một AssertionError), thì thử nghiệm đã không thành công và bạn sẽ thử lại.

Có một thứ khác có thể được sử dụng. Bạn có thể chỉ muốn áp dụng logic thử lại này cho một tập hợp các bài kiểm tra, trong trường hợp đó, bạn có thể thêm vào lớp Thử lại bên trên một bài kiểm tra cho một chú thích cụ thể trên phương thức. Descriptionchứa một danh sách các chú thích cho phương pháp. Để biết thêm thông tin về điều này, hãy xem câu trả lời của tôi cho Cách chạy một số mã trước mỗi phương thức JUnit @Test riêng lẻ, mà không sử dụng @RunWith hay AOP? .

Sử dụng TestRunner tùy chỉnh

Đây là gợi ý của CKuck, bạn có thể định nghĩa Runner của riêng mình. Bạn cần mở rộng BlockJUnit4ClassRunner và ghi đè runChild (). Để biết thêm thông tin, hãy xem câu trả lời của tôi về Cách xác định quy tắc phương thức JUnit trong một bộ? . Câu trả lời này trình bày chi tiết cách xác định cách chạy mã cho mọi phương thức trong Suite mà bạn phải xác định Trình chạy của riêng mình.


Cảm ơn: BTW cho mọi người cách sẽ thử điều này, TestRule là một tính năng đã tồn tại kể từ phiên bản JUnit 4.9
Ralph

@Ralph Trên thực tế, TestRule là sự thay thế cho MethodRule đã được giới thiệu trước đó, khoảng 4.7 IIRC, vì vậy giải pháp này có thể áp dụng trước 4.9, nhưng sẽ hơi khác một chút.
Matthew Farwell

7
Điều này thực sự hữu ích, nhưng có điều gì đó khiến tôi giật mình: retryCount và retries có thể là những tên gây hiểu lầm. Khi thử lại là 1, tôi sẽ giả sử rằng anh ta chạy thử nghiệm và nếu nó không thành công, hãy thử lại một lần, nhưng không phải vậy. Biến có thể được gọi là maxTries.
Thomas M.

1
@MatthewFarwell: điều này có khởi động lại hoạt động không? Có cách nào, chúng tôi có thể làm điều đó?
Basim Sherif,

4
Sử dụng phương pháp này có một ràng buộc rằng việc chạy lại thử nghiệm được thực hiện mà không cần tạo lại phiên bản thử nghiệm. Điều đó có nghĩa là bất kỳ trường cá thể nào trong lớp thử nghiệm (hoặc siêu lớp) sẽ không được khởi động lại, có thể rời trạng thái từ các lần chạy trước đó.
Jonah Graham

19

Bây giờ có một lựa chọn tốt hơn. Nếu bạn đang sử dụng các plugin maven như: surfire hoặc failsefe, có một tùy chọn để thêm tham số rerunFailingTestsCount SurFire Api . Công cụ này đã được thực hiện trong vé sau: Vé Jira . Trong trường hợp này, bạn không cần phải viết mã tùy chỉnh của mình và plugin sẽ tự động sửa đổi báo cáo kết quả kiểm tra.
Tôi chỉ thấy một nhược điểm của phương pháp này: Nếu một số bài kiểm tra không thành công trong bài kiểm tra giai đoạn Trước / Sau lớp học sẽ không được chạy lại.


Ví dụ trên dòng lệnh Maven: mvn install -Dsurefire.rerunFailingTestsCount = 2
activout.se

18

Đối với tôi viết giải pháp Á hậu tùy chỉnh linh hoạt hơn. Giải pháp được đăng ở trên (với ví dụ mã) có hai nhược điểm:

  1. Nó sẽ không thử lại kiểm tra nếu nó không thành công trên giai đoạn @BeforeClass;
  2. Nó tính toán các bài kiểm tra chạy hơi khác một chút (khi bạn có 3 lần thử lại, bạn sẽ nhận được lần chạy thử nghiệm: 4, thành công 1 có thể gây nhầm lẫn);

Đó là lý do tại sao tôi thích cách tiếp cận hơn với việc viết custom runner. Và mã của người chạy tùy chỉnh có thể như sau:

import org.junit.Ignore;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runner.notification.StoppedByUserException;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;


public class RetryRunner extends BlockJUnit4ClassRunner {

    private final int retryCount = 100;
    private int failedAttempts = 0;

    public RetryRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }    


    @Override
    public void run(final RunNotifier notifier) {
        EachTestNotifier testNotifier = new EachTestNotifier(notifier,
                getDescription());
        Statement statement = classBlock(notifier);
        try {

            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            testNotifier.fireTestIgnored();
        } catch (StoppedByUserException e) {
            throw e;
        } catch (Throwable e) {
            retry(testNotifier, statement, e);
        }
    }

    @Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);
        if (method.getAnnotation(Ignore.class) != null) {
            notifier.fireTestIgnored(description);
        } else {
            runTestUnit(methodBlock(method), description, notifier);
        }
    }

    /**
     * Runs a {@link Statement} that represents a leaf (aka atomic) test.
     */
    protected final void runTestUnit(Statement statement, Description description,
            RunNotifier notifier) {
        EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
        eachNotifier.fireTestStarted();
        try {
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
        } catch (Throwable e) {
            retry(eachNotifier, statement, e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }

    public void retry(EachTestNotifier notifier, Statement statement, Throwable currentThrowable) {
        Throwable caughtThrowable = currentThrowable;
        while (retryCount > failedAttempts) {
            try {
                statement.evaluate();
                return;
            } catch (Throwable t) {
                failedAttempts++;
                caughtThrowable = t;
            }
        }
        notifier.addFailure(caughtThrowable);
    }
}

2
Vấn đề là khi kiểm tra không thành công trong phương pháp AfterClass.
user1050755

1
Tôi không thấy có vấn đề gì. Tôi đã viết thử nghiệm mẫu chạy thử nghiệm với người chạy được chỉ định và có vẻ như hoạt động ổn: @RunWith (RetryRunner.class) public class TestSample {private static int i = 0; @AfterClass public static void testBefore () {System.out.println ("Trước khi kiểm tra"); i ++; if (i <2) {fail ("Không đạt"); }}}
user1459144

6

Bạn phải viết của riêng bạn org.junit.runner.Runnervà chú thích các bài kiểm tra của bạn với @RunWith(YourRunner.class).


5

Bình luận đề xuất đã được viết dựa ob này bài viết với một số bổ sung.

Tại đây, nếu một số Test Case từ dự án jUnit của bạn nhận được kết quả "fail" hoặc "error", thì Test Case này sẽ được chạy lại một lần nữa. Ở đây, chúng tôi đặt 3 cơ hội để có được kết quả thành công.

Vì vậy, Chúng tôi cần tạo Lớp quy tắcthêm thông báo "@Rule" vào Lớp kiểm tra của bạn .

Nếu bạn không muốn viết các thông báo "@Rule" giống nhau cho từng Lớp thử nghiệm của mình, bạn có thể thêm nó vào Lớp SetProperty trừu tượng của mình (nếu bạn có) và mở rộng từ Lớp đó.

Lớp quy tắc:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class RetryRule implements TestRule {
    private int retryCount;

    public RetryRule (int retryCount) {
        this.retryCount = retryCount;
    }

    public Statement apply(Statement base, Description description) {
        return statement(base, description);
    }

    private Statement statement(final Statement base, final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Throwable caughtThrowable = null;

                // implement retry logic here
                for (int i = 0; i < retryCount; i++) {
                    try {
                        base.evaluate();
                        return;
                    } catch (Throwable t) {
                        caughtThrowable = t;
                        //  System.out.println(": run " + (i+1) + " failed");
                        System.err.println(description.getDisplayName() + ": run " + (i + 1) + " failed.");
                    }
                }
                System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures.");
                throw caughtThrowable;
            }
        };
    }
}

Lớp kiểm tra:

import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

/**
 * Created by ONUR BASKIRT on 27.03.2016.
 */
public class RetryRuleTest {

    static WebDriver driver;
    final private String URL = "http://www.swtestacademy.com";

    @BeforeClass
    public static void setupTest(){
        driver = new FirefoxDriver();
    }

    //Add this notification to your Test Class 
    @Rule
    public RetryRule retryRule = new RetryRule(3);

    @Test
    public void getURLExample() {
        //Go to www.swtestacademy.com
        driver.get(URL);

        //Check title is correct
        assertThat(driver.getTitle(), is("WRONG TITLE"));
    }
}

0

Câu trả lời này được xây dựng dựa trên câu trả lời này .

Nếu bạn cần ActivityScenario(và Hoạt động của bạn) được tạo lại trước mỗi lần chạy, bạn có thể khởi chạy nó bằng cách sử dụng thử với tài nguyên. Sau ActivityScenariođó sẽ tự động đóng lại sau mỗi lần thử.

public final class RetryRule<A extends Activity> implements TestRule {
    private final int retryCount;
    private final Class<A> activityClazz;
    private ActivityScenario<A> scenario;

    /**
     * @param retryCount the number of retries. retryCount = 1 means 1 (normal) try and then
     * 1 retry, i.e. 2 tries overall
     */
    public RetryRule(int retryCount, @NonNull Class<A> clazz) {
        this.retryCount = retryCount;
        this.activityClazz = clazz;
    }

    public Statement apply(Statement base, Description description) {
        return statement(base, description);
    }

    private Statement statement(final Statement base, final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Throwable caughtThrowable = null;

                // implement retry logic here
                for (int i = 0; i <= retryCount; i++) {
                    try(ActivityScenario<A> scenario = ActivityScenario.launch(activityClazz)){
                        RetryRule.this.scenario = scenario;
                        base.evaluate();
                        return;
                    } catch (Throwable t) {
                        caughtThrowable = t;
                        Log.e(LOGTAG,
                                description.getDisplayName() + ": run " + (i + 1) + " failed: ", t);
                    }
                }
                Log.e(LOGTAG,
                        description.getDisplayName() + ": giving up after " + (retryCount + 1) +
                                " failures");
                throw Objects.requireNonNull(caughtThrowable);
            }
        };
    }

    public ActivityScenario<A> getScenario() {
        return scenario;
    }
}

Sau đó, bạn có thể truy cập kịch bản của mình trong các thử nghiệm bằng getScenario()phương pháp này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.