Testing in Jemini

  13 posts   Feedicon  
Replies: 12 - Last Post: January 20, 2010 07:26
by: jaymcgavren
showing 1 - 13 of 13
 
Posted: January 11, 2010 21:31 by Logan Barnett

One thing that's been on our mind for a while is how to do test first with Jemini. I want to stay away from terms like BDD, TDD, and integration testing because a game/simulator is a totally different realm than a sequential webapp or GUI app, and we shouldn't limit our thinking about how we approach things.

It's highly recommended that any complex game logic be moved out to a behavior. That behavior is an API that your game designer (even if that's you) will consume. That behavior should be tested with your favorite unit testing framework (like RSpec). Have have lots of examples for various behaviors in the Jemini code itself if you need an example.

So how do we do our high level integration tests? This is what I came up with using Cucumber, my integration testing tool of choice, on the Life Tank. LifeTank is our most polished game at time of writing.

Feature:
  As a player
  I should be able to control a tank

  Scenario: Moving
    Given there is a test level
    And   the level uses physics
    And   there is ground
    And   gravity is set to 5.0
    And   there is a tank tied to player 1
    And   player 1's tank is positioned just above the ground in the center of the level
    When  player 1 holds the "a" key for 5 seconds
    Then  the tank is on the left side

  Scenario: Shooting Shells
    Given there is a test level
    And   the level uses physics
    And   there is ground
    And   gravity is set to 5.0
    And   there is a tank tied to player 1
    And   there is a tank tied to player 2
    And   player 1's tank is positioned just above the ground in the center of the level
    And   player 1's tank tank's angle is at 180 degrees

    When  the game runs for 5 seconds
    And   player 1 presses the "space" key
    Then  there should be a shell game object
    When  the game runs for 2 seconds
    Then  player two is not at full health
    Then  the shell is gone

I could refactor a few of these, but it's mostly going to be custom stuff. A lot of physics gets used in LifeTank, but it's hard or not useful to pull those out into steps. Another facet is that LifeTank has randomly generated terrain. Some slopes in the terrain are deliberately too steep to climb. This makes it hard to test moving without using some completely different configuration - at which point we aren't doing an integration test.

I think the best way to tackle this is view the game as a system or a simulation. We want to see a lot of objects interacting with each other in similar ways, and more importantly we want to the game to be testable in a way that allows us to define what's fun. Fun means the tank shells go up and come down just right. It means that the shells can only be fired so far, etc. It's hard to get a machine to do this in precise, numerical terms.

I'd like to see an app that allows us to run a mocked version of our game in an automated fashion, but still allow us to verify that things worked well. This can be used in a test-first manner that allows us to tweak code and values until we get things right where we ant them.

These are my stories I've written up for this app:

  • As a designer I should be able to create a new test.
  • As a designer I should be able to see a list of Jemini core game objects
  • As a designer I should be able to see a list of game objects for my project
  • As a designer I should be able to add game objects to the test
  • As a designer I should be able to select which managers I want my test to use
  • As a designer I should be able to specify code to be evaled in order to create each game object for the objects ctor
  • (*) As a designer I should be able to specify fields to adjust on any game object
  • As a designer I should be able to see a list of game objects I've added to the test
  • As a designer I should be able to record a test
  • As a designer I should be able to play a test
  • As a designer I should be able to loop a test
  • (*)As a designer I should be able to adjust fields while the test is playing/looping so I can see changes in realtime
  • (*)As a designer I should be able to save the field changes to the game object
  • As a designer I should be able to see the test run like it were a game

* is some nice features that won't get done on version 1

Let me know what you think. I think this could be a really helpful tool for us going forward!

 
Posted: January 12, 2010 01:12 by jaymcgavren
This "live tests for fun" environment sounds cool (maybe even necessary). But I find it off-putting that we have no Cucumber tests for more precise scenarios, and I think other Ruby devs will too.

You're concerned about randomness - it is the job of the test author to eliminate that randomness. Here's a re-write of your first example to eliminate the ambiguity...

