Skip to content

prayagupa/java-tdd

Repository files navigation

Java TDD

  1. Unit Testing
  2. Integration Testing
  3. Contract Testing
  4. End2End Testing
  5. User Acceptance Testing

References:

1: Unit Testing

Java has JUnit for unit testing.

xUnit Test Patterns: Refactoring Test Code

TDD xunit

1.1 Test Doubles: Stub vs Mock vs Spy

Stub

  • It's a stub<> if you have the behaviour pre-determined, you don't verify the calls. stub also referred to as state-based.

  • They always respond with the same static response regardless of input.

    Ex.

    class SubjectToTest {
      private Dependency dependency;
      
      public String doSomething(){
          //dependency.doSomething() is already defined, so its mock[]ed
          dependency.doSomething() + "-something else";
      }
    }
    interface DependencyStub {
        String doSomething();
        String doSomething2();
    }
    
    class DependencyStubImpl implements DependencyStub {
    
      public String doSomething(){
         // define the behaviour
         return "static something";
      }
    
      public String doSomething2(){
        return "static something 2";
      }
    }
    public class Test {
      Dependency stub = new DependencyStubImpl();
      SubjectToTest subject = new SubjectToTest(stub);
    }

Spy

  • A spy would allow you to stub out one of subject functions and leave the rest of the class behaving as normal.

  • The spy can tell the test what parameters it was given, how many times it was called.

    def "should deliver and update the state"() {
      given:
      String orderId = "order-id"
      String newState = "state change"
    
      serviceA.serviceB = Spy(ServiceB)
      serviceA.serviceB.deliverOrder(orderId) >> newState
    
      when:
      String state = serviceA.deliverOrder(orderId)
    
      then:
      state == orderId + "-" + newState
    }

Mock

  • It is a mock<> if you verify() calls against it.

  • Allows complete control over the doubled entity and provide the same information as a spy regarding how the entity has been interacted with

  • Mocks are configured before the code under test is executed to behave however we would like.

      def "should execute and make relevant calls"() {
          serviceA.serviceB = Mock(ServiceB)
    
          given:
          String orderId = "order-id"
    
          when:
          serviceA.executeSomething(orderId)
    
          then:
          1 * serviceA.serviceB.doSomething1(_, _)
          1 * serviceA.serviceB.doSomething2(_)
          1 * serviceA.serviceB.doSomething3(_)
          1 * serviceA.serviceB.doSomething4(_, _)
      }

Testing exceptions

UT Practices

  • Test names should tell what it is testing (Documentation)

    def "should ship an order, given valid order" () {
      
    }
  • Test should be deterministic

  • Test one scenario per test

    def "should ship an order, given valid order" () {
    
      then:
      order.state == OrderState.Shipped
    }
  • Avoid database, cache, HTTP calls as part of unit tests(those are integration/ end to end tests)

    def "should ship an order, given valid order" () {
      given:
      subject.cacheService = Stub(CacheService)
      subject.shippingMicroservice = Stub(ShippingMicroservice)
      
      then:
      order.state == OrderState.Shipped
    } 
  • Tests should detect code smells in codebase.

  • Target quality over coverage

  • Static Methods are Death to Testability, Google Inc

    The basic issue with static methods is they are procedural code. I have no idea how to unit-test procedural code. Unit-testing assumes that I can instantiate a piece of my application in isolation. During the instantiation I wire the dependencies with mocks/friendlies which replace the real dependencies. With procedural programing there is nothing to "wire" since there are no objects, the code and data are separate.

Code coverage

  • If you make a certain level of coverage a target, people will try to attain it. The trouble is that high coverage numbers are too easy to reach with low quality testing.
  • If you are testing thoughtfully and well, I would expect a coverage percentage in the upper 80s or 90s.
References
- https://martinfowler.com/articles/mocksArentStubs.html#SoShouldIBeAClassicistOrAMockist - https://martinfowler.com/bliki/TestCoverage.html - [s-coverage plugin](https://github.com/nihil-os/scoverage-maven-plugin)
./gradlew clean test

## using maven
mvn clean test

## test HTML reports
mvn surefire-report:report

## using sbt
sbt test

ls -l target/surefire-reports/
total 872
-rw-r--r--  1 prayagupd NA\Domain Users   9558 Sep  3 10:13 TEST-com.pseudo.tdd.CaseClassFunSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users  13170 Sep  3 10:13 TEST-com.pseudo.tdd.ExceptionInsideTrySuccess.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8995 Sep  3 10:13 TEST-com.pseudo.tdd.FunctionalMapSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users  12323 Sep  3 10:13 TEST-com.pseudo.tdd.FutureSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users  12230 Sep  3 10:13 TEST-com.pseudo.tdd.FutureTests.xml
-rw-r--r--  1 prayagupd NA\Domain Users  12341 Sep  3 10:13 TEST-com.pseudo.tdd.GenericsTests.xml
-rw-r--r--  1 prayagupd NA\Domain Users  19352 Sep  3 10:13 TEST-com.pseudo.tdd.IUseStaticClassSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8623 Sep  3 10:13 TEST-com.pseudo.tdd.ImplicitlySpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8964 Sep  3 10:13 TEST-com.pseudo.tdd.MaybeMonadSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8920 Sep  3 10:13 TEST-com.pseudo.tdd.MockitoTestSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8648 Sep  3 10:13 TEST-com.pseudo.tdd.ObjectMapperSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8978 Sep  3 10:13 TEST-com.pseudo.tdd.ServiceASpec.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8688 Sep  3 10:13 TEST-com.pseudo.tdd.ServiceASpecsUsingScalaMock.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8872 Sep  3 10:13 TEST-com.pseudo.tdd.ServiceAStepwiseSpec.xml
-rw-r--r--  1 prayagupd NA\Domain Users  12265 Sep  3 10:13 TEST-com.pseudo.tdd.ServiceATest.xml
-rw-r--r--  1 prayagupd NA\Domain Users  19399 Sep  3 10:13 TEST-com.pseudo.tdd.StaticClassSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8618 Sep  3 10:13 TEST-com.pseudo.tdd.ThreeSumSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8777 Sep  3 10:13 TEST-com.pseudo.tdd.e2e.TestE2E.xml
-rw-r--r--  1 prayagupd NA\Domain Users  12165 Sep  3 10:13 TEST-com.pseudo.tdd.integration.RunCukeTests.xml
-rw-r--r--  1 prayagupd NA\Domain Users  12167 Sep  3 10:13 TEST-com.pseudo.tdd.integration.ServerIntSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8894 Sep  3 10:13 TEST-com.pseudo.tdd.scalamock.FunctionStubSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8702 Sep  3 10:13 TEST-com.pseudo.tdd.scalamock.GameScalaMockSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8648 Sep  3 10:13 TEST-com.pseudo.tdd.scalamock.MultipleParamsStub.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8690 Sep  3 10:13 TEST-com.pseudo.tdd.scalamock.MyScalamockTrait.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8635 Sep  3 10:13 TEST-com.pseudo.tdd.scalamock.NoParamStub.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8648 Sep  3 10:13 TEST-com.pseudo.tdd.scalamock.ScalaMockJavaClass.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8666 Sep  3 10:13 TEST-com.pseudo.tdd.scalamock.ScalaStubTrait.xml
-rw-r--r--  1 prayagupd NA\Domain Users   9002 Sep  3 10:13 TEST-com.pseudo.tdd.strategy.ApplicationRouterSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8946 Sep  3 10:13 TEST-com.pseudo.tdd.strategy.EventDispatcherSpecs.xml
-rw-r--r--  1 prayagupd NA\Domain Users   8529 Sep  3 10:13 TEST-org.scalatest.tools.DiscoverySuite-bbd206b0-f9b5-4984-86ac-de02598ddae3.xml
-rw-r--r--  1 prayagupd NA\Domain Users    271 Sep  3 10:13 com.pseudo.tdd.FutureTests.txt
-rw-r--r--  1 prayagupd NA\Domain Users    273 Sep  3 10:13 com.pseudo.tdd.GenericsTests.txt
-rw-r--r--  1 prayagupd NA\Domain Users   7154 Sep  3 10:13 com.pseudo.tdd.IUseStaticClassSpecs.txt
-rw-r--r--  1 prayagupd NA\Domain Users    272 Sep  3 10:13 com.pseudo.tdd.ServiceASpec.txt
-rw-r--r--  1 prayagupd NA\Domain Users    272 Sep  3 10:13 com.pseudo.tdd.ServiceATest.txt
-rw-r--r--  1 prayagupd NA\Domain Users   7146 Sep  3 10:13 com.pseudo.tdd.StaticClassSpecs.txt
-rw-r--r--  1 prayagupd NA\Domain Users    284 Sep  3 10:13 com.pseudo.tdd.integration.RunCukeTests.txt
-rw-r--r--  1 prayagupd NA\Domain Users    286 Sep  3 10:13 com.pseudo.tdd.integration.ServerIntSpecs.txt
-rw-r--r--  1 prayagupd NA\Domain Users   5401 Sep  3 10:13 test-suite.log

3: Smoke Testing/ Sanity Testing/ build verification test

A smoke tester will select and run a subset of test cases that cover 
the most important functionality of a component or system.

it is a set of tests run on each new build of a product to verify that the build is testable before the build is released into the hands of the test team

mvn shakedown

 verifies that software, which was previously developed and tested, still performs correctly after it was changed 
 or interfaced with other software.

Further TDD reading

Faults (or mutations) are automatically seeded into your code, then your tests are run.

If your tests fail then the mutation is killed, if your tests pass then the mutation lived.