Upload
chris-oldwood
View
63
Download
1
Embed Size (px)
DESCRIPTION
Modern Unit Testing practices act as a conduit for improved software designs that are more amenable to change and can be easily backed by automation for fast feedback on quality assurance. The necessity of reducing external dependencies forces us to design our modules with minimum coupling which can then be leveraged both at the module, component and subsystem levels in our testing. As we start to integrate our units into larger blocks and interface our resulting components with external systems we find ourselves switching nomenclature as we progress from Unit to Integration testing. But is a change in mindset and tooling really required? The xUnit testing framework is commonly perceived as an aid to Unit Testing but the constraints that it imposes on the architecture mean that it is an excellent mechanism for invoking arbitrary code in a restricted context. Tests can be partitioned by categorisation at the test and fixture level and through physical packaging leading to a flexible test code structure. Throw in its huge popularity and you have a simplified learning curve for expressing more that just unit tests. Using scenarios from his current system Chris aims to show how you can use a similar format and tooling for unit, component and integration level tests; albeit with a few liberties taken to work around the inherent differences with each methodology.
Citation preview
Using xUnit as a Swiss-Army Testing Toolkit
(Does ‘Unit’ Size Matter?)
ACCU Conference 2011
Chris [email protected]
Stream of Consciousness
• Developer Driven Testing
• The Essence of (x)Unit Testing
• Those Pesky Dependencies
• Code & Test Evolution in Practice
Stream of Consciousness
• Developer Driven Testing
• The Essence of (x)Unit Testing
• Those Pesky Dependencies
• Code & Test Evolution in Practice
Text Book Teststring[][] tests = { { "3", "4", "+", "7" }, { "9", "1", "-", "8" }, { "2", "3", "*", "6" }, { "9", "3", "/", "3" },};
void run_tests(){ var calculator = new Calculator();
foreach(var test in tests) { var lhs = test[0]; var rhs = test[1]; var op = test[2];
var result = calculator(lhs, rhs, op);
assert(result == test[3]); }}
Exercise Left for the Reader
External System 1 External System 2 External System 3
The System
42
ServicesDatabase
Unit
Integration
System Component
Stress
Lexicon of Testing
End-to-End
Regression
White Box
Black Box
Characterisation
Exploration
System
Integration
Component
Unit
Dependencies
Feedback
‘Unit’ Evolution
All Regression
Stream of Consciousness
• Developer Driven Testing
• The Essence of (x)Unit Testing
• Those Pesky Dependencies
• Code & Test Evolution in Practice
Test == Specification
public void Execute_Should_Elide_Agreement_When_No_Trades_Match(){ var trades = new List<Trade> { new Trade("trade-id", "product-a") }; var agreement = new Agreement("product-b"); var task = new PreparationTask(trades, agreement);
var result = task.Execute(s_services);
Assert.That(task.Agreement, Is.Null);}
Consistent Stylepublic void a_c_sharp_test(){ var arrangement = new Arrangement();
var result = arrangement.action();
Assert.That(result, Is.EqualTo(expectation));}
create procedure a_sql_testas declare arrangement varchar(100), result varchar(100)
exec action @input = arrangement, @output = result
exec AssertAreEqual @result, "expectation"go
Minimises Dependencies
MyService
External Service DatabaseFile System
Mock ExternalService
IExternalService IFileSystem IDatabase
Mock FileSystem
Mock Database
Promotes Arbitrary Code Execution
public void Prepare_Should_Elide_Agreement_When_No_Trades_Match(){ var trades = new List<Trade> { new Trade("trade-id", "product-a") }; var agreement = new Agreement("product-b"); var task = new PreparationTask(trades, agreement);
var result = task.Execute(s_services);
Assert.That(task.Agreement, Is.Null);}
LibraryEXE Stub
Test Runner LibraryTestsDebugger
Custom TestHarness
Automated Testing
• Lowers the barrier to running tests
• Regression testing is implicit
• Build server watches your back
Stream of Consciousness
• Developer Driven Testing
• The Essence of (x)Unit Testing
• Those Pesky Dependencies
• Code & Test Evolution in Practice
Pesky Dependencies
External System 1 External System 2 External System 3
The SystemService 2
Service 1
File-System
Database
xUnit Abuse
• Fight the Shadow Cache
• Invoke TearDown from SetUp
• Test/build failure isn’t absolute
File-System (Reading)
• Source Control directory
• Build server directory
• Resource files
File-System (Writing)
• TEMP directory
• Output directory
Database
• Per-user / per-branch workspace
• Only need schema not data (Integration)
• Can reuse existing unit test database
• Use same code revision for compatibility
• Use transactions to avoid residual effects
• Fake tables with CSV files
Database Asserts
public void AddCustomer_Should_Persist_The_Customer(){ const id = 1234; const name = "name";
var customer = new Customer(. . .);
using (var connection = AcquireConnection()) { CustomerDataMapper.AddCustomer(customer, connection);
Assert.That(RowExists("dbo.Customer", " CustomerId = {0}" + " AND CustomerName = '{1}'", id, name), Is.True); }}
Database SetUp/TearDown[TestFixture, TestCategory.DatabaseTest]public class SomeEntityTests : DatabaseTestBase{
[TestFixtureSetUp] public void FixtureSetUp { using(var connection = AcquireConnection()) { connection.Execute("insert into thingy_table values(1, 2, 3)");
connection.Execute("test.InsertThingy(1, 2, 3)"); } }
[TestFixtureTearDown] public void FixtureTearDown { using(var connection = AcquireConnection()) { connection.Execute("delete from thingy_table");
connection.Execute("test.DeleteAllThingys"); } }
}
Helper Base Classpublic class DatabaseTestBase{ public ISqlConnection AcquireConnection() { return . . . }
. . .
public bool RowExists(string table, string where, string params[]) { string filter = String.Format(where, params);
string sql = String.Format( "select count(*) as [Count] from {0} where {1}" , table, filter);
using (var connection = AcquireConnection()) { var reader = connection.ExecuteQuery(sql);
return (reader.GetInt("Count") == 1); } }
. . .}
External Systems
• Verify API behaviour
• Test internal façade
• Reliability varies (DEV vs PROD)
Stream of Consciousness
• Developer Driven Testing
• The Essence of (x)Unit Testing
• Those Pesky Dependencies
• Code & Test Evolution in Practice
System Architecture
Market Data Trade Data Analytics
The System
42
ServicesDatabase
Initial System Test
MarketData Service
TradeData Service
Analytics Service
Calculator
Test Runner
System Tests
[Test, TestCategory.SystemTest]public void Calculate_Answer(){ . . .
var result = c.calculate();
Assert.Equal(result, 42);}
Addressing External Risks
External MarketData Service API
External TradeData Service API
External MarketData Service Tests
External TradeData Service Tests
Test Runner
Internal Service Design
External ServiceAPI
External ServiceTests
Internal Service
ExternalService Facade
Internal ServiceTests
MockExternal Services
PerformanceTest Runner
Mock Service
Data Access Layer
DatabasePublic Interface
Database UnitTests
Data AccessLayer
Data AccessLayer Tests
Database APIMock Database
API
Mock DataAccess Layer
DatabasePublic Interface
External AnalyticsService
External MarketData Service API
External MarketData Service API
System Evolution
Mock MarketData Service
Mock TradeData Service
Mock AnalyticsService
Calculator
Test Runner
Unit / Integration/ System Tests
[Test, TestCategory.SystemTest]public void Calc_Answer_For_ABC_Plc(){ . . .
var result = c.calculate();
Assert.Equal(result, 41.75);}
Mock DataAccess Layer
MarketData Service
TradeData Service
Analytics Service
Data AccessLayer