Tests antipatterns

Post on 12-Feb-2017

261 views 1 download

transcript

TestsAntipatterns

by Maciej Przewoźnik

AgendaSlow TestOveruse of mocksObscure Test

Agenda (2)Manual TestFragile TestErratic TestEager Test

Agenda (3)Verbose TestMystery GuestTest Code DuplicationHigh Test Maintenance Cost

Slow Test

Slow TestStabilizing a very slow testIndividual slow testsExecuting all tests is too long

Slow TestStabilizing a very slow testIndividual slow testsExecuting all tests is too long

- -

- -

Slow Test - stabilizing a very slowtest

Don't steer a rover on Mars from EarthExecute a test ...8 minutes later: failureFix & rerun10 minutes later: failureFix & rerun10 minutes later: failure

Slow Test - stabilizing a very slowtest

What to do in the meantime?GTD: don't multitaskCPU cache vs "brain cache"Context switch cost

Slow Test - stabilizing a very slowtest

What to do in the meantime?Solution: use the fastest machineHave the machine in the companyOther tests on slower machines

Slow Test - stabilizing a very slowtest

What to do in the meantime?For performance or stress tests with bigamount of dataSolution: use smaller data sets forstabilization firstThen run with bigger datasets

Slow Test - stabilizing a very slowtest

Solution: use REPL environment tostabilize it fasterReal-eval-print loopA failure shouldn't invalidate previous results!

Slow Test - stabilizing a very slowtest

Solution: stabilize parts separatelyGiven a test:

Generate a big file.Consume it.

Don't remove the file if failure occurs in (2).(to be continued)

Slow TestStabilizing a very slow testIndividual slow testsExecuting all tests is too long

Slow Test - individual tests areslow

What is a slow test?a developer gets irritated or bored waiting forfinishis tempted to get a break, surf the Internet,chat, or walk around

Slow Test - individual tests areslow

What is a slow test?Sometimes more than a second, sometimes amillisecondNot long individually, but all tests execution istoo longOr a group of tests is too long

Slow Test - individual tests areslow

Impact:Developers stop running them after every codechangeDevelopers wait for a coffee break, lunch or ameeting to run themDelayed feedback, loss of "flow"Decreases productivity and job satisfaction

Slow Test - individual tests areslow

Cause: Slow dependent component (1)Solution: Fast Fake ObjectsReal DB → Fake DB (e. g. In Memory DB)Real Cloud → In memory cloudFilesystems: HDD, SSD → RAM disk:

mount ­t tmpfs ­o size=200m tmpfs /mnt/tmp

Slow Test - individual tests areslow

Cause: Slow dependent component (2)Example: Backup applicationsSolution: Recorded testRecord operations done by backupapplicationsWarning: Recorded test may be a Fragile Test

Slow Test - individual tests areslow

Cause: Slow environment or layerSolution: Separate tested logic fromenvironment:

Hardware/GUI/HTTP layerThreads, processesClouds, etc.Kernel code

Slow Test - individual tests areslow

Cause: Over-engineered fixtureLong setup of many complicated objectsEven if most of them are not neededOr using full product in testsSolution: General Fixture → MinimalFixture

Slow Test - individual tests areslow

Cause: Manual testsSlow by definitionEven slower if repeated"Please check that the system just works"before a releaseAutomated tests may get written anyway later

Slow Test - individual tests areslow

Cause: Manual testsOften under pressure or under "give-it-to-me-now driven-development"No future returnsMay be worthless after just a single commitLack of tests automation will slow testdevelopment later

Slow Test - individual tests areslow

"Just Say No to More End-to-End Tests"Testing pyramid

Manual tests

End-to-end tests

Integration tests

Component tests

Fast Unit Tests

Slow TestStabilizing a very slow testIndividual slow testsExecuting all tests is too long

Slow Tests - executing all tests istoo long

In some big IT companies:build time: one minuterelease to production: 8 minutesnot just a release – a release to production!

What problems may it address?

Slow Tests - executing all tests istoo long

Impact: Developers context-switching,worse productivity

tests start ...15 min: a test failsfix & rerun20 min: a test fails

Slow Tests - executing all tests istoo long

Impact: Integrating code slowerBigger release queueSlower features delivery, missing deadlines

Slow Tests - executing all tests istoo long

Impact: Production big fixes aredelayed

A customer requires a fix, we need to test itRelease a quick fix or a well tested fix?

Slow Tests - executing all tests istoo long

Cause: Too much overlap betweentests

Test1: [ DATA LOADING ][doSth][check]

Test2: [ DATA LOADING ][doSth][check]

