4
\$\begingroup\$

Assume the following requirements:

enter image description here

Is it a good idea to use the Composite design-pattern for this, taking into account that there will be:

  • Only 1 TestPlan
  • a TestPlan can only have TestSequences
  • a TestSequence can only have Tests
  • a Test can only have Asserts

I've started coding using the Composite pattern but I got the feeling that this pattern cannot enforce the above rules. Is my feeling correct and should I use another design (pattern) here? Or is using the composite pattern here correct but is my implementation of it just wrong?

public abstract class TestElement
{
    public TestElement(int id)
    {
        this.Id = id;
    }

    public virtual void Add(TestElement element)
    {
        throw new NotImplementedException();
    }

    public virtual void Remove(TestElement element)
    {
        throw new NotImplementedException();
    }

    public virtual IEnumerable<TestElement> GetElements()
    {
        throw new NotImplementedException();
    }

    public virtual TestElement GetElement(int index)
    {
        throw new NotImplementedException();
    }

    public string Id { get; private set; }

    public abstract void Setup();

    public abstract void Teardown();

}


    public class TestPlan : TestElement
{
    // Here TestElement should only be of type TestSequence
    private IList<TestElement> Children = new List<TestElement>();

    public TestPlan(int id)
        : base(id)
    {
    }

    public override void Add(TestElement element)
    {
        this.Children.Add(element);
    }

    public override void Remove(TestElement element)
    {
        this.Children.Remove(element);
    }

    public override TestElement GetElement(int index)
    {
        return this.Children.ElementAt(index);
    }

    public override IEnumerable<TestElement> GetElements()
    {
        return this.Children;
    }

    public override void Setup()
    {
        Console.WriteLine("Setup executed for TestPlan " + Id);
    }

    public override void Teardown()
    {
        Console.WriteLine("Teardown executed for TestPlan " + Id);
    }
}


    public class TestSequence : TestElement
{
    // Here TestElement should only be of type Test
    private IList<TestElement> Children = new List<TestElement>();

    public TestSequence(int id)
        : base(id)
    {
    }

    public override void Add(TestElement element)
    {
        this.Children.Add(element);
    }

    public override void Remove(TestElement element)
    {
        this.Children.Remove(element);
    }

    public override TestElement GetElement(int index)
    {
        return this.Children.ElementAt(index);
    }

    public override IEnumerable<TestElement> GetElements()
    {
        return this.Children;
    }

    public override void Setup()
    {
        Console.WriteLine("Setup executed for TestSequence " + Id);
    }

    public override void Teardown()
    {
        Console.WriteLine("Teardown executed for TestSequence " + Id);
    }
}

public class Test : TestElement
{
    // Here TestElement should only be of type Assert
    private IList<TestElement> Children = new List<TestElement>();

    public Test(int id)
        : base(id)
    {
    }

    public override void Add(TestElement element)
    {
        this.Children.Add(element);
    }

    public override void Remove(TestElement element)
    {
        this.Children.Remove(element);
    }

    public override TestElement GetElement(int index)
    {
        return this.Children.ElementAt(index);
    }

    public override IEnumerable<TestElement> GetElements()
    {
        return this.Children;
    }

    public override void Setup()
    {
        Console.WriteLine("Setup executed for Test " + Id);
    }

    public override void Teardown()
    {
        Console.WriteLine("Teardown executed for Test " + Id);
    }
}



public class Assert : TestElement
{
    public Assert(int id)
        : base(id)
    {
    }

    public override void Setup()
    {
        throw new NotSupportedException("No setup with Assert.");
    }

    public override void Teardown()
    {
        throw new NotSupportedException("No teradown with Assert.");
    }

    public bool ExecuteAssert()
    {
        return true;
    }

}

\$\endgroup\$
4
\$\begingroup\$

I don't think this is an example of composite pattern. Composite is defined as

The Composite Pattern allows you to compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Main idea of Composite is to allow treating whole (several parts) as a individual part. I.e. when client asks Composite to do something, then it means that Composite will ask all it's Components to do same thing. But neither Setup nor Teardown methods of your Composite do not touch collection of TestElements. So, you simply have set of classes with some base class, but that is not Composite.

If you would like to treat TestSequence or TestPlan as a single Test then it would be a Composite. I.e. if you would add Run functionality to your base class (or interface)

public interface ITest
{
    void Run();
}

and TestSequence would run all it's tests:

public class TestSequence : ITest
{
    private IList<ITest> tests = new List<ITest>();

    public void Run()
    {
        Setup();
        foreach(var test in tests)
           test.Run();
        Teardown();
    }

    public void Setup()
    {
        Console.WriteLine("Setup executed for Test " + Id);
    }

    public void Teardown()
    {
        Console.WriteLine("Teardown executed for Test " + Id);
    }
}

Then TestSequence would be a Composite, because it's completely transparent for client whether it works with single part (Test) or with a whole (TestSequence, collection of Tests). Same with TestPlan - you can treat it as a single TestSequence. Thus you can treat TestSequence as single Test, then for client TestPlan can also be treated as single Test. Client can run TestPlan, and it will run all it's test sequences, which in their turn will run all tests.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ This makes sence. One more question though: is there a way to enforce that TestPlancan only contain TestSequenceas child (and so on)? Or is that something I can take care of in my Builder. \$\endgroup\$ – Koen Jan 10 '14 at 9:49
  • \$\begingroup\$ @Koen yes, you can. All client should know about TestPlan or TestSequence is that it is runnable. You can create base class CompositeTest which will provide collection for storing child tests and method Run() which runs all children (as above). In that case restriction of children types goes to builder and interface of composite (e.g. TestPlan can have only method Add (TestSequence sequence). If you don't need base composite class, then each composite can hold collection of specific children (i.e. TestSequece list for TestPlan) \$\endgroup\$ – Sergey Berezovskiy Jan 10 '14 at 10:13
  • \$\begingroup\$ Ok. Thx for the info. \$\endgroup\$ – Koen Jan 10 '14 at 10:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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