How To Test Your Software With Julia | by Emmett Boudreau | Towards Data Science

Testing is one of the most important aspects of iterative software development. This is because tests can reveal problems with software automatically without ever needing to actually use the software. Needless to say, it is probably a bad idea to push software that does not even work to a repository, and testing can avoid ever running into that issue. The implementation of testing into the Julia programming language is an implementation that I think is rather awesome and unique. I think it is great because it provides a very structured and easy way to test software using conditionals and macros. Today I wanted to walk through the basics of the Testing.jl package for Julia so that you can make sure your software works before making a huge mistake!
In order to demonstrate Julia’s testing package, we are first going to need to set up a new project. In order to set up a new project, we can use Pkg.generate(), or the generate command in the Pkg REPL. Once in the Julia REPL, I will press ] and generate our new project named “ Sample.”
julia> ](@v1.6) pkg> generate sample
Next, I will backspace out of that REPL and then press ; to enter the Bash REPL. Afterwards, we will cd into our new Project folder.
julia> ;shell> cd sample
Now I will cd into the source folder and nano the sample.jl file, adding a new function that we can test.
shell> cd src/home/emmett/dev/julia/sample/srcshell> nano sample.jl
Inside of this file, I put a few functions that are used for model validation, along with the functions that are used to support them:
mean(x) = sum(x) / length(x)function mae(actual,pred)    l = length(actual)    lp = length(pred)    if l != lp        throw(ArgumentError("The array shape does not match!"))    end    result = actual-pred    maeunf = mean(result)    if maeunf < 0        maeunf = (maeunf - maeunf) - maeunf    end    return(maeunf)endfunction mse(y,ŷ)    diff = y .- ŷ    diff = diff .^ 2    Σdiff = sum(diff)    return(Σdiff)endfunction correlationcoeff(x,y)    n = length(x)    yl = length(y)    @assert n == yl DimensionMismatch("These Arrays are not the same size.")    xy = x .* y    sx = sum(x)    sy = sum(y)    sxy = sum(xy)    x2 = x .^ 2    y2 = y .^ 2    sx2 = sum(x2)    sy2 = sum(y2)    ((n*sxy) - (sx * sy)) / (sqrt((((n*sx2)-(sx^2)) * ((n*sy2)-(sy^2)))))endfunction r2(actual,pred)    l = length(actual)    lp = length(pred)    if l != lp        throw(ArgumentError("The array shape does not match!"))    end    r = correlationcoeff(actual,pred)    rsq = r^2    rsq = rsq * 100    return(rsq)endexport r2, mae, mse
Then I used ctrl+O to right that new buffer out, and now we can get started on actually testing these functions!
Now that we actually have a package to test, we can go about setting up our test environment. With Julia packages, both the testing and the documentation are considered packages separate to the original package. That in mind, we are going to use the generate command once again in order to create both of these project directories. We will activate this new test environment, and then we will add the test dependency to its virtual environment.
shell> cd ../home/emmett/dev/julia/sample(@v1.6) pkg> activate test  Activating new environment at `~/dev/julia/sample/test/Project.toml`(test) pkg> add Test    Updating registry at `~/.julia/registries/General`   Resolving package versions...    Updating `~/dev/julia/sample/test/Project.toml`  [8dfed614] + Test    Updating `~/dev/julia/sample/test/Manifest.toml`  [2a0f44e3] + Base64  [b77e0a4c] + InteractiveUtils  [56ddb016] + Logging  [d6f4376e] + Markdown  [9a3f8284] + Random  [9e88b42a] + Serialization  [8dfed614] + Test(test) pkg>
Now we can write our testing algorithms. The first function inside of the package is MAE. In order to perform a test, we need to start a test set. This is done with the test set macro. After the macro, we provide a string as the first parameter, and then we provide an expression as the second parameter. The string is a title of for our test-set, and the expression is the code that will perform our test. We can create such an expression using a begin and end block. That all comes together to look something like this:
using Testinclude("../src/sample.jl")using Main.sample@testset "MAE tests" beginend
We will start these tests with a test where the two are equal, for this scenario, the MAE should be one. The test macro takes a single argument, and that argument is a boolean. Let’s use a bool-type bitwise operator on the MAE of two equal arrays, which should be equal to zero.
x = [5, 10, 15]y = [5, 10, 15]@test mae(x, y) == 0
Our next test is going to be whether or not we are actually getting the mean of the difference of the dims.
x, y = [4, 10, 15], [5, 10, 15]    @test mae(x, y) == mean([1, 0, 0])
For a final test set that looks like this:
using Main.sample: r2, mae, mse, mean@testset "MAE Tests" begin    x = [5, 10, 15]    y = [5, 10, 15]    @test mae(x, y) == 0    x, y = [4, 10, 15], [5, 10, 15]    @test mae(x, y) == mean([1, 0, 0])end
Next, we will begin a new test set for the MSE function. We will be doing one assertion test, and one zero test just like we did before.
@testset "MSE Tests" beginx = [5, 10, 15]y = [5, 10, 15]@test mse(x, y) == 0y = [4, 10, 15]y2 = [6, 10, 15]@test mse(x, y) == mse(x, y2)end
Next, we will test our r2 function. Since our r2 function is meant to return a percentage, we will both check the type of the return as a Float64 and then check if it is less than 1.
@testset "R2 Tests" beginx = randn(50)y = randn(50)@test r2(x, y) <= 1@test typeof(r2(x, y)) == Float64
Now we can run our tests by going back to the REPL and including this code.
include("test/test.jl")
No matter what kind of software you create, it is always going to be important to test it. Julia has a pretty cool method of testing software that utilizes macros and makes the tests very organized. I do happen to prefer this to a lot of other styles of testing, and I think that Julia makes testing a breeze using this methodology. Thank you very much for reading my article, and I hope the information provided is valuable towards making your software work better! Thank you for reading!