Test3: [ DATA LOADING ][doSth][check]

Slow Tests - executing all tests istoo long

Cause: Too much overlap betweentests

Use Shared Fixture?Shared setup for many testsWarning: may get closer to Erratic Tests,

High Test Maintenance Cost

Slow Tests - executing all tests istoo long

Cause: Too Many TestsToo many tests run too frequentlySolution: subsets of tests can be run atthe timeAll tests within larger period of timePrerelease tests: all with fast fakesPostrelease tests: with real objects

Slow Tests - executing all tests istoo long

Cause: Too Many TestsThe system is too largeSolution: break it into independentsubsystems

Slow Tests - executing all tests istoo long

Cause: Slow MachinesOnly one integration serverToo slow integration serversInsufficient parallelization: look at "top","vmstat"

Overuse of (strict)mocks

Overuse of mocksImpact:Tests can be harder tounderstand

The extra code detracts from the tester authorsintent

Overuse of mocksImpact: Tests are harder to maintain

Focus: implementation vs public interfaceFocus: implementation vs behaviorExtra code for mock behavior neededOver-specified softwareFragile

Overuse of mocksImpact: Less assurance that the codeis working properly

It's hard to guarantee that the mocks behave asreal implementationsEspecially over time

Overuse of mocksWhen the real object cannot be used:

They are too slowTheir setup is too complexThey charge money, etc.

Overuse of mocksAlternatives:

Fake objectsHermetic local servers (for cash transactions,etc.)

Consider thefollowing bad code ...

class TeaHouse(object):    def __init__(self, cash_service, tax_service):        self.cash_service = cash_service        self.tax_service = tax_service        self.money_total = 0

    def order_cup(self, credit_card):        cup = Tea()        self.cash_service.begin_transaction()        result = self.cash_service.charge(            credit_card, cup.price)        if result:            self.money_total += cup.price            self.cash_service.commit_transaction()            self.tax_service.register_transaction(                cup.price            )        else:            self.cash_service.cancel_transaction()

def should_compute_doubled_price_for_two_cups():    cs = mock(CashService)    cs.expect_call("begin_transaction").times(2)    cs.expect_call("charge").times(2).repeatedly(        Return(Cup())    )    cs.expect_call("end_transaction").times(2)    ts.mock(TaxService)    ts.expect_call("register_transaction").times(2)    t = TeaHouse(cs, ts)    c = CreditCard(money=usd(30))    t.set_tea_price(usd(10))    t.order_cup(c)    t.order_cup(c)    assert t.money_total == usd(20)

Overuse of mocksWe actually don't care for interaction with cashservice and tax service here

Overuse of mocksAlternative: use a fake

def should_compute_doubled_price_for_two_cups():    t = TeaHouse(        FakeInMemoryCashService(), FakeTaxService()    )    c = CreditCard(money=usd(30))    t.set_tea_price(usd(10))    t.order_cup(c)    t.order_cup(c)    assert t.money_total == usd(20)

Overuse of mocksAlternative: use a stub or a non-strictmock:

def should_compute_doubled_price_for_two_cups():    t = TeaHouse(        StubCashService(always_ok=True), StubTaxService()    )    c = CreditCard(money=usd(30))    t.set_tea_price(usd(10))    t.order_cup(c)    t.order_cup(c)    assert t.money_total == usd(20)

But in this particularsituation, even better

is ...

One function, oneresponsibility

Overuse of mocksclass TeaHouseOrders:    def __init__(self):        # no cash_service and no tax service!        self.orders = []    def order_cup(credit_card):        self.orders.append(TeaOrder())        return     def total_price():        return sum([o.price() for o in self.orders])

Overuse of mocksclass TeaHouseCash:    def charge(price):        self.cash_service.begin_transaction()        result = self.cash_service.charge(cc, price)        if result:            self.cash_service.commit_transaction()        else:            self.cancel_transaction()        return self.cups

Overuse of mocksdef should_compute_doubled_price_for_two_cups():    t = TeaHouseOrders()    t.set_tea_price(usd(10))    t.order_cup()    t.order_cup()    assert t.total_price() == usd(20)

Neither mocks nor fakes needed!Important concept: separate descriptionfrom execution

Obscure Test

Obscure TestImpact:

Harder to understandHarder to maintainDevelopers will not read them asdocumentationMay lead to Can result in a Buggy Test, and

then Production Bugs

Obscure TestGood Tests:

Tests should act as documentationAnd also a self-verifying executablespecification

Obscure TestIs it a good test?

