Click here to Skip to main content
15,885,546 members
Articles / Programming Languages / C#

Advanced Unit Testing, Part IV - Fixture Setup/Teardown, Test Repetition And Performance Tests

Rate me:
Please Sign up or sign in to vote.
4.70/5 (43 votes)
8 Oct 200321 min read 207.1K   2.3K   187  
This article extends the unit testing framework, adding fixture setup/teardown capability and performance (time and memory) measurement/testing.
// Copyright 2003 Marchesin Eric <eric.marchesin@laposte.net>
// All rights reserved.
//
// This source file(s) may be redistributed by any means PROVIDING they
// are not sold for profit without the authors expressed written consent,
// and providing that this notice and the authors name and all copyright
// notices remain intact.
// THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED. USE IT AT YOUT OWN RISK. THE AUTHOR ACCEPTS NO
// LIABILITY FOR ANY DATA DAMAGE/LOSS THAT THIS PRODUCT MAY CAUSE.
using System;
using System.Collections;

namespace KALib
{
	/// <summary>
	/// The SortableList allows to maintain a list sorted as long as needed.
	/// If no IComparer interface has been provided at construction, then the list expects the Objects to implement IComparer.
	/// If the list is not sorted it behaves like an ordinary list.
	/// When sorted, the list's "Add" method will put new objects at the right place.
	/// As well the "Contains" and "IndexOf" methods will perform a binary search.
	/// </summary>
	public class SortableList : IList, ICloneable
	{
		private ArrayList _List;
		private IComparer _Comparer = null;
		private bool _UseObjectsComparison;

		/// <summary>
		/// Default constructor.
		/// Since no IComparer is provided here, added objects must implement the IComparer interface.
		/// </summary>
		public SortableList()
		{ InitProperties(null, 0); }

		/// <summary>
		/// Constructor.
		/// Since no IComparer is provided, added objects must implement the IComparer interface.
		/// </summary>
		/// <param name="Capacity">Capacity of the list (<see cref="ArrayList.Capacity">ArrayList.Capacity</see>)</param>
		public SortableList(int Capacity)
		{ InitProperties(null, Capacity); }

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="Comparer">Will be used to compare added elements for sort and search operations.</param>
		public SortableList(IComparer Comparer)
		{ InitProperties(Comparer, 0); }

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="Comparer">Will be used to compare added elements for sort and search operations.</param>
		/// <param name="Capacity">Capacity of the list (<see cref="ArrayList.Capacity">ArrayList.Capacity</see>)</param>
		public SortableList(IComparer Comparer, int Capacity)
		{ InitProperties(Comparer, Capacity); }

		/// <summary>
		/// 'Get only' property that indicates if the list is sorted.
		/// </summary>
		public bool IsSorted { get { return _IsSorted; } }
		private bool _IsSorted;
		/// <summary>
		/// Get : Indicates if the list must be kept sorted from now on.
		/// Set : Tells the list if it must stay sorted or not. Impossible to set to true if the list is not sorted.
		/// <see cref="KeepSorted">KeepSorted</see>==true implies that <see cref="IsSorted">IsSorted</see>==true
		/// </summary>
		/// <exception cref="InvalidOperationException">Cannot be set to true if the list is not sorted yet.</exception>
		public bool KeepSorted
		{
			set
			{
				if ( value==true && !_IsSorted ) throw new InvalidOperationException("The SortableList can only be kept sorted if it is sorted.");
				_KeepSorted = value;
			}
			get { return _KeepSorted; }
		}
		private bool _KeepSorted;
		/// <summary>
		/// If set to true, it will not be possible to add an object to the list if its value is already in the list.
		/// </summary>
		public bool AddDuplicates { set { _AddDuplicates = value; } get { return _AddDuplicates; } }
		private bool _AddDuplicates;

