There are several approaches:
- Make the interactive parts optional, for example accept command line arguments, and only ask interactively if command lines are missing. Simplifies testing and automation a lot.
- Make the code an object on which it calls the ->readline method, and only create a Term::ReadLine object in its place if no such object was passed. That makes it easy to supply a "fake" Term::ReadLine object of your own creatin in the test code (that's called "dependency injection").
- Automate the whole thing across a process boundary with Expect-like modules.
I'd certainly go with the first option if possible, and fall back to the second option if ithe first isn't applicable.