Specs2 breaks my test data, due to the way it work

2019-08-01 19:23发布

问题:

AFTER GETTING COMMENTS, AND FIGURED OUT HOW IT WORKS, I STILL THINK:

Would be nice though, if specs2 provides non consumable logic, along with consumable for iterators. Like if I don't use iterator.size method directly, but use specs' method like: haveSize

I have a test, which has a code:

 val ids = for(software <- parser) yield software.productID

 //ids.size must_== 2;

 ids.foreach(x => println(x))

It produces the output:

 1
 2

If I uncomment spec2 check (ids.size must_== 2), it will provide with empty output.

It seems spec2, goes over iterator (ids) and then I end up with iterator that points to the end of data (empty iterator). Thus I can not use this iterator anymore - in next tests.

Shod spec2/test framework behave like this?

So, if I use test like this (by some reason):

  ids.size must_== 2;
  ids.size must_== 2;

It would fail.

//--

Here we use iterator's size() method. So, I've got that that's ok having behaviour like that. But if use code like this:

Ids.toIterable must haveSize(2); // here we do not use iterator.size() method dirrectly
for(id <- ids) println(id). 

Prints nothing. It seems it still consumes my 'poor' iterator..


I found a work-around:

  val (it1, it2) = ids.duplicate    

  it1.size must_== 2;
  it2.size must_== 2;

And with this (convert to List), it will work also (like was suggested in comments):

val ids = for(software <- parser.toList) yield software.productID

But this exactly what spec2 could use by default (for methods like haveSize). (i've posted a bug).

回答1:

When you write iterator.size must_== 2 you consume yourself your iterator, specs2 just receives the value 2 (if the iterator has size 2). So there's nothing that specs2 can do about it.

Then you could ask specs2 to check the iterator size by writing iterator must haveSize(2) and expect the iterator not to be consumed. I don't think that this would be a good idea either. I think that iterator must haveSize(2) is reasonably expected to be a shorthand for iterator.size must be_==(2), which consumes the iterator.

My proposal is to leave to the user code the decision to control if something should be consumed or not. You can either leave your iterator as it is, or turn it to a Stream if you want to both assess its size and check its elements:

 val iterator = Seq({println(1); 1}, {println(2); 2}).iterator
 val elements = iterator.toStream

 elements must haveSize(2)
 elements(1) === 2


回答2:

An iterator is mutable, and can only be consumed once, in this case by calling .size. The scaladoc has helpful details, and mentions "one should never use an iterator after calling a method on it."

What you seem to need is a collection that's a subtype of Iterable (which contains a foreach method) such as List or Vector. If you are only calling foreach like in the example, all you need to do is surround the for loop in the first line with {..} then add .toIterable or toList, or the following will work.

val ids = parser.map(_.software).toIterable // collection would be the same as parser
ids.size must_== 2;
ids.size must_== 2;


标签: scala rspec2