2

Let's suppose I want to follow TDD (Test driven development) and I want to implement a class that is supposed to return a considerable object. It starts to get really complex in my opinion and doesn't seem like a great design.

Let me give a concrete example I'm working on, a class that contains some methods, and one of them is public, that method does many things inside of it:

public class SpotTradeAlgorithm : ITradeAlgorithm
{
public async Task<DecisionResponse> DoAlgorithm(decimal openPrice, decimal closePrice, SymbolPairDto symbolPair)
{
...
}
}

Where DecisionResponse is

    public class DecisionResponse
    {
        private decimal _quantity;
        private decimal _price;
...
        public ActionEnum Action { get; private set; } = ActionEnum.NONE;
        public StateEnum State { get; private set; } = StateEnum.NOTFILLED;
        public decimal Quantity { get { return _quantity; } private set { _quantity = decimal.Round(value, 5); } }
        public decimal Price { get { return _price; } private set { _price = decimal.Round(value, 8); } }
        public decimal TotalBalance { get; private set; }
        public string OrderId { get; private set; }
        public decimal MakerFee { get; set; }
        public decimal TakerFee { get; set; }
        public int RelatedOrderId { get; set; }
        public string BuyOrderNumber { get; set; }
    }

Where SymbolPairDto is

public class SymbolPairDto
{
    public string Symbol1 { get; set; }
    public string Symbol2 { get; set; }
    public decimal BuyValue { get; set; }
    public decimal IncrementalBuyThreshold { get; set; }
    public decimal? BuyWeight { get; set; }
    public decimal? SellWeight { get; set; }
    public FeeDto feeDto { get; set; }
    public string Algorithm { get; set; }
}

As we can see the method has objects with considerable size as both argument and result. This doesn't seem like a great design to make the class testable. But in some cases, I don't know how to do it differently.

So my questions are,

  • Is this ok in some cases?
  • Should I try to avoid as much as possible objects for arguments and returns and make the classes as simple as possible, following the single principle responsibility? That would probably imply creating many more classes just for the same thing.
  • Is there any good lecture that provides good information on this dilemma?
  • Suggestions?
5
  • 4
    Seems very straightforward to test, what exactly is your problem with it?
    – nvoigt
    Feb 26, 2023 at 7:55
  • 1
    You’re asking a question about TDD, but between the lines your actual concern seems to be about class design. How to create supple OO models that tackle complex business problems is an entirely different topic.
    – Rik D
    Feb 26, 2023 at 9:39
  • @RikD dont know if youre the one that downvoted, but care to elaborate? maybe I can change the title if thats the case?
    – Nmaster88
    Feb 26, 2023 at 17:16
  • 1
    @Nmaster88 the question has very little to do with TDD. If you are concerned about the complexity of your classes (I think they aren't) the question would be better placed in StackExchange sites focused on code review. Or, simple remove TDD from the topic because it's misleading
    – Laiv
    Feb 27, 2023 at 7:31
  • 1. Eight data members don’t make a class complex. 2. Some things in life are complicated, and if a class doesn’t implement such complicated behaviour then it is broken, no matter how much you worry about complexity.
    – gnasher729
    May 21, 2023 at 6:53

3 Answers 3

6

You're focusing on the wrong thing.

A SymbolPairDto and a couple of prices go in and and a DecisionResponse comes out. So long as you know how to build all of those, and how to compare one DecisionResponse to another this is testable.

The hard part is coming up with the tests. Which is actually less about what goes in and goes out than about what decisions, that is, behaviors, are inside.

Those classes look big and scary but if there’s only a few if’s inside DoAlgorithm making decisions then you won’t need that many tests to cover all your cases.

After all, one little int already has way too many values to test each one. That’s why we don’t test that way. We test against the behaviors we need. Not everything that could possibly happen.

Given that, I care more about what DoAlgorithm is supposed to do than about how ugly this other stuff is. Long as I can build them and compare them I’m good.

1
  • thanks for having an useful reply in this question.
    – Nmaster88
    Feb 26, 2023 at 17:23
1

It's fine to have complex result objects, but you must be careful when writing your tests that each test only has expectations for the fields which matter to the behavior being tested. E.g. when you test the way DoAlgorithm chooses Price, you likely do not care about what OrderID it returns. When you test that DoAlgorithm properly formats the OrderID, you likely do not care about what Price it chooses.

If you always write expectations for every field (e.g. by comparing the entire return object to an expectation object via an equality operator) your tests will be cluttered with irrelevant expectations, making them both less readable and an obstacle to future refactoring. Instead, only write expectations for the specific fields which pertain to the behavior being tested.

This is simple for return objects - they enter the scope of the test code, so you just perform comparisons on individual fields. It's sometimes more complex for other result objects (e.g. when the object under test calls a function on a dependency to give it the result) but there's still almost always a framework-specific way to specify "do not care" fields.

-2

It seems very simple to me. Why wouldn’t it be testable? Is there anything you could remove from your classes - importantly without making things more complicated?

1
  • While I don't disagree with the core of this answer, the quality of the answer is severely lacking in many ways. I would suggest offering a better approach (or way to look at things), rather than (only) ask Socratic questions. The Q&A format of StackExchange renders Socratic questions unproductive as there is no iterative back and forth here, it's a straight up question-then-answer format.
    – Flater
    Mar 7, 2023 at 3:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.