		/// <summary>
		/// IList implementation.
		/// Gets - or sets - object's value at a specified index.
		/// The set operation is impossible if the <see cref="KeepSorted">KeepSorted</see> property is set to true.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Index is less than zero or Index is greater than Count.</exception>
		/// <exception cref="InvalidOperationException">[] operator cannot be used to set a value if KeepSorted property is set to true.</exception>
		public object this[int Index]
		{
			get
			{
				if ( Index>=_List.Count || Index<0 ) throw new ArgumentOutOfRangeException("Index is less than zero or Index is greater than Count.");
				return _List[Index];
			}
			set
			{
				if ( _KeepSorted ) throw new InvalidOperationException("[] operator cannot be used to set a value if KeepSorted property is set to true.");
				if ( Index>=_List.Count || Index<0 ) throw new ArgumentOutOfRangeException("Index is less than zero or Index is greater than Count.");
				if ( ObjectIsCompliant(value) )
				{
					object OBefore = Index>0 ? _List[Index-1] : null;
					object OAfter = Index<Count-1 ? _List[Index+1] : null;
					if ( OBefore!=null && _Comparer.Compare(OBefore, value)>0 || OAfter!=null && _Comparer.Compare(value, OAfter)>0 ) _IsSorted = false;
					_List[Index] = value;
				}
			}
		}

		/// <summary>
		/// IList implementation.
		/// If the <see cref="KeepSorted">KeepSorted</see> property is set to true, the object will be added at the right place.
		/// Else it will be added at the end of the list.
		/// </summary>
		/// <param name="O">The object to add.</param>
		/// <returns>The index where the object has been added.</returns>
		/// <exception cref="ArgumentException">The SortableList is set to use object's IComparable interface, and the specifed object does not implement this interface.</exception>
		public int Add(object O)
		{
			int Return = -1;
			if ( ObjectIsCompliant(O) )
			{
				if ( _KeepSorted )
				{
					int Index = IndexOf(O);
					int NewIndex = Index>=0 ? Index : -Index-1;
					if (NewIndex>=Count) _List.Add(O);
					else _List.Insert(NewIndex, O);
					Return = NewIndex;
				}
				else
				{
					_IsSorted = false;
					Return = _List.Add(O);
				}
			}
			return Return;
		}

		/// <summary>
		/// IList implementation.
		/// Search for a specified object in the list.
		/// If the list is sorted, a <see cref="ArrayList.BinarySearch">BinarySearch</see> is performed using IComparer interface.
		/// Else the <see cref="Equals">Object.Equals</see> implementation is used.
		/// </summary>
		/// <param name="O">The object to look for</param>
		/// <returns>true if the object is in the list, otherwise false.</returns>
		public bool Contains(object O)
		{
			return _IsSorted ? _List.BinarySearch(O, _Comparer)>=0 : _List.Contains(O);
		}

		/// <summary>
		/// IList implementation.
		/// Returns the index of the specified object in the list.
		/// If the list is sorted, a <see cref="ArrayList.BinarySearch">BinarySearch</see> is performed using IComparer interface.
		/// Else the <see cref="Equals">Object.Equals</see> implementation of objects is used.
		/// </summary>
		/// <param name="O">The object to locate.</param>
		/// <returns>
		/// If the object has been found, a positive integer corresponding to its position.
		/// If the objects has not been found, a negative integer which is the bitwise complement of the index of the next element.
		/// </returns>
		public int IndexOf(object O)
		{
			int Result = -1;
			if ( _IsSorted )
			{
				Result = _List.BinarySearch(O, _Comparer);
				while ( Result>0 && _List[Result-1].Equals(O) ) Result--; // We want to point at the FIRST occurence
			}
			else Result = _List.IndexOf(O);
			return Result;
		}

		/// <summary>
		/// IList implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		public bool IsFixedSize { get { return _List.IsFixedSize ; } }

		/// <summary>
		/// IList implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		public bool IsReadOnly { get { return _List.IsReadOnly; } }

