JuliaDynamics/Agents.jl

Reform tests according to Good Scientific Code practices

Open

#634 opened on Jun 13, 2022

View on GitHub
 (4 comments) (2 reactions) (0 assignees)Julia (904 stars) (146 forks)batch import
good first issuehelp wantedsimplificationtests

Description

One of the things the Good Scientific Code Workshop teaches is writing good unit tests. I have to admit, Agents.jl suffers a lot from "bad tests", primarily because of its old age. When I got involved with the repo it was so many years ago I didn't have much bearings of good software development practices. While refreshing Agents.jl to v7 it became again apparent that the low quality test suite makes development harder. We should try to improve the test suite in the following ways:

1. Ensure independence

remove "global" definitions in main test file

The problem now is that the main runtest.jl file just defines a buuuuuuunch of stuff that are used in a bunch of other test files. But tests should be atomic: each file should be fully independent and be runnable without relying on any other file being run before. For example, the file test/collect_tests.jl does a good job of that and successfully does not rely on any other file having been run first.

each file is a test suite

Something I have started doing in other packages of JuliaDynamics is to make each package be a test suite without explicitly defining an @testset statement. This is done with the wonderful function (see e.g., the tests of Attractors.jl).

using Test

defaultname(file) = uppercasefirst(replace(splitext(basename(file))[1], '_' => ' '))
testfile(file, testname = defaultname(file)) = @testset "$testname" begin
    include(file)
end

@testset "Attractors.jl" begin
    @testset "mapping" begin
        testfile("mapping/grouping.jl")
        testfile("mapping/recurrence.jl")
        # ...
    end

    @testset "basins analysis" begin
        testfile("basins/tipping_points_tests.jl")
        # ....
    end

    # ...
end

Which also means that test files should be grouped into folders for easier navigation!

2. Follow good test suite practices

I am pasting here the slide with "good advice on writing unit tests":

  • Actually unit: test atomic, self-contained functions. Each test must test only one thing, the unit. When a test fails, it should pinpoint the location of the problem. Testing entire processing pipelines (a.k.a. integration tests) should be done only after units are covered, and only if resources/time allow for it!
  • Known output / Deterministic: tests defined through minimal examples that their result is known analytically are the best tests you can have! If random number generation is necessary, either test valid output range, or use seed for RNG
  • Robust: Test that the expected outcome is met, not the implementation details. Test that the target functionality is met without utilizing knowledge about the internals. Also, never use internal functions in the test suite.
  • High coverage: the more functionality of the code is tested, the better
  • Fast: use the minimal amount of computations to test what is necessary
  • Regression: Whenever a bug is fixed, a test is added for this case
  • Input variety: attempt to cover a wide gambit of input types

3. Resolve specific issues with Agents.jl

Specific problems are:

  • They rely way too much on random numbers, where we should be writing analytic tests instead. Deterministic, analytically resolvable tests are the most accurate ones. The expected outcome of a test comes from pen and paper instead of running code. Right now we have a large amount of tests that were generated like "seed an rng, run some model code, write down the result as a test suite.". What we should be doing instead is "devise a distribution of agents. Analytically resolve their neighbors and actions given some rules and distances. Write tests that test again this analytically resolved output." I recall we had problems many times throught the lifespan of Agents.jl with random tests being broken etc. (obviously, random stuff like the RNG in the model should also be tested, but if you can avoid randomness in the tests, you should)
  • They are not computationally minimal, which means that you should be doing the least amount of computer operations to confirm whether some functionality works. E.g., look at the start of continuousSpace_tests.jl. 8 spaces are initialized, but as far as the subsequent tests are concerned, you only need 3. In a similar vein, the test aren't also memory minimal. For the majority of the tests, we use agents that have extra fields like weight, even though we don't actually use these fields anywhere. E.g., see "mutable graphs" tests, which never uses the weight field yet assigns it anyways.

4. Remove dependence on AgentsExampleZoo.jl

The tests should not depend on the package AgentsExampleZoo.jl as this both breaks the independence principle and introduces cyclic dependencies.


One doesn't have to worry about re-writing all tests. In fact, a PR "correcting" a single test file is already very much welcomed!

Note: issue description has been updated in 22-Feb-26.

Contributor guide