What is a good method for creating TDD with legacy Delphi code with embedded SQL

I need to take some old Delphi code pointing to a database and make it support a new, better, database, which has a completely different scheme. The updated database has the same data. It contains a combination of stored procedures and embedded SQL.

Is there a good test-based development methodology that can help make sure I don't break anything? This code has no unit tests, and I need to make changes to many hardcoded SQL.

Simple execution after each change sounds erroneous and time consuming. I like the idea of ​​doing TDD or BDD, I just don't know how to do it.

+4
source share
3 answers

It's good that you want to get into unit testing, but I would like to warn you that he does not take it too hard.

Adding unit tests to legacy code is the main thing, and it is almost always completely impossible to stop other work just to add test cases. In addition, if you do not already have experience in TDD, this learning curve in itself can be a difficult obstacle to overcome.

However, if you persevere and take it step by step, your efforts will be rewarded at the end.

Problems you are likely to encounter:

  • Outdated applications are usually very complex for retro-fit with test cases. This is due to the fact that the code was not written with the possibility of verification.
    • Many procedures do too many things, so tests should take into account a large number of side effects.
    • The code is not properly autonomous, so setting up prerequisites for the test is a lot of work.
    • Entry points for testing / checking are often missing because they are not needed for production code; and therefore were not added in the first place.
    • Code often relies on global state. Either directly or through Singleton's. This global state (no matter where it is) destroys your test cases.
  • Group testing of databases is inherently more complex than other types of unit testing. The reason for this is that the test cases do not like the global state, and the databases are really massive containers of the global state. Problems are manifested in many ways:
    • If you use IDENTITY, Auto Inc columns or number generators of any shape: they either lead to a certain difference between each test run, or you need a way to reset these numbers between tests.
    • Databases are slow. After you have created a large number of test cases, it would be impractical to run all the tests between each change. (One of my Db test suites takes almost 10 minutes to run.)
    • If your database generates date / time values, this can also complicate testing. Especially if the database is running on another machine.
    • Database testing is complicated by the fact that a database has two aspects: its schema and its data. Therefore, if you want to test a new / changed stored procedure (part of a schema), it needs appropriate data changes and, possibly, other aspects of the schema (for example, tables / views).
  • Even without unnecessary complications, there are “normal problems” that you will have to deal with.
    • A global state often unexpectedly appears in some uncomfortable places. Consider Now() , which returns a TDateTime. It uses the global state: current time-time. If your system has rules based on time and date, these rules may return different results depending on when your tests will run. If you do not find an effective way to cope with this task, you will have a number of “unsustainable” test cases.
    • Writing test cases is a fundamentally different programming paradigm that most developers are used to. It’s very difficult to break old habits. The code style of the test code is almost declarative: Given , When I do this, I expect this to happen. Test tests should be simple and clear about what they are trying to achieve.
    • Learning curve can be complex. At first, you may be 3 times longer to write code if you are not familiar with test cases. And although it will eventually improve (perhaps even to the point where you are faster than before, with unstructured and random testing), other people around you are likely to express disappointment. (Not cool if it's your boss.)

I hope I did not discourage you, I have practical advice:

As the saying goes: "Do not tear off more than you can chew."
Be prepared to start slowly. For the moment, continue most of your work in a way that is familiar to you. But force yourself to write 1 or 2 test cases every day. At your convenience, you can increase this number.

Try to stick to the “proven and proven principles”
TDD Workflow: First write a test and make sure the test is not working . I know that it’s difficult to stick to a habit, but the principle serves a very important purpose. This is the level of confirmation that your test case proves an error / missing function. Too often, I have seen test code that will run with / without changing production, which makes the test somewhat useless.

For your database tests, you need to create a framework that works for you.
First, you will need a mechanism to get the database to its "base state". One of which should pass all your tests - no matter what order or how many times they run. Usually this will include some Reset between tests (but this should be pretty fast). Secondly, you will need an easy way to update your database schema to the expected production code.

Initially, you only need to test new features or bug fixes.
Avoid the temptation to check everything. Over time, your test coverage will increase. Once your framework and templates are installed, you can get the opportunity to start adding tests just to increase coverage.

Refactoring existing code.
When you become familiar with testing, you will learn about coding habits that make testing difficult. You will probably find many such problems in legacy code. Such code will not be checked as is. You may need to reorganize your code before you can test it. Obviously, this is not ideal, because you prefer tests that always pass to prove that your changes didn't break anything. A good refactoring book will give you some methods that you can use that will change the structure of your code without changing its behavior.

Testing existing code.
When writing a test for an existing procedure, review the code and identify each of the inputs that may affect different behavior. For instance. When there is an if statement , something will cause the condition to evaluate to True, and another to False. At a minimum, you will need a test for each permutation.

+11
source

If I were you, I would use DUnit to create a unit test project. For each of the objects, I would write test methods that will run old and new sentences, and then write methods to compare the results.

I would write a TTestCase class with the name, let's say TMyTestCase and add some helper methods to it, and then create my new test classes as subclasses from TMyTestCase .

The idea of ​​the ancestor class is to provide common functionality that makes it easy to write tests (comparison methods for intance) to increase performance and comfort.

+2
source

You can start creating a database simulator. Plug it in instead of the old one and see what it needs to do. Lots of work though

0
source

Source: https://habr.com/ru/post/1500723/