Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

A delightful unit testing framework for GLua projects

License

NotificationsYou must be signed in to change notification settings

CFC-Servers/GLuaTest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

328 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🎉The missing test framework for GMod 🎉

GLuaTest is a testing framework built for Garry's Mod.Its job is to make writing automated tests for Garry's Mod projects easy and tolerable.

It offers an approachable and flexible syntax that makes writing tests intuitive.

GLuaTest takes a lot of inspiration from both Ruby'sRSpec and JavaScript'sJest

Glossary

GLuaLint


(Are you an impatient software developer? Check out thequickstart guide to go fast)

(Is the idea of testing your code new to you? That's great! Check out theguided testing walkthrough to see some great examples of how to test real code)


Features

Simple test setup and quirky (yet intuitive!) test syntax

-- lua/tests/project_name/main.luareturn {groupName="MyProject",cases= {        {name="Should create project tables",func=function()expect(MyProject ).to.exist()end        }    }}

Beautiful test output

Handsome, informative error reporting

image


Some additional reading:

Foreword about automated testing in GMod
Automated testing is a crucial part of any software workflow.Your automated tests define a contract that give you and your would-be users confidence that the project will behave properly.

Without tests, you may find yourself spending large amounts of time debugging obscure issues.Automated tests require more work up front, but will save you time and frustration in the future as your project grows.


Traditionally, Garry's Mod developers have included, at most, a few crucial tests with their codebase - usually only ran manually when the maintainer remembers.Modern testing infrastructure allow you to run your tests on a Pull Request, before the code has made it into the main branch.

Such a tool has never existed for Garry's Mod. Until now!

Technical info
GLuaTest was made to run on GitHub Actions, but it's flexible enough to fit anywhere you'd like.

You can use the GLuaTest Docker image to spin up a test server, run your tests, and see the output - all without having to install a server of your own.

This makes it easy to test your code in a real Garry's Mod environment without worrying about other addons or config values.


Usage

GLuaTest can be used in a number of ways. Whether you want to run your tests when you open a PR, or if you just want to have it run on your development server - we've got you covered.


Automated testing on Pull Requests

Run your tests in a Pull Request

To set up automated test runs, we'll use GitHub Workflows.

It's actually really simple to set up the workflow. Add the following file to your project:

name:GLuaTest Runneron:pull_request:jobs:run-tests:uses:CFC-Servers/GLuaTest/.github/workflows/run_tests.yml@main

And that's it! The next time you make a PR, it'll spin up a new test server, run your project's test, and report any failures in your PR.

There are a couple of config options you can use though.


Requirements

If your project depends on an external project, GLuaTest can automatically grab them for you

Let's say you needed:

Make a new file somewhere in your project (i.e.lua/tests/my_project/requirements.txt) with the following:

TeamUlysses/ulxTeamUlysses/ulibCFC-Servers/gm_logger@lua

Each line should be in the format of:<Github owner name>/<Project name>.

You can use a specific branch of the project by adding@<branch-name> to the end of the line.

(If your requirement is hosted in a private GitHub repo, you'll need to do some annoying legwork to get everything connected.More info here.)

Great, now we update our workflow to use our requirements file

name:GLuaTest Runneron:pull_request:jobs:run-tests:uses:CFC-Servers/GLuaTest/.github/workflows/run_tests.yml@mainwith:requirements:lua/tests/my_project/requirements.txt

All done! Commit those changes and GLuaTest will automatically clone your requirements.


Server Configs

Sometimes your project requires specific convars / server settings

Similar to how you define requirements, we'll make a new.cfg file.

This file will be dumped straight into the end of server'sserver.cfg file. You can override existing configs, too.

So, create the file:

# Example file name/location: lua/tests/my_project/server.cfgmy_convar 1name "My favorite server"

Update the workflow:

name:GLuaTest Runneron:pull_request:jobs:run-tests:uses:CFC-Servers/GLuaTest/.github/workflows/run_tests.yml@mainwith:server-cfg:lua/tests/my_project/server.cfg

And that's it!


Gamemodes and Maps

You can customize which gamemode and map the server starts with

Simply specify the desired gamemode and/or map in your workflow'swith section.

name:GLuaTest Runneron:pull_request:jobs:run-tests:uses:CFC-Servers/GLuaTest/.github/workflows/run_tests.yml@mainwith:gamemode:darkrpmap:rp_downtown_tits_v2

Workshop Collection

To make dependency management easier, you can tell the test server to use a specific workshop collection.

Add the collection ID in your workflow'swith section.

name:GLuaTest Runneron:pull_request:jobs:run-tests:uses:CFC-Servers/GLuaTest/.github/workflows/run_tests.yml@mainwith:collection:1629732176

GMod Branch

You can run your tests on any of the GMod branches

Just set thebranch input in your workflow:

name:GLuaTest Runneron:pull_request:jobs:run-tests:uses:CFC-Servers/GLuaTest/.github/workflows/run_tests.yml@mainwith:branch:x86-64

Acceptable options are:

  • live (Main GMod version - this is the default)
  • x86-64
  • prerelease
  • dev

Extra Startup Arguments

You can give GLuaTest custom startup args to fine-tune your test setup

You can use theextra-startup-args input to pass any arguments you want to the srcds instance. For example:

name:GLuaTest Runneron:pull_request:jobs:run-tests:uses:CFC-Servers/GLuaTest/.github/workflows/run_tests.yml@mainwith:extra-startup-args:"-tickrate 16 -usegh"

Note: These args are passed in before the base params, so you can override any of the base srcds arguments.


All options

Here are all of the options you can pass to the workflow
NameDescriptionExample
server-cfgA path (relative to project directory) with extra server config optionsdata_static/my_addon.cfg
requirementsA path (relative to project directory) with a list of all requirements to test this projectdata_static/my_addon.txt
gamemodeThe name of the gamemode for the test server to rundarkrp
mapThe direct name of the map you want the server to startup withgm_bigcity_improved_lite
collectionThe workshop ID of the collection for the test server to use1629732176
extra-startup-argsAdditional startup arguments to add to the srcds startup-tickrate 16 -usegh
ssh-private-keyThe Private SSH key to use when cloning the dependencies-----BEGIN OPENSSH PRIVATE KEY-----\n...
github-tokenA GitHub Personal Access Token, used when cloning dependencies
timeoutHow many minutes to let the job run before killing the server10
branchWhich GMod branch to run your tests onlive
gluatest-refWhich tag/branch of GLuaTest to runmain
custom-overridesAn absolute path with custom files to copy to the server directly. Structure should match the contents ofgarrysmod/$GITHUB_WORKSPACE/my_overrides
download-artifactA URL path to a .tar.gz file that will be unpacked in the root directoryhttps://github.com/RaphaelIT7/gmod-holylib/releases/download/Release0.7/gmsv_holylib_linux_packed.zip
additional-setupIf specificed, executes the given string as a script after all setup is complete, allowing additional setupecho "Hello, this is a test!"

Speed 🏃

Running tests in a GitHub Runner is surprisingly fast.

Even with hundreds of tests, you can expect the entire check to takeunder 30 seconds!

In fact, the test suite itself will often complete in only a couple of seconds. Most of the time is spent downloading the image and setting up the runner.

(Failing async tests will slow down the time significantly because it has to wait for the timeouts)

Cost 💸

You should incur no costs by using GitHub Actions.

Nothing better than free 😎

Running locally

Running your tests locally

It's actually extremely simple to run GLuaTest locally.

Just put GLuaTest into youraddons/ directory and restart!

All of your tests will run when the server starts up and you can view the output in the server console/logs.

Running locally without a server

Running your tests locally without a server

Sounds weird, right? Well it's really not all that different from running GLuaTest on GitHub.

In fact, many of the steps are the same.

Requirements / Server Configs

If your project depends on other projects, you can have GLuaTest automatically acquire them for you.

Take a quick skim through theGitHub Action setup instructions for the relevant sections on how to set this up.

Environment setup

When running GLuaTest without a server, you need to tell it where to find your project and custom files.

You can do that with simple environment variables, i.e.:

export REQUIREMENTS=/absolute/path/to/requirements.txtexport CUSTOM_SERVER_CONFIG=/absolute/path/to/server.cfgexport PROJECT_DIR=/home/me/Code/my_projectexport GAMEMODE="sandbox"export COLLECTION_ID="12345"export SSH_PRIVATE_KEY="the-entire-private-key"export GITHUB_TOKEN="a-personal-access-token"
  • You can skip theREQUIREMENTS andCUSTOM_SERVER_CONFIG if you don't need them, but you must set thePROJECT_DIR variable.

  • TheGAMEMODE variable defaults to"sandbox", so you can omit it if that's appropriate for your tests.

  • TheCOLLECTION_ID variable allows you to pass a workshop collection ID for the server to grab before starting.

  • TheSSH_PRIVATE_KEY variable is used when one or more of your Requirements are hosted on a Private Repository.

  • TheGITHUB_TOKEN variable, like theSSH_PRIVATE_KEY is also used to grant access to private repositories. Personal Access Tokens are a simpler (but ultimately worse) alternative to a full SSH keypair.

(Read more about privately-hosted project requirements:https://github.com/CFC-Servers/GLuaTest/wiki/Private-Requirements )

Running in Docker

Now you'll need docker-compose. I'll leave it to you to figure out how to install it:https://docs.docker.com/compose/install/

Once that's done, you just need to run thedocker-compose file in thedocker/ directory.

On Linux/OSX, this looks like:

docker-compose up

And.. that's it! It'll pull the latest Runner, start the server, and run your tests.You can even follow the test output live.


Writing Tests ✍️

Your first test file

In your GLua project, create a new directory:lua/tests/<your project name>/.

This is where you'll keep the tests for your project.You can put all of your tests in one file, or split the files up based on module/responsibility.

For example, if your addon had two entities you'd like to test, you could makelua/tests/your_project/entity_1.lua andlua/tests/your_project/entity_2.lua.We suggest you group your tests, but it'll work either way.

The test file itself is fairly simple. It has a few keys you can use, but the only requirement is thecases key; a table of Test Cases.

For example:

-- lua/tests/my_clock/get_time.luareturn {cases= {        {name="It should return the correct time",func=function()localmyClock=Clock.New()localrealTime=os.time()expect(myClock:GetTime() ).to.equal(realTime )end        }    }}

The Test Group

The Test Group (that is, the table you return from your Test File) can have the following keys:

KeyTypeDescriptionRequired
casestableA table ofTest Cases✔️
groupNamestringThe name of the module/function this Test Group is testing
beforeAllfunctionA function to run once before running your Test Group
beforeEachfunctionA function to run before each Test Case in your Test Group. Takes astate table
afterAllfunctionA function to run after all Test Cases in your Test Group
afterEachfunctionA function to run after each Test Case in your Test Group. Takes astate table

The Test Case

Each Test Case is a table with the following keys:

KeyTypeDescriptionRequiredDefault
namestringName of the Test Case (for reference later)✔️
funcfunctionThe actual test function. Takes astate table✔️
asyncboolIf your test relies on timers, hooks, or callbacks, it must run asynchronouslyfalse
coroutineboolThis allows your test to use coroutines to control its executionfalse
timeoutintHow long to wait for your async test before marking it as having timed out5
cleanupfunctionThe function to run after running your test. Takes astate table
whenbool / functionOnly run this test case "when" this field is(or evaluates to)true
skipbool / functionSkip this test case if this field is(or evaluates to)true

Theexpect function

The heart of a test is theexpectation. You did a thing, and now you expect a result.

In each test function, you have access to theexpect function.

Let's say you expect"a" to equal"b". In regular Lua you might do:

assert("a"=="b")

Similarly, in GLuaTest, you'd do:

expect("a").to.equal("b")

There are a number of different expectations you can use.

Expectations

ExpectationDescriptionExample
equal/eqBasic== equality checkexpect( a ).to.equal( b )
aboutEqualBasic== equality check, with a toleranceexpect( 0.999 ).to.aboutEqual( 1 )
deepEqualExpects that two tables are deeply equalexpect( {{ Entity(1) }} ).to.deepEqual( {{ Entity(1) }} )
beLessThanBasic< comparisonexpect( 5 ).to.beLessThan( 6 )
beGreaterThanBasic> comparisonexpect( 10 ).to.beGreaterThan( 1 )
beBetweenExpects the subject to be less than min, and greater than maxexpect( 5 ).to.beBetween( 3, 7 )
beTrueExpects the subject to literally betrueexpect( Entity( 1 ):IsPlayer() ).to.beTrue()
beFalseExpects the subject to literally befalseexpect( istable( "test" ) ).to.beFalse()
beValidExpectsIsValid( value ) to returntrueexpect( ply ).to.beValid()
beInvalidExpectsIsValid( value ) to returnfalseexpect( nil ).to.beInvalid()
beNilExpects the subject to literally benilexpect( player.GetAll()[2] ).to.beNil()
beNaNExpects the subject to be NaNexpect( 0 / 0 ).to.beNaN()
existExpects the subject to not benilexpect( MyProject ).to.exist()
beA/beAnExpects the subject to have the giventypeexpect( "test" ).to.beA( "string" )
succeedExpects the subject function to run without errorexpect( func, param ).to.succeed()
errExpects the subject function to throw an errorexpect( error ).to.err()
errWithExpects the subject function to throw the given errorexpect( badFunc, param ).to.errWith( "error message" )
calledExpects the subject Stub have been calledexpect( myStub ).was.called()

Expectation Negation

You can invert an Expectation by using.toNot or.notTo in place of your.to

i.e.:

expect(ply ).toNot.beInvalid()expect("test").notTo.beA("table")

Was

You may replace.to with.was in any expectation..wasNot is also valid.

Primarily this is syntax sugar for thecalled expectation. Technically these two calls are equivalent:

expect(func ).to.called()expect(func ).was.called()

when andskip

These fields can be used to control your test invocation.

For example, to run your test case only on thex86-64 branch:

{name="Is valid on x86-64",when=BRANCH=="x86-64",func=function()-- x86-64 specific stuff hereend}

Skipping is also handy if you want to disable a test but keep the code:

{name="Broken test (but I'll definitely fix it some day 100%),skip=true,func=function()error()end}

Note:skip takes precedence overwhen


Thecoroutine option

Sometimes you need your test to wait indefinitely for unpredictable circumstances that don't have callbacks

In these situations, you can use thecoroutine option to run your test in a coroutine.

For example, if you're testing with Nextbots, you'll find it frustrating to wait for them to disconnect before your next test runs.

-- lua/gluatest/extensions/my_nextbot_extensions.lua--- Halts the coroutine until the server is emptyWaitForEmptyServer=function()localco=coroutine.running()localidentifier=getWaitIdentifier()hook.Add("Think",identifier,function()localcount=player.GetCount()ifcount>0thenreturnendhook.Remove("Think",identifier )coroutine.resume(co )end )returncoroutine.yield()end
-- lua/tests/my_nextbot/my_nextbot.luareturn {groupName="My Nextbot tests",-- Automatically kick all bots after each testafterEach=function()for_,botinipairs(player.GetBots() )dogame.KickID(bot:UserID() )endend,cases= {        {name="Should be able to spawn a nextbot",async=true,timeout=2,coroutine=true,func=function()WaitForEmptyServer()-- We need to be sure the server is empty before we do our tests, otherwise it could fail due to timinglocalmyBot=player.CreateNextBot("Silly little guy")expect(player.GetCount() ).to.equal(1 )expect(player.GetAll()[1] ).to.equal(myBot )done()end        }    }}

Thestub function

Isolating your tests is important. Stubs are a powerful way of controlling which parts of your code your tests invoke.

Let's say your addon looks like this:

MyProject= {}functionMyProject.UserExistsInDatabase(user )localuserObject=lookupUserInDatabase(user )returnuserObject.existsendfunctionMyProject.CheckUser(user )ifnotMyProject.UserExistsInDatabase(user )thenreturnendifnotuser.namethenreturnendif#user.name==0thenreturnendreturntrueend

You want to test the functionality ofCheckUser.

There are three checks inCheckUser:

  • The user exists in the database
  • The user's name exists
  • The user's name is not empty

You could add a fake user to the database and use the function normally, but you're not testing thedatabase, you're testingCheckUser.

Instead, we couldpretend thatUserExistsInDatabase returnstrue for our tests. We can do this using a Stub.

-- lua/tests/my_project/checkuser.luareturn {groupName="CheckUser",beforeEach=function(state )state.validUser= {name="Valid User"}end,cases= {        {name="Should return true with a valid User",func=function(state )stub(MyProject,"UserExistsInDatabase").returns(true )expect(MyProject.CheckUser,state.validUser ).to.beTrue()end        },        {name="Should check if user exists in database",func=function()localdbCheck=stub(MyProject,"UserExistsInDatabase").returns(true )MyProject.CheckUser(state.validUser )expect(dbCheck ).to.haveBeenCalled()end        }    }}

Now ourCheckUser testonly tests the functionality inCheckUser, and doesn't depend on any other function's correctness.

A Stub will replace the given function on the given table with a callable Stub object. The Stub keeps track of how many times it was called, and what parameters it was called with.

Stub Restoration

If you need to restore the original functionality of the stubbed function, you can usestub:Restore().

Un-restored stubs are automatically restored after each Test Case, but you can manually call:Restore() any time you need.

Empty Stubs

You can create an empty Stub that doesn't automatically replace anything by callingstub() with no arguments.

You can use the Stub like normal. This is particularly useful for functions that take callbacks, i.e.:

{name="RunCallback should run the given callback",func=function()localmyStub=stub()MyProject.RunCallback(myStub )expect(myStub ).to.haveBeenCalled()end}

Restoring empty stubs is a no-op, but won't break anything.

Stub return values

You can tell your stubs what to return when they're called.

.with( function )

If you want to replace a function with another function, you can use the.with modifier.

When your stub is called, it will pass all of the parameters it received to the function you gave to.with, and will return whatever your given function returns.

.returns( ... )

If you just want to return a certain value every time your Stub is called, you can use the.returns modifier.

When your Stub is called, it will simply return everything you passed into.returns.

.returnsSequence( table sequence, any default )

If you need to specify a sequence of values that your stub will return as it's called,.returnsSequence is the right choice.

Every time your stub is called, it will return the next value in the sequence table.One cool trick is to include gaps in your sequence table. So, for example, if you wanted to return"" for every call except the 6th one, you could do:

stub(net,"ReadString").returnsSequence( { [6]="hello"},"")

This would makenet.ReadString return"" for the first 5 calls,"hello" for the 6th, and"" for every call after.

Note: Because lua discards all indices withnil values, using thedefault parameter will override any intentionalnils in your sequence table.


Async tests and thedone/fail functions

If your test relies on timers, hooks, callbacks, etc., then you need to run your test Asynchronously.

The test is otherwise completely normal, but it's your job to tell GLuaTest when the test is done by callingdone() orfail() anywhere in your test.

done() and timeout functionality

If your test fails for some reason before it can calldone(), it'll be marked as having failed after timing out.

If you know the maximum amount of time your test will take, you can include thetimeout key on the test with the number of seconds to wait until failing the test.

If you don't include atimeout on your Test Case, you'll have to wait for the default 60-second timer before the test can complete. So if speed is important to you, consider setting a conservativetimeout value for your async tests.

For example, say we were trying to test this code:

-- lua/my_project/main.luaMyProject= {didRun=false }functionMyProject:StartRun()timer.Simple(2,function()MyProject.didRun=trueend )end

We want to make sure that whenMyProject:StartRun() is called, that it changesMyProject.didRun two seconds later.

Our test might look like:

-- lua/tests/my_project/start_run.luareturn {groupName="StartRun",cases= {        {name="Runs within two seconds of being called",async=true,timeout=3,-- If it hasn't finished in 3 seconds, something went wrong and it can be marked as failedfunc=function()MyProject:StartRun()timer.Simple(2,function()expect(MyProject.didRun ).to.beTrue()done()end )end        }    }}

Thefail() function

In the event that you want to fail a test manually, you can callfail( "with a reason if you want to" ) anywhere in your test case.

This is useful if you have a callback/timer/etc thatshould never run, and indeed, fail your test if it does.

Here's an example:

-- lua/tests/my_project/async_failure.luareturn {groupName="Async Failure Examples",cases= {        {name="HTTP Request succeeds",async=true,timeout=5,func=function()localsuccess=function(body )-- Expect exactly 1024 bytes in the body (for example)expect(#body ).to.equal(1024 )done()endlocalfailure=function(reason )-- This shouldn't ever happen! If it does, we need to fail the test instead of letting it time out.fail("HTTP Request failed with reason:"..reason )-- We don't need to call done() here because we already called fail() :)endhttp.Fetch("my url",success,failure )end        }    }}

Before / After functions

You may find yourself writing a lot of repetitive setup/teardown code in each of your Test Cases.

GLuaTest has a few convenience functions for you to use.

beforeEach/afterEach

Here's an example of howbeforeEach andafterEach could make your life easier while working with entities:

-- lua/tests/my_project/tickle_monster.luareturn {groupName="Tickle Monster",beforeEach=function(state )state.ent=ents.Create("sent_ticklemonster")state.ent:Spawn()end,afterEach=function(state )ifIsValid(state.ent )thenstate.ent:Remove()endend,cases= {        {name="Should accept the Tickle function",func=function(state )expect(state.ent.Tickle ).to.exist()expect(function()state.ent:Tickle()end ).to.succeed()expect(state.ent.wasTickled ).to.beTrue()end        },        {name="Should not be tickled by default",func=function(state )expect(state.ent.wasTickled ).to.beFalse()end        },        {name="Should have the correct model",func=function(state )expect(state.ent:GetModel() ).to.equal("materials/ticklemonster/default.mdl")end        }    }}

ThebeforeEach function created a brand-new Tickle Monster before every test, and theafterEach function deleted it, ensuring a clean test environment for each Test Case.

You'll notice thestate variable in that example. Thestate parameter is just a table that's shared between the before/after funcs and the Test Case function.

You also have access tobeforeAll andafterAll, which are self-explanatory. Please note that these two functionsdo not take astate table.



cleanup

Thecleanup function is a lot likeafterEach, except it's used only for a specific Test Case.

One common way to use this is to make sure that your test cleans up after itself even if it errors.


For example, say I want to test myWrapperFunc in this file:

-- lua/my_project/wrapper.luaGlobalFunc=function()return"Test"endWrapperFunc=function()returnGlobalFunc()end

I might write something like this:

{name="Wrapper should call the original function",func=function()localogGlobalFunc=GlobalFunclocalwasCalled=falseGlobalFunc=function()wasCalled=trueendWrapperFunc()expect(wasCalled ).to.beTrue()GlobalFunc=ogGlobalFuncend}

But consider, what would happen ifWrapperFunc errored, or the expectation failed?

GlobalFunc would still be defined as our local function for all future tests, potentially causing them to fail.

Instead, we can use GLuaTest'scleanup function to make our test safer:

-- lua/tests/my_project/wrapper.luareturn {groupName="Wrapper Functions",cases= {        {name="Wrapper should call the original function",func=function(state )state.GlobalFunc=GlobalFunclocalwasCalled=falseGlobalFunc=function()wasCalled=trueendWrapperFunc()expect(wasCalled ).to.beTrue()end,cleanup=function(state )GlobalFunc=state.GlobalFuncend        }    }}

Extensions

Extensions allow you to make your own test utilities or GLuaTest extensions

If you have a function or tool that you'd like to use in your tests, you can add it to thegluatest/extensions/ directory.

For example, if you were going to be testing a lot of entities in the same way, you could save yourself some headache by making an extension like this:

-- lua/gluatest/extensions/with_entity.lua--- Returns a GLuaTest TestGroup that is set up for Entity Testing--- @paramclassnamestring The class name of the entity to spawn--- @paramtestGroupGLuaTest_TestGroupWithEntityTests=function(classname,testGroup )testGroup.beforeEach=function(state )state.ents= {}functionstate.SpawnEnt()localent=ents.Create(classname )ent:Spawn()table.insert(state.ents,ent )returnentendfunctionstate.RemoveEnt(ent )table.RemoveByValue(state.ents,ent )ifIsValid(ent )thenent:Remove()endendendtestGroup.afterEach=function(state )for_,entinipairs(state.ents )doifIsValid(ent )thenent:Remove()endendendend

And then you can set up your test like this:

-- lua/tests/my_project/entity_1.luareturnWithEntityTests("my_special_entity", {groupName="My Special Entity Tests",-- No need to include any special setup/cleanup logic here as it's already handled by WithEntityTestscases= {        {name="Spawns with default values",func=function(state )localent=state.SpawnEnt()expect(ent:GetModel() ).to.equal("models/my_special_entity.mdl")expect(ent:GetColor() ).to.equal(Color(255,255,255 ) )end        }    }} )



Now what?

At this point, you might be excited to try GLuaTest. Maybe you already have a test file set up!

But... now what?

Check out this wiki page addressing that very question:

https://github.com/CFC-Servers/GLuaTest/wiki/I-set-up-GLuaTest...-now-what


Developers 👨‍💻

Information about working with GLuaTest

Interested in making an extension for GLuaTest?

Check out the wiki article outlining the hooks you can use:https://github.com/CFC-Servers/GLuaTest/wiki/Developers

About

A delightful unit testing framework for GLua projects

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

[8]ページ先頭

©2009-2026 Movatter.jp