		/// <summary>
		/// IList implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		public void Clear() { _List.Clear(); }

		/// <summary>
		/// IList implementation.
		/// Inserts an objects at a specified index.
		/// Cannot be used if the list has its KeepSorted property set to true.
		/// </summary>
		/// <param name="Index">The index before which the object must be added.</param>
		/// <param name="O">The object to add.</param>
		/// <exception cref="ArgumentException">The SortableList is set to use object's IComparable interface, and the specifed object does not implement this interface.</exception>
		/// <exception cref="ArgumentOutOfRangeException">Index is less than zero or Index is greater than Count.</exception>
		/// <exception cref="InvalidOperationException">If the object is added at the specify index, the list will not be sorted any more and the <see cref="KeepSorted"/> property is set to true.</exception>
		public void Insert(int Index, object O)
		{
			if ( _KeepSorted ) throw new InvalidOperationException("Insert method cannot be called if KeepSorted property is set to true.");
			if ( Index>=_List.Count || Index<0 ) throw new ArgumentOutOfRangeException("Index is less than zero or Index is greater than Count.");
			if ( ObjectIsCompliant(O) )
			{
				object OBefore = Index>0 ? _List[Index-1] : null;
				object OAfter = _List[Index];
				if ( OBefore!=null && _Comparer.Compare(OBefore, O)>0 || OAfter!=null && _Comparer.Compare(O, OAfter)>0 ) _IsSorted = false;
				_List.Insert(Index, O);
			}
		}

		/// <summary>
		/// IList implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		/// <param name="Value">The object whose value must be removed if found in the list.</param>
		public void Remove(object Value) { _List.Remove(Value); }

		/// <summary>
		/// IList implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		/// <param name="Index">Index of object to remove.</param>
		public void RemoveAt(int Index) { _List.RemoveAt(Index); }

		/// <summary>
		/// IList.ICollection implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		/// <param name="array"></param>
		/// <param name="arrayIndex"></param>
		public void CopyTo(Array array, int arrayIndex) { _List.CopyTo(array, arrayIndex); }
		
		/// <summary>
		/// IList.ICollection implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		public int Count { get { return _List.Count; } }

		/// <summary>
		/// IList.ICollection implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		public bool IsSynchronized { get { return _List.IsSynchronized; } }

		/// <summary>
		/// IList.ICollection implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		public object SyncRoot { get { return _List.SyncRoot; } }

		/// <summary>
		/// IList.IEnumerable implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		/// <returns>Enumerator on the list.</returns>
		public IEnumerator GetEnumerator()
		{ return _List.GetEnumerator(); }

		/// <summary>
		/// ICloneable implementation.
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		/// <returns>Cloned object.</returns>
		public object Clone()
		{
			SortableList Clone = new SortableList(_Comparer, _List.Capacity);
			Clone._List = (ArrayList)_List.Clone();
			Clone._AddDuplicates = _AddDuplicates;
			Clone._IsSorted = _IsSorted;
			Clone._KeepSorted = _KeepSorted;
			return Clone;
		}

		/// <summary>
		/// Idem IndexOf(object), but starting at a specified position in the list
		/// </summary>
		/// <param name="O">The object to locate.</param>
		/// <param name="Start">The index for start position.</param>
		/// <returns></returns>
		public int IndexOf(object O, int Start)
		{
			int Result = -1;
			if ( _IsSorted )
			{
				Result = _List.BinarySearch(Start, _List.Count-Start, O, _Comparer);
				while ( Result>Start && _List[Result-1].Equals(O) ) Result--; // We want to point at the first occurence
			}
			else Result = _List.IndexOf(O, Start);
			return Result;
		}

		/// <summary>
		/// Defines an equality for two objects
		/// </summary>
		public delegate bool Equality(object O1, object O2);