def should_have_the_same_permissions_after_copy():    fs = FileSystem("r­w")    f = File("a.txt", "rwxrwxrwx")    fso = FileSystemOps(fs)    fso.copy(f, "b.txt")    assert(fso.fileperm("a.txt") == fso.fileperm("b.txt"))

Obscure TestIs it a good test?

def should_have_the_same_permissions_after_copy():    fs = FileSystem("r­w", "no­auto", 620, false,         true, true, 8096, 10*1024*1024*1024, 1000000)    f = File("a.txt", "rwxrwxrwx", 64, "xyzxyz",         "2016­12­12", "2016­12­12", "2016­12­12")    fso = FileSystemOps(fs, true, false, 1024, 11)    fso.copy(f, "b.txt")    assert(fso.fileperm("a.txt") == fso.fileperm("b.txt"))

Obscure TestCause: Irrelevant information

which values affect the outcome?There can be a hundred constants in a testcode

Obscure TestCause: Too much informationSolution: introduce higher-level testlanguage

def file_copy_permissions_should_be_as_original():    given_a_filesystem()    given_a_file_with_permissions("rwxrwxrwx")    when_a_file_is_copied()    then_the_new_file_has_permissions("rwxrwxrwx")

Obscure TestCause: Too much information, EagerTest

The test verifies too much functionality in asingleTest Method and therefore is hard tounderstand

Obscure TestCause: Too much information, EagerTest

def test_my_filesystem():    fs = FileSystem("r­w", "no­auto", 620, false, true,         true, 8096, 10*1024*1024*1024, 1000000)    assert(fs.isReadOnly() == false)    f = File("a.txt", "rwxrwxrwx", 64, "xyzxyz",         "2016­12­12", "2016­12­12", "2016­12­12")    assert(f.getPermissions() == "rwxrwxrwx")    assert(f.getCreationDate() == "2016­12­12")    fso = FileSystemOps(fs, true, false, 1024, 11)    fso.copy(f, "b.txt")    assert(fso.fileperm("a.txt") == fso.fileperm("b.txt"))    assert(fso.size("a.txt") == fso.size("b.txt")

Obscure TestCause: Too much information, EagerTest

Often a Fragile TestAnd a High-Maintenance Test

Obscure TestCause: Too much information

Not caring about clean codeNo refactoring"These are only tests""just do it in-line" mentality

Obscure TestCause: Too little information – MysteryGuest

Cause and effect not clearSomething is hidden from the Test Method

Obscure TestCause: Too little information – MysteryGuestDoesn't exist because is copied to root?

def test123():    fs = createFileSystem()    f = new File("a.txt", "rwxrwxrwx", 64*MB, "xyzxyz",         "2016­12­12", "2016­12­12", "2016­12­12"    )    fso = new FileSystemOps(fs, true, false, 1024, 11)    fso.copy(f, "/b.txt")    assert(fso.exists("b.txt") == false)

Obscure TestCause: Too little information – MysteryGuestNo, because createFileSystem creates read-onlyfilesystem:

def createFileSystem():    return new FileSystem(        "r­o", "no­auto", 620, false, true, true,         8096, 10*1024*1024*1024, 1000000    ))

Obscure TestCause: Setup-action-verify phases notclear

def test123():    fs = createFilesystem()    f = createFile("a.txt")    fso = createFso()    f2 = fso.copy(f, "b.txt")    s1 = fso.size("a.txt")    s2 = fso.size("b.txt")    assert(s1 == s2)

Do we test "size" here or "copy"?Solution: Given-when-then

Obscure TestCause: Setup-action-verify phases notclearSolution: Given-when-then:

    given_filesystem()    given_file()    given_file_system_operations()    when_the_file_is_copied()    then_the_new_file_size_is_as_original()

Obscure TestCause: Overcomplicated Fixture

class TestFSFixture:    def createRealOnlyFS():    def createRWFS():    def createJournalingFS():    def createTransactionalFS():    def createNFS():    def createHDFS():    def createNoAcFs():    def createTwoFilesystems():    def createPerfOptFS():    def createBuggyFS():    def createSlowFS():

Obscure TestCause: Overcomplicated FixtureThe fixture is too big and too complexTo understand the test, reading the fixture maybe necessarySolutions:

Divide a General Fixture into smaller FixturesUse Minimal FixtureUse Facades

Obscure TestCause: Indirect Testing

Testing one component through othercomponentsExample: Execute a product in debug mode tosee if log rotation worksRoot Cause: software not designed fortestability

SourcesGerard Meszaros - xUnit Test Patternstesting.googleblog.comPaul Chiusano and Rúnar Bjarnason - FunctionalProgramming in Scala

THE END