Skip to content

Instantly share code, notes, and snippets.

@ilkerde
Created March 31, 2011 07:40
Show Gist options
  • Save ilkerde/895974 to your computer and use it in GitHub Desktop.
Save ilkerde/895974 to your computer and use it in GitHub Desktop.
What do you expect? Fail or Pass? Me expects Fail!
/* Question:
What do you expect from assertion below? Fail or Pass?
*/
List<string> aList = new List<string> { "any" };
List<string> anEmptyList = new List<string>();
aList.ShouldContain(anEmptyList); // this currently passes!
/*
My opinion: above assertion should fail. My spec for this is:
*/
public class when_a_list_contains_an_element_and_another_list_is_empty
{
It should_fail_the__ShouldContains__assertion =
() => SpecException.ShouldBeOfType<SpecificationException>();
Because of =
() => SpecException = Catch.Exception(() => AList.ShouldContain(AnotherList));
Establish context =
() =>
{
Element = "An Element";
AList = new List<string> {Element};
AnotherList = new List<string>();
};
static Exception SpecException;
static List<string> AList;
static List<string> AnotherList;
static string Element;
}
/*
What do you think?
*/
@forki
Copy link

forki commented Mar 31, 2011

I think it should pass.

My argument would be the same as "an empty string is infix of every string".