		/// <summary>
		/// Idem IndexOf(object), but with a specified equality function
		/// </summary>
		/// <param name="O">The object to locate.</param>
		/// <param name="AreEqual">Equality function to use for the search.</param>
		/// <returns></returns>
		public int IndexOf(object O, Equality AreEqual)
		{
			for (int i=0; i<_List.Count; i++)
				if ( AreEqual(_List[i], O) ) return i;
			return -1;
		}

		/// <summary>
		/// Idem IndexOf(object), but with a start index and a specified equality function
		/// </summary>
		/// <param name="O">The object to locate.</param>
		/// <param name="Start">The index for start position.</param>
		/// <param name="AreEqual">Equality function to use for the search.</param>
		/// <returns></returns>
		public int IndexOf(object O, int Start, Equality AreEqual)
		{
			if ( Start<0 || Start>=_List.Count ) throw new ArgumentException("Start index must belong to [0; Count-1].");
			for (int i=Start; i<_List.Count; i++)
				if ( AreEqual(_List[i], O) ) return i;
			return -1;
		}

		/// <summary>
		/// Idem <see cref="ArrayList">ArrayList</see>
		/// </summary>
		public int Capacity { get {return _List.Capacity; } set { _List.Capacity = value; } }

		/// <summary>
		/// Object.ToString() override.
		/// Build a string to represent the list.
		/// </summary>
		/// <returns>The string refecting the list.</returns>
		public override string ToString()
		{
			string OutString = "{";
			for (int i=0; i<_List.Count; i++)
				OutString += _List[i].ToString() + (i!=_List.Count-1 ? "; " : "}");
			return OutString;
		}

		/// <summary>
		/// Object.Equals() override.
		/// </summary>
		/// <returns>true if object is equal to this, otherwise false.</returns>
		public override bool Equals(object O)
		{
			SortableList SL = (SortableList)O;
			if ( SL.Count!=Count ) return false;
			for (int i=0; i<Count; i++)
				if ( !SL[i].Equals(this[i]) ) return false;
			return true;
		}

		/// <summary>
		/// Object.GetHashCode() override.
		/// </summary>
		/// <returns>Hash code for this.</returns>
		public override int GetHashCode() { return _List.GetHashCode(); }

		/// <summary>
		/// Sorts the elements in the list using <see cref="ArrayList.Sort">ArrayList.Sort</see>.
		/// Does nothing if the list is already sorted.
		/// </summary>
		public void Sort()
		{
			if (_IsSorted) return;
			_List.Sort(_Comparer);
			_IsSorted = true;
		}

		/// <summary>
		/// If the <see cref="KeepSorted">KeepSorted</see> property is set to true, the object will be added at the right place.
		/// Else it will be appended to the list.
		/// </summary>
		/// <param name="C">The object to add.</param>
		/// <returns>The index where the object has been added.</returns>
		/// <exception cref="ArgumentException">The SortableList is set to use object's IComparable interface, and the specifed object does not implement this interface.</exception>
		public void AddRange(ICollection C)
		{
			if ( _KeepSorted ) foreach (object O in C) Add(O);
			else _List.AddRange(C);
		}

		/// <summary>
		/// Inserts a collection of objects at a specified index.
		/// Should not be used if the list is the KeepSorted property is set to true.
		/// </summary>
		/// <param name="Index">The index before which the objects must be added.</param>
		/// <param name="C">The object to add.</param>
		/// <exception cref="ArgumentException">The SortableList is set to use objects's IComparable interface, and the specifed object does not implement this interface.</exception>
		/// <exception cref="ArgumentOutOfRangeException">Index is less than zero or Index is greater than Count.</exception>
		/// <exception cref="InvalidOperationException">If the object is added at the specify index, the list will not be sorted any more and the <see cref="KeepSorted"/> property is set to true.</exception>
		public void InsertRange(int Index, ICollection C)
		{
			if ( _KeepSorted ) foreach (object O in C) Insert(Index++, O);
			else _List.InsertRange(Index, C);
		}

