Question

I have been trying to figure out how to setup OCUnit unit tests for my game that uses cocos2d. So I followed these instructions to set up the test target initially: http://blog.shalomfriss.com/?p=894

Unfortunately, the code below triggers a signal SIGABRT when I run the test through Product > Test in Xcode. The function where it breaks seems to be at [CCGLProgram compileShader:type:byteArray:]

@implementation GameTests

- (void)setUp
{
    [super setUp];
}

- (void)tearDown
{
    // Tear-down code here.

    [super tearDown];
}

- (void)testExample
{
    STAssertNotNil([InitialLayer scene], @"initial layer scene was null.");
}

@end

This is the scene method from InitialLayer:

+(CCScene *) scene
{
    CCScene *scene = [CCScene node];
    InitialLayer*layer = [InitialLayernode];

    [scene addChild: layer];

    return scene;
}

Some other possibly relevant info:

  • target device: ipad 6.1
  • xcode version: 4.6.3
  • cocos2d version 2.1

Does anyone know how to fix this or is there a better way in general to setup the tests using OCUnit or any other testing framework?

Was it helpful?

Solution

You don't want to unit test anything cocos2d-related. Without properly setting up the cocos2d CCGLView, the Director, and so on (the code that goes in the app delegate) in the setUp method that won't work to begin with.

Even when you do set it up, you always will be testing cocos2d itself, which will make your tests less isolated and you're almost required to set up a fresh new scene for each test in order to test any code in subclassed nodes. Which means the time to write tests as well as how long each test runs quickly grows.

Further complicating matters is that cocos2d's updates are tied to the CADisplayLink updates, and I'm not even sure they get fired when running a unit test. Even when updates do fire, you can't test them because a unit test runs once and ends, it has no concept "runs over a certain period of time". So something like "move a node to x,y and check that it got there after 3 seconds" is inherently not testable with the way cocos2d and unit tests work. At least not without heavily modifying cocos2d itself.

If you consider that cocos2d represents the view of your app, the real question is: why would you even want to unit test anything view-related in the first place?

Instead what you can do is to design your classes completely independent of the view. For example you can create extra game logic classes that are added to a node's userObject property and then control the node - the actual testable game logic happens in the model classes. You can have additional controller classes which fire off things like running animations, or particle effects, and any other view-related stuff that doesn't need to be tested - because at worst it won't look right, but it won't affect the gameplay.

One of the key benefits of test-driven development is to make classes testable, and that means not making them depend on the view. So your first goal should actually be to make tests that run without cocos2d.

If you want to see how this can be done, check out OpenGW and specifically the class reference. Besides pushing a game object's (OGWEntity) position and rotation to the view delegate (OGWNode) there's absolutely no dependency on view code. The intermediary class that performs actions on the view based on the entity's state is OGWController.

All of this makes OpenGW a highly testable game simulation engine. In fact it is so decoupled from anything view-related that I'm in the process of making the same code run with both Sprite Kit and cocos2d-iphone.

OTHER TIPS

I agree that a complete test for everything is a waste of time but it doesn't mean to avoid it completely. It's good to separate your logic classes and methods and test them using the unit test to make sure that they are working correctly using gTest with modification to cmake. I did unit testing for util and logic classes like this: I added this to end of my CMAKE file

if(WINDOWS)

    set(UNIT_TEST_SOLUTION_NAME runUnitTests)

    FILE(GLOB_RECURSE USER_TEST "Classes/*.test.cpp")

    list(APPEND GAME_TEST
        ${USER_TEST}
        #${USER_HEADER}
        #${USER_CPP}
        Classes/utils/common_operators/CommonOperators.cpp
        Classes/utils/common_operators/CommonOperators.h
    )

    #list(FILTER GAME_TEST EXCLUDE REGEX "Classes/scenes/.*")
    #list(FILTER GAME_TEST EXCLUDE REGEX "AppDelegate.cpp")
    #list(FILTER GAME_TEST EXCLUDE REGEX "AppDelegate.h")
    #list(FILTER GAME_TEST EXCLUDE REGEX "Classes/text/persian_language_support/.*")

    set(run_unit_test_file
        ${GAME_TEST}
        proj.unit_test/main.cpp
    )

    #option(test "Build all tests." ON)
    enable_testing()

    find_package(GTest CONFIG REQUIRED)
    add_executable(${UNIT_TEST_SOLUTION_NAME} ${run_unit_test_file})
    target_link_libraries(runUnitTests GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main)
    add_test(runUnitTests runUnitTests)
endif()

And example test file is :

#include "gtest/gtest.h"
#include "CommonOperators.h"

TEST(CommonOperators, split) {
    auto case1Result = CommonOperators::split("mamad reza masood", ' ');
    std::vector<std::string> case1ExpectedResult = { "mamad", "reza", "masood" };
    EXPECT_EQ(case1Result, case1ExpectedResult);
}

Please note that I downloaded google test (gTest) using vckpg and then placed its folder in my root directory. Another note: For running your test in windows you need to define new solution with name that is mentioned in your cmake test part.And also you need to create main file like following in a new directory


#include "gtest/gtest.h"

int main(int argc, char** argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top