Unit Test Bootstrapping - #5902
Conversation
NortySpock
left a comment
There was a problem hiding this comment.
(senior developers, please let me or keithharvey know if there was some other unit testing framework already in use that we missed.)
@keithharvey I have questions about where the type-check code came from (see the review comment later down on "how is this licenced, exactly?"), but I would love to be able to have these non-engine-based unit tests so we can write tests quickly and not manually need to check everything.
(for those unfamiliar with unit tests, the advantage is you can write code that tests one or two functions together, easily, and those tests help monitor for accidental changes or unexpected edge cases.)
To install the unit test framework, a Debian Linux developer would run
sudo apt install luarocks
sudo luarocks install busted
and then could list all the tests with
busted -l
spec/gamedata/unitdefs_spec.lua:10: UnitDefs should be loaded
spec/gamedata/unitdefs_spec.lua:22: UnitDefs should have valid structure
and then run all the tests with
busted
●●
2 successes / 0 failures / 0 errors / 0 pending : 0.265552 seconds
If we can add tests to verify that a function or module "does what it says on the tin" when looking for a bug or when making a change, we can easily test or disprove hypotheses. We can also support safe refactoring of code that we wanted to clean up, by wrapping it in tests that characterize the current behavior, splitting it out into smaller modules (that likely also get tests) and verify that the system has the same tests pass before and after the code change. It allows you to make changes with less need for manual testing.
|
Edited the description to better highlight the parts of this PR better that need scrutiny. There's some examples of more complicated specs in another branch I am working on if anyone is curious what that would look like. |
154a0ea to
e44f7ae
Compare
b0ed082 to
4420be7
Compare
|
It would be nice if recoil-lua-library/library/generated/rts/Lua/LuaUnsyncedCtrl.cpp.lua and SyncedCtrl would publish an equivalent EmmyLua class type to Spring's API ( Edit: I deleted |
|
I'm all for unit tests but copying the code of the other library sounds off to me. Is there no better way? What if they update their library with critical fixes, do we have to remember to manually copy paste it again? Maybe I'm spoiled with the java world but it just feels wrong to me. |
I get that, but I'm in favor of merging this as-is so we get something working. Figuring out how to write the config file for the luarocks package manager sounds like extra steps that, while possibly useful in the future, don't solve the problem now of "we just need code that runs unit tests to allow people to write unit tests." "Make it work, then make it right, then make it fast" - Kent Beck We're still at "Make it work" |
efrec
left a comment
There was a problem hiding this comment.
I'm all for unit tests but copying the code of the other library sounds off to me. Is there no better way? What if they update their library with critical fixes, do we have to remember to manually copy paste it again? Maybe I'm spoiled with the java world but it just feels wrong to me.
I get that, but I'm in favor of merging this as-is so we get something working. Figuring out how to write the config file for the luarocks package manager sounds like extra steps that, while possibly useful in the future, don't solve the problem now of "we just need code that runs unit tests to allow people to write unit tests."
"Make it work, then make it right, then make it fast" - Kent Beck
We're still at "Make it work"
Have to agree that this puts me off the PR.
It absolutely is wrong. It also feels off to me. Unfortunately in this case I think we don't have a good package manager setup in the project so the alternatives are submodules, which imo are worse. These are strictly type definitions and I did include a provenance.md so in a few years if we notice the types don't match, someone can go grab the |
1af3194 to
cf6330f
Compare
a3e274a to
056f520
Compare
4d18960 to
624e290
Compare
cfb93dd to
b0ca938
Compare
|
Well shoot I just noticed I did a bad job pulling work out of my sharing tab branch, moving this back to draft for a second while I clean up the mocks. |
5a65910 to
5281827
Compare
I did rename the files so the alphabetization should help and fix the check_name on the post-processing job to be to be "Integration Test Results" but we won't see that until this hits master.
|
6db8950 to
e60d37c
Compare
Include lux and add the busted and luassert test dependencies. Lux seems like it is set to become the de facto package manager for lua and it is also not luarocks.
Add the busted unit testing framework with example specs, installation docs, and legal licensing. Includes luassert for assertion DSL. Key features: - Builder pattern for mock construction with fluent interfaces - Spring repository wrapper for dependency injection. Includes helpers like WithAlliance, AddTeamResources, and mod option support. Allows loading global dependent code in test closures. - Team builder for configuring team properties - Global state isolation with WithGlobalsDefined helper Busted needs to load project dirs in `.busted`. This is necessary because lx expects a `src/` folder matching lua package structure.

What this does
Adds a set of configuration defaults, CI, and mocks/builders for the busted unit testing framework with an example spec. Updated the readme with installation instructions and examples.
Related Work
Docs
types/luassert/, with attributionWhy Unit Testing
You get to make assertions on idempotent code and see what pops out the other side in <1s. Very useful for breaking down moderate to complex problems.
Abstracts Global State
We try not to touch global state in unit tested code because it makes tests fragile and verbose. Instead, we pass in our dependencies (like Spring, or in the case of a single function its parameters).
Builder (Mocks)
I think the builders are the part of this PR that deserves the most scrutiny since that is one of the more opinionated parts of this test setup and their design will be super important to having elegant tests.
The builder pattern is my default for building mocks when unit testing across languages because it provides good intellisense and navigate-to-definition support and is relatively straightforward. You can create the builder in a parent closure/describe block, then leave it a builder until you are ready to test some specific condition, and then call
builder:Build()to create the mock. An example:Mocks
spring_builder
Builds a mock for implementations that consume
ISpringto use instead. This builder provides a number of helper functions such asWithAlliance,AddTeamResources, and mod option support.It also provides a
WithGlobalsDefined(fun: void), which allows easy loading of files that depend on global state into a local spec closure.teamBuilder
Provides an interface to set up a specific team's properties
For example:
I tend to not like static mock libraries very much (even in strongly typed languages like C#, I'm thinking of Moq) and prefer shared, bespoke builders representing things in my domain versus each test creating its own mocks derived from type definitions. I doubt this would be super viable in a dynamic language like LUA-5.1, but thought it worth mentioning as an alternative pattern I have seen.
Asks
SpringBuilderwith its builder pattern and fluentWithinterface?springBuilder:WithTeam(teamBuilder)? Would we preferSpringBuilderjust handled the setup for its own api entirely by itself without the builder composition?LLM DIsclosure
I am sure some of these builders were touched by an LLM at some point but I've been working this code for a while myself now.