		/// <summary>
		/// Limits the number of occurrences of a specified value.
		/// Same values are equals according to the Equals() method of objects in the list.
		/// The first occurrences encountered are kept.
		/// </summary>
		/// <param name="Value">Value whose occurrences number must be limited.</param>
		/// <param name="NbValuesToKeep">Number of occurrences to keep</param>
		public void LimitNbOccurrences(object Value, int NbValuesToKeep)
		{
			if (Value==null) throw new ArgumentNullException("Value");
			int Pos = 0;
			while ( (Pos=IndexOf(Value, Pos)) >= 0 )
			{
				if ( NbValuesToKeep<=0 ) _List.RemoveAt(Pos);
				else { Pos++; NbValuesToKeep--; }
				if ( _IsSorted && _Comparer.Compare(_List[Pos], Value)>0 ) break; // No need to follow
			}
		}

		/// <summary>
		/// Removes all duplicates in the list.
		/// Each value encountered will have only one representant.
		/// </summary>
		public void RemoveDuplicates()
		{
			int PosIt;
			if (_IsSorted)
			{
				PosIt = 0;
				while ( PosIt<Count-1 )
				{
					if ( _Comparer.Compare(this[PosIt], this[PosIt+1])==0 ) RemoveAt(PosIt);
					else PosIt++;
				}
			}
			else
			{
				int Left = 0;
				while ( Left>=0 )
				{
					PosIt = Left+1;
					while (PosIt>0)
					{
						if ( Left!=PosIt && _Comparer.Compare(this[Left], this[PosIt])==0 ) RemoveAt(PosIt);
						else PosIt++;
					}
					Left++;
				}
			}
		}

		/// <summary>
		/// Returns the object of the list whose value is minimum
		/// </summary>
		/// <returns>The minimum object in the list</returns>
		public int IndexOfMin()
		{
			int RetInt = -1;
			if ( _List.Count>0 )
			{
				RetInt = 0;
				object RetObj = _List[0];
				if ( !_IsSorted )
				{
					for ( int i=1; i<_List.Count; i++ )
						if ( _Comparer.Compare(RetObj, _List[i])>0 )
						{
							RetObj = _List[i];
							RetInt = i;
						}
				}
			}
			return RetInt;
		}

		/// <summary>
		/// Returns the object of the list whose value is maximum
		/// </summary>
		/// <returns>The maximum object in the list</returns>
		public int IndexOfMax()
		{
			int RetInt = -1;
			if ( _List.Count>0 )
			{
				RetInt = _List.Count-1;
				object RetObj = _List[_List.Count-1];
				if ( !_IsSorted )
				{
					for ( int i=_List.Count-2; i>=0; i-- )
						if ( _Comparer.Compare(RetObj, _List[i])<0 )
						{
							RetObj = _List[i];
							RetInt = i;
						}
				}
			}
			return RetInt;
		}

		private bool ObjectIsCompliant(object O)
		{
			if ( _UseObjectsComparison && !(O is IComparable) ) throw new ArgumentException("The SortableList is set to use the IComparable interface of objects, and the object to add does not implement the IComparable interface.");
			if ( !_AddDuplicates && Contains(O) ) return false;
			return true;
		}

		private class Comparison : IComparer
		{
			public int Compare(object O1, object O2)
			{
				IComparable C = O1 as IComparable;
				return C.CompareTo(O2);
			}
		}

		private void InitProperties(IComparer Comparer, int Capacity)
		{
			if ( Comparer!=null )
			{
				_Comparer = Comparer;
				_UseObjectsComparison = false;
			}
			else
			{
				_Comparer = new Comparison();
				_UseObjectsComparison = true;
			}
			_List = Capacity>0 ? new ArrayList(Capacity) : new ArrayList();
			_IsSorted = true;
			_KeepSorted = true;
			_AddDuplicates = true;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions