Skip to content
/ fuzzy Public

A handy little library for writing expressive "fuzz tests" in Java

License

Notifications You must be signed in to change notification settings

redfin/fuzzy

Repository files navigation

Fuzzy is a handy little library for writing expressive "fuzz tests" in Java.

Build Status Coverage Status fuzzy-core Javadoc fuzzy-core Javadoc

public void test() {
    // Create an input that can be one of three different strings
    Generator<String> myString = Generator.of("A", "B", "C");

    // The value of myString returned by the .get() method will be
    // different for each iteration of the test.
    System.out.println(myString.get());

    // You can use the generator multiple times during your test, and
    // each time it will return the same value.
    assert myString.get() != "D";
}

Fuzzy also comes with some built-in generators that ensure that your code is tested against common input edge cases. For example, a generator of Any.integer() will inject your inputs with negative values, positive values, and zero.

Installation

At a minimum, you will need to take a dependency on the fuzzy-core library:

<!-- Maven -->
<dependency>
    <groupId>com.redfin</groupId>
    <artifactId>fuzzy-core</artifactId>
    <version>0.6.1</version>
    <scope>test</scope>
</dependency>

In addition, if you're using a supported unit testing framework (currently only Junit 4), you can take a dependency on the library for that framework:

<!-- Maven Junit 4 -->
<dependency>
    <groupId>com.redfin</groupId>
    <artifactId>fuzzy-junit-4</artifactId>
    <version>0.6.1</version>
    <scope>test</scope>
</dependency>

Use with JUnit

The FuzzyRule test rule can be added to your JUnit test cases to automatically execute your tests against various permutations of their input variables:

public class MyClassTest {

    @Rule public FuzzyRule fuzzyRule = FuzzyRule.DEFAULT;

    @Test
    public void testSomething() {
        // Declare your test variables
        Generator<String> myString = Generator.of(Any.string());

        // Execute the test
        MyClass subject = new MyClass();

        boolean actual = subject.something(myString.get());
        assertTrue(actual);
    }

}

See the FuzzyRule Options section later in this document for a description of the different configuration settings available.

Generators and Cases

When writing tests with Fuzzy, you use Cases to describe test values, and Generators to declare, retrieve, and use those values.

Each Case broadly describes a particular variable. For example, you might have an EmailCase implements Case<String> that describes email addresses. Cases then provide subcases (or edge cases) that cover the variants of those variables. The EmailCase might return subcases for typical address (e.g., [email protected]), the newer top-level domains (e.g., [email protected]), for special formatting (e.g., me@[1.1.1.1]), and for the more obscure types (e.g., this."is\ valid"@example.com).

When you declare a generator for the EmailCase in one of your tests, Fuzzy will make sure to run the test enough times so that each of the subcases is returned at least once. That's four tests (four subcases) for the price of one!

Fuzzy will automatically detect all of the generators declared by your test and use pair-wise combination to ensure that each variable's subcases are all executed. The catch is that for this to work, you need to declare all variables before you use any of them, and they all need to be declared on the thread that is executing your tests. (Note: it's OK to access generators from another thread, as long as they're all declared on the thread running the tests.)

Good:

@Test
public void myTest() {
   // All of your generators must be declared before any of them are used, so
   // that fuzzy understands the number of necessary test permutations.

   // Declare test variables
   Generator<String> to = Generator.of(new EmailCase());
   Generator<Integer> orderCount = Generator.of(Any.integer().inRange(1, 100));

   // Use your test variables
   assertTrue(subject.sendEmail(to.get(), orderCount.get()));
}

Bad:

@Test
public void myTest() {
   // All of your generators must be declared before any of them are used, so
   // that fuzzy understands the number of necessary test permutations.

   // Declare and access one generator
   Generator<String> emailGenerator = Generator.of(new EmailCase());
   String toAddress = emailGenerator.get();

   // Declare and access another generator; you'll get an IllegalStateException
   // on the next line
   Generator<Integer> intGenerator = Generator.of(Any.integer().inRange(1, 100));
   int orderCount = intGenerator.get();
}

Behavioral Specifications

Permuation Modes

Pairwise

Each Subcase At Least Once

Use with Other and Custom Test Frameworks

For other frameworks or test scenarios, you can configure the test process manually. Use the Context class to initialize and iterate over the possible permutations.

public void executeFuzzyTest() {
    // Initialize the testing context. This applies to all generators
    // created on this thread.
    Context.init();

    try {
        do {
            // Execute your test code here.
            executeSingleTestIteration();
        } while(Context.next());
    }
    catch(AssertionError | Exception e) {
        // Handle test failures
        // You can use Context.report() or Context.reportTo(StringBuilder)
        // to get a sense of the inputs that caused the test failure.
    }
    finally {
        // Clean up for the next test
        Context.cleanUp();
    }
}

FAQ

FuzzyRule Options

FuzzyRule defines a number of options for controlling the flow of your tests:

failImmediately

@Rule FuzzyRule fuzzyRule = FuzzyRule.custom()
                                     .withFailImmediately(false)
                                     .build();

@Rule FuzzyRule fuzzyRule = FuzzyRule.REPORTING_ALL_FAILURES;

Determines if a failure of any test case iteration will be reported immediately. When false, FuzzyRule will execute your test case against all expected iterations regardless of whether any individual iteration results in a test failure. Only the last encountered failure will be reported to JUnit.

The FuzzyRule.REPORTING_ALL_FAILURES preset sets failImmediately to false and also uses a test reporter that summarizes the generated input values for each failed iteration, which can be helpful when debugging broken tests.

The default value for failImmediately is true, meaning that failures may preempt some test case iterations from executing.

testReporter

@Rule FuzzyRule fuzzyRule = FuzzyRule.custom()
                                     .withTestReporter(...)
                                     .build();

@Rule FuzzyRule fuzzyRule = FuzzyRule.SUMMARIZING;
@Rule FuzzyRule fuzzyRule = FuzzyRule.VERBOSE;

Configures the amount of output generated by fuzzy during test runs, or allows you to attach your own listeners to the testing engine.

The FuzzyRule.SUMMARIZING preset will output the number of iterations executed for each of your test cases.

The FuzzyRule.VERBOSE preset will output detailed information during the test run, including the generated inputs for each test case iteration.

For custom test reporters, see the documentation for TestReporter.

The default value for testReporter is TestReporter.DEFAULT, which produces no additional output.

maxIterations

@Rule FuzzyRule fuzzyRule = FuzzyRule.custom()
                                     .withMaxIterations(100)
                                     .build();

The maximum number of times each test in your suite will be executed. If the number of permutations necessary to cover all input pairs in the test is greater than this bound, they will not be executed.

The default value for maxIterations is 100.

failAfterMaxIterations

@Rule FuzzyRule fuzzyRule = FuzzyRule.custom()
                                     .withFailAfterMaxIterations(true)
                                     .build();

Determines if tests fail when their input permutations cannot be covered with fewer than the maximum iterations.

The default value for failAfterMaxIterations is false.

Contributing

TODO: flesh this section out

  • Do not add any new runtime dependencies to fuzzy-core; fuzzy-junit should only depend on junit itself. In order to make it easy to import fuzzy, we don't want to introduce any dependency conflicts on our consumers. This is the reason for classes such as FuzzyPreconditions.

About

A handy little library for writing expressive "fuzz tests" in Java

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages