Gives a glimpse of that Software Development is about, illustrated by 3 challenges:
- Avoiding misunderstandings using "Agile" methods
- Avoiding techical debt with clean code
- Delivering quality fast with automated tests
the beginning ➡ Many assumptions need to be made (possibly wrong ones) • Difficult to do accurate resource planing and time estimations for the whole process • Customer does not want what he/she needs until seeing final solution • Once design is made, it's difficult / costly to change
user and password # connect to the database # construct query # perform query for all users # iterate over result # if "only_active" is set, keep only active # return list -Doing too much -Mixing different levels of abstraction
... object Configuration: def get_datbase_user(): ... def get_database_password(): ... def fetch_ist_users(only_active): # Obtain all users from Database # iterate over result # if "only_active" is set, keep only active # return list Separated concerns into re-usable components
not re-usable In general, de-compose your problem into specific and generic aspects. Otherwise the whole is not re-usable. You benefit in the future, by being able to leverage your re- usable components and cut cost&time...
clearly, in advance+ • Focuses on one problem at a time ➡ Avoids designing too early for inexistent problems • Leads to better, re-usable, testable code • Leads to high test-coverage
assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): return "Hello, " + name greeter_test.py greeter.py We write a test for a further requirement, which initially fails
assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == ""): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py Once the implementation has been corrected to comply with this requirement, the test passes
assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == ""): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) One last requirement, and an initially failing test
assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == nil): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) ? Does this new implementation make all tests pass? ? ?
assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == nil): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) No, it actually breaks the second test. Note that we didn't need to analyse the whole problem. The testing framework does it for us.
assert(result == expected) def test("greet the world when no name"): expected = "Hello, World" result = greet("") assert(result == expected) def greet(name): if(name == nil || name == ""): return "Hello, World" else return "Hello, " + name greeter_test.py greeter.py def test("greet the world when nil"): expected = "Hello, World" result = greet(nil) assert(result == expected) Once fixed, all tests pass