I have done some JSF code digging and, yes, it seems that they have intentionally designed it to work this way. During the JSF lifecycle processing on the server, the clientId of the HtmlDataTable object continuously changes, since it includes the index of the "currently selected" row. And, in the 'Invoke Application' lifecycle phase, shortly before invoking the Ajax handler (i.e. the 'testAction' method), the JSF framework sets the row index of the HtmlDataTable to correspond to the row on which the clicked button is located.
As far as I understand from the JavaDoc of the UIData::getClientId(FacesContext) method (which is inherited by HtmlDataTable) the rationale, for including the row index in the clientId of UIData components, is to avoid clientId collision between child components that belong to different rows.