Useful design patterns for unit testing/TDD?

后端 未结 9 652
一个人的身影
一个人的身影 2021-01-29 17:54

Reading this question has helped me solidify some of the problems I\'ve always had with unit-testing, TDD, et al.

Since coming across the TDD approach to development I k

相关标签:
9条回答
  • 2021-01-29 18:33

    Here Mike Clifton describes 24 test patterns from 2004. Its a useful heuristic when designing unit tests.

    http://www.codeproject.com/Articles/5772/Advanced-Unit-Test-Part-V-Unit-Test-Patterns

    Pass/Fail Patterns

    These patterns are your first line of defence (or attack, depending on your perspective) to guarantee good code. But be warned, they are deceptive in what they tell you about the code.

    • The Simple-Test Pattern
    • The Code-Path Pattern
    • The Parameter-Range Pattern

    Data Transaction Patterns

    Data transaction patterns are a start at embracing the issues of data persistence and communication. More on this topic is discussed under "Simulation Patterns". Also, these patterns intentionally omit stress testing, for example, loading on the server. This will be discussed under "Stress-Test Patterns".

    • The Simple-Data-I/O Pattern
    • The Constraint-Data Pattern
    • The Rollback Pattern

    Collection Management Patterns

    A lot of what applications do is manage collections of information. While there are a variety of collections available to the programmer, it is important to verify (and thus document) that the code is using the correct collection. This affects ordering and constraints.

    • The Collection-Order Pattern
    • The Enumeration Pattern The
    • Collection-Constraint Pattern
    • The Collection-Indexing Pattern

    Performance Patterns

    Unit testing should not just be concerned with function but also with form. How efficiently does the code under test perform its function? How fast? How much memory does it use? Does it trade off data insertion for data retrieval effectively? Does it free up resources correctly? These are all things that are under the purview of unit testing. By including performance patterns in the unit test, the implementer has a goal to reach, which results in better code, a better application, and a happier customer.

    • The Performance-Test Pattern

    Process Patterns

    Unit testing is intended to test, well, units...the basic functions of the application. It can be argued that testing processes should be relegated to the acceptance test procedures, however I don't buy into this argument. A process is just a different type of unit. Testing processes with a unit tester provide the same advantages as other unit testing--it documents the way the process is intended to work and the unit tester can aid the implementer by also testing the process out of sequence, rapidly identifying potential user interface issues as well. The term "process" also includes state transitions and business rules, both of which must be validated.

    • The Process-Sequence Pattern
    • The Process-State Pattern
    • The Process-Rule Pattern

    Simulation Patterns

    Data transactions are difficult to test because they often require a preset configuration, an open connection, and/or an online device (to name a few). Mock objects can come to the rescue by simulating the database, web service, user event, connection, and/or hardware with which the code is transacting. Mock objects also have the ability to create failure conditions that are very difficult to reproduce in the real world--a lossy connection, a slow server, a failed network hub, etc.

    • Mock-Object Pattern
    • The Service-Simulation Pattern
    • The Bit-Error-Simulation Pattern
    • The Component-Simulation Pattern

    Multithreading Patterns

    Unit testing multithreaded applications is probably one of the most difficult things to do because you have to set up a condition that by its very nature is intended to be asynchronous and therefore non-deterministic. This topic is probably a major article in itself, so I will provide only a very generic pattern here. Furthermore, to perform many threading tests correctly, the unit tester application must itself execute tests as separate threads so that the unit tester isn't disabled when one thread ends up in a wait state

    • The Signalled Pattern
    • The Deadlock-Resolution Pattern

    Stress-Test Patterns

    Most applications are tested in ideal environments--the programmer is using a fast machine with little network traffic, using small datasets. The real world is very different. Before something completely breaks, the application may suffer degradation and respond poorly or with errors to the user. Unit tests that verify the code's performance under stress should be met with equal fervor (if not more) than unit tests in an ideal environment.

    • The Bulk-Data-Stress-Test Pattern
    • The Resource-Stress-Test Pattern
    • The Loading-Test Pattern

    Presentation Layer Patterns

    One of the most challenging aspects of unit testing is verifying that information is getting to the user right at the presentation layer itself and that the internal workings of the application are correctly setting presentation layer state. Often, presentation layers are entangled with business objects, data objects, and control logic. If you're planning on unit testing the presentation layer, you have to realize that a clean separation of concerns is mandatory. Part of the solution involves developing an appropriate Model-View-Controller (MVC) architecture. The MVC architecture provides a means to develop good design practices when working with the presentation layer. However, it is easily abused. A certain amount of discipline is required to ensure that you are, in fact, implementing the MVC architecture correctly, rather than just in word alone.

    • The View-State Test Pattern
    • The Model-State Test Pattern
    0 讨论(0)
  • 2021-01-29 18:36

    Arrange, Act, Assert is a good example of a pattern that helps you structure your testing code around particular use cases.

    Here's some hypothetical C# code that demonstrates the pattern.

    [TestFixture]
    public class TestSomeUseCases() {
    
        // Service we want to test
        private TestableServiceImplementation service;
    
        // IoC-injected mock of service that's needed for TestableServiceImplementation
        private Mock<ISomeService> dependencyMock;
    
        public void Arrange() {
            // Create a mock of auxiliary service
            dependencyMock = new Mock<ISomeService>();
            dependencyMock.Setup(s => s.GetFirstNumber(It.IsAny<int>)).Return(1);
    
            // Create a tested service and inject the mock instance
            service = new TestableServiceImplementation(dependencyMock.Object);
        }
    
        public void Act() {
            service.ProcessFirstNumber();
        }
    
        [SetUp]
        public void Setup() {
            Arrange();
            Act();
        }
    
        [Test]
        public void Assert_That_First_Number_Was_Processed() {
            dependencyMock.Verify(d => d.GetFirstNumber(It.IsAny<int>()), Times.Exactly(1));
        }
    }
    

    If you have a lot of scenarios to test, you can extract a common abstract class with concrete Arrange & Act bits (or just Arrange) and implement the remaining abstract bits & test functions in the inherited classes that group test functions.

    0 讨论(0)
  • 2021-01-29 18:37

    Dependency Injection/IoC. Also read up on dependency injection frameworks such as SpringFramework and google-guice. They also target how to write testable code.

    0 讨论(0)
  • 2021-01-29 18:39

    Michael Feather's book Working Effectively With Legacy Code is exactly what you're looking for. He defines legacy code as 'code without tests' and talks about how to get it under test.

    As with most things it's one step at a time. When you make a change or a fix try to increase the test coverage. As time goes by you'll have a more complete set of tests. It talks about techniques for reducing coupling and how to fit test pieces between application logic.

    As noted in other answers dependency injection is one good way to write testable (and loosely coupled in general) code.

    0 讨论(0)
  • 2021-01-29 18:43

    A lot of problems like this can be solved with proper encapsulation. Or, you might have this problem if you are mixing your concerns. Say you've got code that validates a user, validates a domain object, then saves the domain object all in one method or class. You've mixed your concerns, and you aren't going to be happy. You need to separate those concerns (authentication/authorization, business logic, persistence) so you can test them in isolation.

    Design patterns help, but a lot of the exotic ones have very narrow problems to which they can be applied. Patterns like composite, command, are used often, and are simple.

    The guideline is: if it is very difficult to test something, you can probably refactor it into smaller problems and test the refactored bits in isolation. So if you have a 200 line method with 5 levels of if statements and a few for-loops, you might want to break that sucker up.

    So, start by seeing if you can make complicated code simpler by separating your concerns, and then see if you can make complicated code simpler by breaking it up. Of course if a design pattern jumps out at you, then go for it.

    0 讨论(0)
  • 2021-01-29 18:47

    I'd say you need mainly two things to test, and they go hand in hand:

    • Interfaces, interfaces, interfaces
    • dependency injection; this in conjunction with interfaces will help you swap parts at will to isolate the modules you want to test. You want to test your cron-like system that sends notifications to other services? instanciate it and substitute your real-code implementation for everything else by components obeying the correct interface but hard-wired to react in the way you want to test: mail notification? test what happens when the smtp server is down by throwing an exception

    I myself haven't mastered the art of unit testing (and i'm far from it), but this is where my main efforts are going currently. The problem is that i still don't design for tests, and as a result my code has to bend backwards to accomodate...

    0 讨论(0)
提交回复
热议问题