[http://de.wikipedia.org/wiki/Infix_(Theoretische_Informatik)#Infix - German]

@ilkerde
Copy link
Author

ilkerde commented Mar 31, 2011

IMHO the assertion should fail. I added specs for machine.specifications already: http://bit.ly/f9sNmK

@forki
Copy link

forki commented Mar 31, 2011

Can you give a practical example where this would make sense? Maybe I am thinking too "theoretical".

@forki
Copy link

forki commented Mar 31, 2011

You are suggesting "an empty should contain an empty list" but "an non-empty list should not contain an empty list". I consider this a contradiction since we are not talking about equals.

@ilkerde
Copy link
Author

ilkerde commented Mar 31, 2011

@forki Very plausible argumentation. I'm not quite sure if this is fits with my perspective of that assertion.

@forki
Copy link

forki commented Mar 31, 2011

Another point:
Consider a string as a list of char (like in Haskell). Now we have this notion of "contains an infix" and this would match my ShouldContain expectations above. But I am not sure if this is what's going on here, since "contains an infix" also means all elements are in the given order. I think we should use more concrete assertions here. In NaturalSpec we have something like "it should contain_all_elements_from" and "it should contain_no_other_elements_than" (see https://github.com/forki/NaturalSpec/blob/master/src/app/NaturalSpec/SequenceFunctions.fs).

Maybe the words "ShouldContain" are not strong enough.

@agross
Copy link

agross commented Mar 31, 2011

@forki, thank you for your crisp and technically correct explanation! I believe ShouldContain was used because .NET developers are used to list.Contains(). MSpec's current implementation does not respect the element order, so I basically use list.SequenceEqual(other).ShouldBeTrue() if I need to assert that the order is respected by the implementation (this maps to contain_no_other_elements_than, right?).

If you like we should include the strict collection assertions you have in NaturalSpec into MSpec, perhaps adding XML comments to describe the behaviorial difference to Should[Not]Contain and ShouldContainOnly.

@forki
Copy link

forki commented Mar 31, 2011

I think "contains" is not easy to define for IEnumerable.

Let consider the easy part. "IEnumerable y should contain element x". That's easy to implement for all implementations of IEnumerable:

    bool ContainsElement<T>(IEnumerable<T> y, T x)
    {
        foreach (var element in y)
        {
            if (element.Equals(x))
                return true;
        }
        return false;
    }

or simply by using LINQ:

    bool ContainsElement<T>(IEnumerable<T> y, T x)
    {
        return y.Contains(x);
    }

Now the hard part: "IEnumerable y should contain IEnumerable x". What does this mean? Let's look at some implementations

Set: "Set y should contain set x" (x is subset of y):

    bool ContainsSet<T>(HashSet<T> y, HashSet<T> x)
    {
        foreach (var element in y)
        {
            if (!ContainsElement(x, element))
                return false;
        }
        return true;
    }

or simply by:

    bool ContainsSet<T>(HashSet<T> y, HashSet<T> x)
    {
        return y.All(element => ContainsElement(x, element));
    }

or even:

    bool ContainsSet<T>(HashSet<T> y, HashSet<T> x)
    {
        return x.IsSubsetOf(y);
    }

List: "List y should contain list x":

Naïve implementation (like contains set):

    bool ContainsList<T>(List<T> y, List<T> x)
    {
        foreach (var element in y)
        {
            if (!ContainsElement(x, element))
                return false;
        }
        return true;
    }

I think this violates the statement above. We are not testing if the list y contains the list x. We are testing if y contains all elements of x in any order.

Second approach ("infix test"):

    // Don't use this. Indices might be wrong ;-)
    bool ContainsList<T>(List<T> y, List<T> x)
    {
        int j = 0;
        for (int i = 0; i < y.Count; i++ )
        {
            if (y[i].Equals(x[j]))
                j++;
            else
            {
                j = 0;
                continue;
            }
            if (j == x.Count)
                return true;                
        }
        return false;
    }

Better (faster) implementations would use Boyer-Moore, Rabin-Karp or something like that. (http://en.wikipedia.org/wiki/String_searching_algorithm)

The point is: You can't really say what "contains" means without knowing the concrete type of x.
I think this should be considered when we define new "ShouldContain" assertions.

@ilkerde
Copy link
Author

ilkerde commented Apr 1, 2011

@forki Thanks indeed for this great insight about type relevance. I didn't thought about this aspect before. Nonetheless, my initial perspective is a slightly different one.

To be honest I was a little puzzled after your comment regarding empty sets. Although your argumentation was fine and very plausible for me, I didn't feel like "yeah, this is what I wanted to do". I made up my mind about it yesterday and came to my personal conclusion that I failed to describe my intents and perspective in a precise manner.

That said, I strive to get better in communication. I'm willing to try to lay down my thoughts and "issues" with ShouldContain assertion once more. As a side note, I consider myself weak in formal languages and theoretical background. Hence, please bear with me if my explanation lacks formal/math correctness. Thanks.

Your hint regarding the wording of ShouldContain led me to the point to re-address my "uncomfyness" with the assertion. You said that it is indeed a difference whether it is being tested that set B is a subset of A or that sequence D is a subsequence of C. My expectation on the term Contains was different, though. For me, Contains expressed (in my particular context) something like "a true containment of B in A". Let's see if I can describe this a little better:

A = {a,b,c} , B = {b} , C = {}

naturally,

A.Contains(B) && A.Contains(C) && B.Contains(C) == true

However, my expectation was constrained on the cardinality of all sets, as

|A| > 0 , |B| > 0 , |C| > 0

which effectively whould render to

A.ContainsConstrained(B) == true && A.ContainsConstrained(C) == false

I think in math this should be exactly the same as:

Given a set A and B, it must be A ∩ B != {} and A ∩ B = B

Expressed in words: I want to assert that B is a non-empty set which is a full subset of A. Note: There's no relevance of order in stated constraints. Having said this, it's clear for me that "my expectation" of ShouldContain requires a clear wording. While thinking about this and reading more about empty sets, I stumbled over the term Inhabited Set (http://en.wikipedia.org/wiki/Inhabited_set). Basically, an inhabited set requires to be non-empty (I know that this is not really true for some logical models, nonetheless it is a viable expression in classical logic). With this background, I'd like to think about the elements of this non-empty "Inhabited set" as Inhabitants. A strong method name which then maps to all above stated contraints would then imho be something like
ShouldContainAllInhabitantsOf

So that I can state in my observations:
A.ShouldContainAllInhabitantsOf(B)

I truly hope that my second attempt in describing my "issue" on ShouldContain was a little better.
Please let me know if this makes sense for you, since I value any feedback to improve myself.

@forki
Copy link

forki commented Apr 1, 2011

Hi Ilker,

Given a set A and B, it must be A ∩ B != {} and A ∩ B = B

this can be reduced to:

B != {} and A ∩ B = B

I would write this as two expression:

It should_be_a_subset_of_A = () => A.ShouldContainAllElementsOf(B);
It should_be_nonempty = () => B.ShouldNotBeEmpty();

I think this separation is valid since the mathematical notation also distincts both cases and it would be good to communicate this in the specs. It also reads better in the error report (it might be the case that only one statement is broken).

Inhabited Sets might be a valid solution but I think Constructivism terms and arguments are not that widespread. ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment