Jump to content
  • Advertisement
Sign in to follow this  
Angelic Ice

Unit Testing Classes with many class depencies

This topic is 504 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello : )

I wanted to start to test my code, but my code passes down dependencies.

E.g. class a passes down dependency x to class b and class b passes down x to c.
If I want to test class c, I need to instantiate a lot of other classes, because class c will use dependency x in diverse functions but also will interact with multiple other data-structure-functions.

Is this ok? Is this bad? How can I fix this?
Are there any tricks? I thought it is common practice to pass down dependencies as it shows from where what comes.

Thanks for your time : )

Share this post


Link to post
Share on other sites
Advertisement
Ideally your classes should not have many dependencies, precisely so you don't get yourself into the situation you describe. Fewer dependencies means your code is easier to test and -more importantly- easier to understand.

Using signals is one way to make testing easier. Instead of calling a member function of a class you depend on, you could issue a signal (think of it as a callback, if you are not familiar with signals). There will be some part of your code that instantiates all the objects and hooks all the signals and slots. Then for testing purposes you can hook the signals to dummy versions instead of having to instantiate the other class.

Share this post


Link to post
Share on other sites

I've started using unit tests after I wrote some good deal of code, so this problem has been haunting me for a while (and a way longer while to go...). Sometimes, you can simply refactor the code to have less dependencies. It is the most desirable solution.

I like to see mocking as a "last resort" effort. I've got into some situations where production code changed, but I forgot to update some mocks and the tests passed while the code failed in real world. Even though this is not supposed to happen with a good architecture and good mocks, it is a real, annoying risk.

In my case, I've noticed that I can make a clever use of factories methods/classes, sometimes from the application, other times only for the test code. This induces me to make my groups of classes more modular, reduces the amount of mocks objects and tend to make it way easier to update dozens of tests when something big changes. The drawback is that you might go over the invisible line of "unit testing" and step into integration testing...

Depending on you language and tools, some awesome mocking tools might be available, search on them. They potentially can do a lot more than you could ever do alone by code.

Last note...

E.g. class a passes down dependency x to class b and class b passes down x to c. If I want to test class c, I need to instantiate a lot of other classes, because class c will use dependency x in diverse functions but also will interact with multiple other data-structure-functions.
 

In this example, it simply feels like class C is hard to test for the sole reason that it holds way too many responsibilities or features. Overengineering is a real danger but more, smaller classes might help in making everything else testable.

Share this post


Link to post
Share on other sites

Thanks for all these cool bits of information : ) Should have mentioned it, I use C++... sorry : /

Sadly, many of my classes use member references (yes, the class owning the references are being destroyed before their referenced object!).

An example of my dependency chain:

There is one class in my code, which is pretty much the holder of all components: Scripting language module, painter class, event bus ...

Now, if the level-class needs to run Lua, someone has to pass it down to the level-class or if the level-painter wants to paint everything, it will require the painter class. So upon their construction, I pass it down to them as reference (as the modules already exist and will outlive their using classes). Sadly, the level-class sits within a state-class, same goes for the level-loader-class... So the state-class needs to take a lot upon construction.

As it seems, this is a terrible design for unit-testing? Especially, as mock classes in C++ sound like a ton of annoying work. But hey, this helps me to get a better understanding on how to structure my next project : )

Share this post


Link to post
Share on other sites

Can you be more specific, rather than "I pass it down to them"? 'It' and 'Them' isn't very clear. Maybe provide a single concrete example.

 

On the whole though, I don't see what your problem is. Objects have dependencies, which is normal, and you have to supply them, which is normal, even for testing. So, either supply them, or mock them, or refactor to avoid needing them. Nobody guaranteed it would be easy or convenient. :)

 

If your general design is "an object owns references to pretty much all the other objects" then yes, that is considered bad design, whether you're unit-testing or not.

Share this post


Link to post
Share on other sites

There is one class in my code, which is pretty much the holder of all components: Scripting language module, painter class, event bus ...

"One class has it all" is, as pointed out by kylotan, a good flag of bad design. Dependency injection usually results in huge dependencies lines naturally. Please do note, however, that no "100% this" solution is adequate. Sometimes, breaking DI in strategic points will actually make your code easier to change and understand.

In the case of "one has it all", I've handled this problem in a few ways

- Grouping dependencies into a single, container class.

- Passing a "kind of a" service locator, usually in the form of std::function or class.

- Removing dependencies. Some dependencies are there only for your convenience. Much more than you think.

- When all else is panic, meditate on Singletons, Service Locators or Global Variables.

This last item is a last resort. Singletons, Service Locators and Global Variables are all almost always an anti-pattern. But sometimes, you only need, say, one Keyboard Input Reader (or one mock of Keyboard Input Reader) accessible at a time. Passing this by parameters all the way down from the most abstract class of the engine down to your logic components will result in dependencies going over objects that don't even need them. It sux, and makes code evolving a  pain. If it is too generic, meditate. The bad solution might be, actually, good.

(ps: I usually hide globals within an indirection, to help changing definitions when in tests environments. Downside is that I need someone to initialize them.)

Share this post


Link to post
Share on other sites

- When all else is panic, meditate on Singletons, Service Locators or Global Variables.

Globals and singletons are anthemata for unit testing, not to mention threading and maintenance in general. If you need to set up an entire system just to test a unit, you're not unit testing.

Saying 'you are only using one instance of an input reader at a time' is not justification for a singleton or a global. You obviously need two input readers: the real one and the mock one.

I've put curses on many a programmer over the years for engaging in globals and singletons thereby ruling out unit testing. You can usually identify such software in advance: it's the crashy buggy stuff that no one want to touch.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!