why is it important? Requirements, tests and code. Outside-in. Changing gears. Hands on TDD, part 1 Rock Paper Scissors Lizard Spock. Lunch TDD Methodology, part 2: End-to-end. Dependency injection and mocking. Bug hunting. Pair programming. When tests go bad! Hands on TDD, part 2 Designing an email sign-up service using TDD. Today
do not test it? How can you test your code, if you do not understand the problem? How can you reason about a problem, if you try to solve it perfectly, first time? Why is TDD important?
code works, and stays working. A method which forces us to understand the requirement, problem and solution – before we commit to implementation decisions. A method which forces us to concentrate only on small parts of the problem, at any given time. So again, what is TDD?
behaviour. Run the test, watch and understand the failure. Be very wary of tests that pass first time! In a compiled language, it is likely that the first failure is a compiler error.
Make the test pass as quickly as possible – committing any sins required; speed trumps quality. Don’t engineer your code – we only need to solve the problem.
now engineer a clean solution. Run the tests regularly – to ensure that you don’t break anything. Be sure to refactor test code as well – the quality, maintainability and readability of test code is just as important as production code.
to maintain The more code that exists, the more bugs can exist. Bugs cost money. So lets not write too much code, eh? i.e. if there isn’t a requirement, you can’t write a test, and without a test, you can’t write production code. Keep honest. Requirements, Tests, Code
know the requirements: Who or what is the consumer? What are the expected behaviours? How do we know if it worked, or if something went wrong? Can you get some concrete examples? Scenarios? User stories? Requirements
test software and assert expected behaviours are expressed. A good unit test has three main parts: Arrange (Given) Act (When) Assert (Then) A great unit test should be readable – it should obviously express the requirement. If you get “stuck” not knowing how to write a test – you might not understand the requirement. Remember (this is important) – you are testing behaviours; not new code. Tests
production code: If you have access modifiers at your disposal – then tests call public members; everything else is implementation (internal or private). Don’t get too DRY: DRY is the enemy of Decoupling Keep your production code lean – write what is required to make the test pass, nothing more. Code
come inside-out: Database DAL Domain Model UX/UI, Web Service etc. But how often does that lead to feature drift, and functionality not actually required? Or how often do you get to that outer-layer, just to realised you don’t quite have what you need? This is an anti-pattern – let’s stop doing that! Outside-in
as such software exposes an API to consumers, which is used to “reach in” and take advantage of functionality. Since we’re testing behaviours and requirements – our unit tests must also live on the outside; and they let us drive our development inwards. Unit tests interact with our outer API. Each layer then defines requirements on each internal layer as it get towards the core of the application. Outside-in
fewer tests – less code to write and maintain. Cheaper. Build only what you need – maintain focus on a requirement in each layer and avoid feature drift. Helps to maintain a design focus on the external API. Stops your test suite from being highly coupled to your internal implementation – test your behaviours, and not your implementation. Outside-in
Yes – they test behaviours, through the entire application stack. But a chain is only as strong as the weakest link – if there is a bug in an internal component; it will surface on the outer layer. If we get really stuck… we can always change down gears.
in 5th gear. No obstacles, nothing to think about. But what happens when we hit a few junctions, and we don’t know where we’re going? And what if we end up off-road? What then? This is actually fairly analogous to TDD. Changing gears
having to find our way; but still able to go quite fast as we drive in development of behaviours. The key here is that we’re still solving the problem. 5th gear is reserved for when are driving in development of a well known problem – something solved time and time again. Here we can engineer at the same time. When we hit a difficult problem – it is like needing to go off-road. We’ve got to drop down gears and take it slowly. Now is a good time to use smaller, implementation based tests – to feel your way through the problem. But afterwards – you should feel safe to delete them! Changing gears
Or rather – is probably essential for good continuous delivery. Tests run against the software system, as it is deployed to a production-like environment. Puts an emphasis on ensuring that the deployment pipeline is functioning, and that software correctly interacts with integrated systems. End-to-End Testing
deal with some hard problems – first (like deployment, automation etc). Confidence that all software systems involved – work together in a production-like environment. End-to-End Testing
that provide the most benefit-to-cost; are not end- to-end tests. So how do we test our own code in isolation from other software systems? Databases Logging 3rd Party software and services With Dependency Injection and Mocking DI and Mocking
contract (i.e. rely heavily on interfaces and base classes). Objects with dependencies on other objects – must require that those dependencies are provided at initialization/construction. Can be used in conjunction with Inversion of Control to help reduce coupling and cyclic dependency graphs. But DI is a design principle – what has that got to do with testing? DI and Mocking
Ensure that our software is correctly integrated with external components and systems Exclude other software components and systems from our code-under-test Simulate behaviours in other components and systems that we need our software to deal with Use them to “peek” into the internals of our own code. DI and Mocking
a dependency to our software. The mock object is a “stand-in” – an object of our own creation that behaves in a way that aids the unit test suite; to prove or disprove correct behaviour from the code-under-test. We can hand roll mock objects – but there are also a wealth of frameworks that help use create mock objects quickly. DI and Mocking
and killing bugs in software. If you think of a bug as a requirement – you can easily replicate the issue in a unit test (trap it) and then fix the problematic production code (kill it). Bug hunting
and triple the fun! – Power Tool, Two Heads TDD helps maintain focus on solving problems. Pair programming helps share learning of a problem. The two work incredibly well in tandem. Pair programming
are that you’re not testing against a focused API – but rather internal implementation. Interdependent test cases. A test case that is expecting a “state” created by another test, i.e. not a unit test, testing in isolation. Discover and fix this will tools that randomize your test execution. When tests go bad (TDD anti-patterns)
behaviour that they are asserting in their name. Steps within a test case should be easy to read and relate to the name of the test. Other developers may need to maintain your test suite. Test suite code is a second class citizen: Not well refactored. Hard to maintain. When tests go bad (TDD anti-patterns)
times – use DI and mocking. UI testing: Brittle. UI undergoes refactoring perhaps more often than code. Expensive to write. Expensive to maintain. When tests go bad (TDD anti-patterns)