get_posts() loop returns the same the_title() for each post

Making Milos’s comment into an answer….

The problem you are facing is the result of many wordpress template oriented functions expect some global variables. In WP_Query based loops setting those variable is done by calling the the_post() method of the WP_Query object, but with get_posts you need to call setup_postdata() for that.

My personal preference is to just try to avoid functions like the_title in favor of functions like get_the_title, that accept an explicit post id, whenever possible.