Firstly, make sure that critical logic is closed, as per the Open/Closed Principle. As for the more flexible, open logic, use the Liskov Substitution Principle to enforce that subclasses are substitutable for their base classes. E.g. subclasses should pass the same requirements/tests as their parents. If you expect a parent class to act a certain way, all subclasses should also fulfill those duties.
You’ve probably heard how the classic rectangle/square analogy breaks this principle, so I won’t bore you to repeat it. Instead, let’s think of an analogy you might encounter in the Swift world. Imagine UIButton, UIImage, and their parent, UIView. If you are using a UIView to reference either a UIButton or UIImage, you can expect that every responsibility of UIView will be carried out by either subclass (animations, layouts, etc.). You can thank the Liskov Substitution Principle for that.
How can we enforce that Liskov Substitution? First, see the Open/Closed Principle for closing off critical functionality. Then, you can write some handy unit tests to ensure all subclasses maintain are substitutable for the parent.