Scenario: Moving
  Given there is a test level
  And   the level uses physics
  And   there is flat ground (generate this in Cucumber steps - isolate the randomness)
  And   gravity is set to 5.0
  And   there is a tank tied to player 1
  And   the tank is positioned at 200, 400
  When  player 1 holds the "a" key for 5 seconds
  Then  the tank's x position is about 0
  (Or substitute "less than 100", "near 90", etc.)


All I had to do was flatten the ground (setting the y position of all its points to equal values shouldn't be too difficult). Using specific number makes writing the steps easier.

Second scenario:

Scenario: Shooting Shells
  Given there is a test level
  And   the level uses physics
  And   there is ground
  And   gravity is set to 5.0
  And   there is a tank tied to player 1
  And   there is a tank tied to player 2
  And   player 1's tank is positioned at 200, 400
  And   player 2's tank is positioned at 250, 400
  And   player 1's tank's angle is at 90 degrees
  When  player 1 presses the "space" key
  And   the game runs for 0.5 seconds
  Then  there should be a shell game object
  When  the game runs for 2 seconds
  Then  player two is not at full health
  And  the shell is gone


Again, swap in a few specific numbers, and the steps get a lot more generic and easier to write. Hell, "position" can be used for almost every Spatial ever made.

I'm all in favor of experimenting with the "live test" environment. But it must not be done at the exclusion of Cucumber tests. We don't even have to include step files in the core library, but we darn well better make sure it's possible to write them.
 
Posted: January 12, 2010 19:04 by Logan Barnett

Jay, My main concern with Cucumber tests is that Cucumber seems to be really good for integration testing, but what you're seeing there is something along the lines of RSpec testing - instead of starting the game and playing it, we're now just seeing how a few particular things operate in highly controlled environments. This is not a bad thing, but I don't think we can truly achieve integration testing.

That said, your steps are much better than mine. I'm open to us going both directions at the same time, but I'd like your help with the Cucumber steps (and what level of steps we're going to define for all of our behaviors).

Just at a gut I think simulation tests vs cucumber is going to take a relatively equal amount of work. Which is more useful to us now and going forward?

 
Posted: January 13, 2010 02:55 by jaymcgavren
> Just at a gut I think simulation tests vs cucumber is going
> to take a relatively equal amount of work. Which is more
> useful to us now and going forward?

I'm going to say Cucumber. The simulation thing hasn't been done, so the burden of proving otherwise is on you. Smile

Already started on some sample steps to include with the generator, though I got thwarted by Given /(.+) is at position (x), (y)/ and such, due to the need to convert $1 to an object reference. (Hard to do in a sufficiently flexible fashion.) Should be easy to set up game states, physics managers and so forth, though.
 
Posted: January 15, 2010 04:44 by Logan Barnett

Remember that Jemini stole Rails' inflector. You can camelize.constantize on the string you get back. You aren't actually using the $n vars are you? (:

 
Posted: January 15, 2010 06:04 by jaymcgavren
No, $1 was a bit of shorthand on my part.

Getting the class name from the matched string isn't the problem, it's passing it to a helper module (similar to the paths.rb that cucumber-rails generates), and always getting the same object back.

Here's what I want to do, similar to the paths.rb that cucumber-rails generates:

module ObjectHelpers
 def object_for(name)
   case name

   when /the tank/
     @tank

   # Add more mappings here.

   else
     raise "Can't find mapping from \"#{name}\" to an object.\n" +
       "Now, go and add a mapping in #{__FILE__}"
   end
 end
end

World(ObjectHelpers)


That way I can have common steps like:

Given /^(.+) is located at ([\-\d\.]+), ([\-\d\.]+)$/ do |name, x, y|
  object_for(name).location = Vector.new(x.to_f, y.to_f)
end


But paths.rb gets to just return a String with the resolved path; I have to return an object. That @tank is local to ObjectHelpers, not the spec. Not sure how to fix this - passing bindings around or doing a bunch of instance_evals would get messy fast.

This is only a problem with common steps; obviously "Given /^the tank is located at ([\-\d\.]+), ([\-\d\.]+)$/" (as opposed to "(.+) is located at...") can just reference @tank created elsewhere within the same spec.

I've moved on; better to get Cucumber working in the first place and worry about what common steps we'll generate later.
 
Posted: January 15, 2010 19:46 by Logan Barnett

Just make the method global (on Object). I have some tests for a project that do the same thing. You're pretty much making it global anyways by including it in the world. Our behavior system should prevent collisions with object_for if someone gets clever and tries to use that for a production behavior d:

 
Posted: January 15, 2010 23:20 by jaymcgavren
Well, if I modified Object (and I am very reluctant to do that in *any* context) it would only be via features/support/env.rb. So the method wouldn't exist outside of Cucumber specs.

Blargh, does that mean I have to keep the objects in a global hash? $cucumber_objects['the tank'] = Tank.new? I might prefer leaving objects out of the common steps over writing a kludge like that.
 
Posted: January 18, 2010 16:14 by Logan Barnett

No, you can still use instance variables. I took your existing object_for code and modified it. If you make a file and drop this in the support dir (env gets generated typically, so leave it alone), you'll get the desired effect without feeling like a giant kludge:

def object_for(name)
  case name

  when /the tank/
    @tank

  # Add more mappings here.

  else
    raise "Can't find mapping from \"#{name}\" to an object.\n" +
      "Now, go and add a mapping in #{__FILE__}"
  end
end

Note the lack of module and World call. This puts a method on Object since the context of execution is on Object.

Granted, everything gets object_for. However, it's contextual with use of instance vars. Since it's on object, it's also the first thing to be overridden if someone decides to get cute.

 
Posted: January 20, 2010 07:26 by jaymcgavren
Well, I started out by trying to load an entire state, but I'm getting thwarted bigtime. Any attempt to load images or sounds into Slick fails unless you're in init() or the game loop (and of course, every real-world state is going to do that). That means creating a Game and AppGameContainer, but the init methods for those have so many side effects that I don't see a way to just load the game; seems like you *have* to actually start it. I can of course start stubbing stuff (already tried to in the ResourceManager, but that introduces more problems), but that's gonna take quite a bit of experimentation.

My next attempt is going to involve loading the desired Managers and GameObjects via explicit steps. After that, well, I'm giving up and waiting to see what Logan comes up with for a live test environment. Revisiting Cucumber might be more productive after the transition to JME (and I will definitely be pushing for the ability to set up a game environment without actually running it).

I *am* confident that I can set up RSpec within a newly-generated project. That should ease unit testing, at least.
 
Posted: January 12, 2010 16:56 by gustin
Awesome, this was exactly what I had in mind to help introduce BDD with Jemini as a teaching tool.

I tend to be as high-level as possible with cucumber tests, not sure about the data having to be so specific (tank at 200, 400). But data specifics could be wrapped in reusable steps.

I'd love to chip in on this integration if help is needed.

 
Posted: January 12, 2010 18:58 by jaymcgavren
For the Cucumber portion, I'd want to generate a features directory, Cucumber tasks for Rake, and some base sample steps (a la Webrat's Cucumber integration) when a new project is generated.

But since there's a physics and/or graphics engine involved, we're at least partially blazing a trail here. (Not sure how exactly to handle the "game runs for 5 seconds" step, for example - do we actually have to run for 5 seconds to avoid timing issues?)

So I think experiments would be necessary. Gustin, if you wanted to go the Cucumber route, I'd say the best thing would be to set it up in your own project. If you get some reusable sample steps, we could then add them to generated Jemini projects. That way you won't have to wait on us to roll something out, either.

For the live test environment, well, I suggest sitting back and seeing what Logan comes up with. If it's anything like his map editor, it won't take him long, and it'll be a unique and elegant solution.

I'll set to work on a features directory generator that can be used across projects tonight.
 
Posted: January 12, 2010 19:08 by Logan Barnett

You can rapidly apply deltas to the app's update method to simulate the passage of time. I recommend this is the only way that time elapses in line-based tests, because you don't want assertions on a different thread elapsing time, or allowing an update to go by between assertions.

showing 1 - 13 of 13
Replies: 12 - Last Post: January 20, 2010 07:26
by: jaymcgavren
  • Mysql
  • Glassfish
  • Jruby
  • Rails
  • Nblogo
Terms of Use; Privacy Policy;
© 2010, Oracle Corporation and/or its affiliates
(revision 20120518.3c65429)
 
 
Close
loading
Please Confirm
Close